Check tsconfig "references" arrays (#2891)

Closes #2800

This PR makes it so that `check-scripts` will error out if you forget to
add a "references" entry to a tsconfig file when adding an internal
dependency in our monorepo.

If these project references are missed it can prevent TS from
building/rebuilding things when they need to be built/rebuilt.

### Change Type

- [x] `internal` — Any other changes that don't affect the published
package[^2]
This commit is contained in:
David Sheldrick 2024-02-21 13:07:53 +00:00 committed by GitHub
parent 5fd6b4dca7
commit 987a576423
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 228 additions and 34 deletions

View file

@ -44,6 +44,9 @@ jobs:
- name: Check scripts
run: yarn check-scripts
- name: Check tsconfigs
run: yarn check-tsconfigs
- name: Lint
run: yarn lint

View file

@ -7,10 +7,17 @@
"emitDeclarationOnly": false
},
"references": [
{ "path": "../../packages/tlsync" },
{ "path": "../../packages/tlschema" },
{ "path": "../../packages/validate" },
{ "path": "../../packages/store" },
{ "path": "../../packages/utils" }
{
"path": "../../packages/store"
},
{
"path": "../../packages/tlschema"
},
{
"path": "../../packages/tlsync"
},
{
"path": "../../packages/utils"
}
]
}

View file

@ -25,8 +25,14 @@
"include": ["**/*.ts", "**/*.tsx"],
"exclude": ["node_modules", "_archive"],
"references": [
{ "path": "../../packages/tlsync" },
{ "path": "../../packages/tldraw" },
{ "path": "../../packages/assets" }
{
"path": "../../packages/assets"
},
{
"path": "../../packages/tldraw"
},
{
"path": "../../packages/tlsync"
}
]
}

View file

@ -6,9 +6,11 @@
"outDir": "./.tsbuild"
},
"references": [
{ "path": "../../packages/tldraw" },
{ "path": "../../packages/utils" },
{ "path": "../../packages/assets" },
{ "path": "../../packages/validate" }
{
"path": "../../packages/assets"
},
{
"path": "../../packages/tldraw"
}
]
}

View file

@ -7,5 +7,9 @@
"emitDeclarationOnly": false,
"types": ["@cloudflare/workers-types", "@types/node"]
},
"references": []
"references": [
{
"path": "../../packages/utils"
}
]
}

View file

@ -19,5 +19,12 @@
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
"exclude": ["node_modules", "_archive"],
"references": [{ "path": "../../packages/utils" }, { "path": "../../packages/validate" }]
"references": [
{
"path": "../../packages/utils"
},
{
"path": "../../packages/validate"
}
]
}

View file

@ -23,5 +23,12 @@
"experimentalDecorators": true
},
"include": ["src", "../messages", "scripts"],
"references": [{ "path": "../../../packages/tldraw" }]
"references": [
{
"path": "../../../packages/assets"
},
{
"path": "../../../packages/tldraw"
}
]
}

View file

@ -23,5 +23,12 @@
"experimentalDecorators": true
},
"include": ["src", "../messages", "scripts"],
"references": [{ "path": "../../../packages/tldraw" }]
"references": [
{
"path": "../../../packages/editor"
},
{
"path": "../../../packages/tldraw"
}
]
}

View file

@ -53,6 +53,7 @@
"format": "prettier --write --cache \"**/*.{ts,tsx,js,jsx,json}\"",
"typecheck": "yarn refresh-assets && tsx scripts/typecheck.ts",
"check-scripts": "tsx scripts/check-scripts.ts",
"check-tsconfigs": "tsx scripts/check-tsconfigs.ts",
"api-check": "lazy api-check",
"test-ci": "lazy test-ci",
"test": "lazy test",

View file

@ -14,5 +14,9 @@
"rootDir": "src",
"resolveJsonModule": false
},
"references": [{ "path": "../utils" }]
"references": [
{
"path": "../utils"
}
]
}

View file

@ -11,10 +11,20 @@
"types": ["node", "@types/jest"]
},
"references": [
{ "path": "../tlschema" },
{ "path": "../store" },
{ "path": "../validate" },
{ "path": "../utils" },
{ "path": "../state" }
{
"path": "../state"
},
{
"path": "../store"
},
{
"path": "../tlschema"
},
{
"path": "../utils"
},
{
"path": "../validate"
}
]
}

View file

@ -5,6 +5,5 @@
"compilerOptions": {
"outDir": "./.tsbuild",
"rootDir": "src"
},
"references": [{ "path": "../utils" }]
}
}

View file

@ -6,5 +6,12 @@
"outDir": "./.tsbuild",
"rootDir": "src"
},
"references": [{ "path": "../utils" }, { "path": "../state" }]
"references": [
{
"path": "../state"
},
{
"path": "../utils"
}
]
}

View file

@ -4,9 +4,12 @@
"exclude": ["node_modules", "dist", "**/*.css", ".tsbuild*", "./scripts/legacy-translations"],
"compilerOptions": {
"outDir": "./.tsbuild",
// TODO: Enable noImplicitReturns
"noImplicitReturns": false,
"rootDir": "src"
},
"references": [{ "path": "../editor" }]
"references": [
{
"path": "../editor"
}
]
}

View file

@ -6,5 +6,18 @@
"outDir": "./.tsbuild",
"rootDir": "src"
},
"references": [{ "path": "../store" }, { "path": "../validate" }, { "path": "../utils" }]
"references": [
{
"path": "../state"
},
{
"path": "../store"
},
{
"path": "../utils"
},
{
"path": "../validate"
}
]
}

View file

@ -7,10 +7,20 @@
"rootDir": "src"
},
"references": [
{ "path": "../tldraw" },
{ "path": "../tlschema" },
{ "path": "../state" },
{ "path": "../store" },
{ "path": "../utils" }
{
"path": "../state"
},
{
"path": "../store"
},
{
"path": "../tldraw"
},
{
"path": "../tlschema"
},
{
"path": "../utils"
}
]
}

View file

@ -6,5 +6,9 @@
"outDir": "./.tsbuild",
"rootDir": "src"
},
"references": [{ "path": "../utils" }]
"references": [
{
"path": "../utils"
}
]
}

100
scripts/check-tsconfigs.ts Normal file
View file

@ -0,0 +1,100 @@
import kleur from 'kleur'
import { join, relative } from 'path'
import { readJsonIfExists, writeJsonFile } from './lib/file'
import { nicelog } from './lib/nicelog'
import { Package, getAllWorkspacePackages } from './lib/workspace'
const packagesWithoutTSConfigs: ReadonlySet<string> = new Set(['config'])
async function checkTsConfigs({ packages, fix }: { fix?: boolean; packages: Package[] }) {
let numErrors = 0
for (const workspace of packages) {
const tsconfigPath = join(workspace.path, 'tsconfig.json')
if (packagesWithoutTSConfigs.has(workspace.name)) {
continue
}
const tsconfig = (await readJsonIfExists(tsconfigPath)) as {
references?: { path: string }[]
}
if (!tsconfig) {
throw new Error('No tsconfig.json found at ' + tsconfigPath)
}
const tldrawDeps = Object.keys({
...workspace.packageJson.dependencies,
...workspace.packageJson.devDependencies,
}).filter((dep) => dep.startsWith('@tldraw/'))
const fixedDeps = []
const missingRefs = []
const currentRefs = new Set<string>([...(tsconfig.references?.map((ref) => ref.path) ?? [])])
for (const dep of tldrawDeps) {
// construct the expected path to the dependency's tsconfig
const matchingWorkspace = packages.find((p) => p.name === dep)
if (!matchingWorkspace) {
throw new Error(`No workspace found for ${dep}`)
}
const tsconfigReferencePath = relative(workspace.path, matchingWorkspace.path)
fixedDeps.push({ path: tsconfigReferencePath })
if (currentRefs.has(tsconfigReferencePath)) {
currentRefs.delete(tsconfigReferencePath)
} else {
missingRefs.push(dep)
}
}
fixedDeps.sort((a, b) => a.path.localeCompare(b.path))
if (currentRefs.size > 0) {
if (fix) {
tsconfig.references = fixedDeps
writeJsonFile(tsconfigPath, tsconfig)
} else {
numErrors++
nicelog(
[
'❌ ',
kleur.red(`${workspace.name}: `),
kleur.blue(relative(process.cwd(), tsconfigPath)),
kleur.grey(' has unnecessary reference(s) to '),
kleur.red([...currentRefs].join(', ')),
].join('')
)
}
}
if (missingRefs.length) {
if (fix) {
tsconfig.references = fixedDeps
writeJsonFile(tsconfigPath, tsconfig)
} else {
numErrors++
nicelog(
[
'❌ ',
kleur.red(`${workspace.name}: `),
kleur.blue(relative(process.cwd(), tsconfigPath)),
kleur.grey(' is missing reference(s) to '),
kleur.red(missingRefs.join(', ')),
].join('')
)
nicelog('The references entry should look like this:')
nicelog('"references": ' + JSON.stringify(fixedDeps, null, 2))
}
}
}
if (numErrors > 0) {
nicelog('Run `yarn check-tsconfigs --fix` to fix these problems')
process.exit(1)
}
}
async function main({ fix }: { fix?: boolean }) {
const packages = await getAllWorkspacePackages()
await checkTsConfigs({ packages, fix })
}
main({
fix: process.argv.includes('--fix'),
})