From 987a576423703afce84b3e35e9b6eb49bf673d95 Mon Sep 17 00:00:00 2001 From: David Sheldrick Date: Wed, 21 Feb 2024 13:07:53 +0000 Subject: [PATCH] Check tsconfig "references" arrays (#2891) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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] --- .github/workflows/checks.yml | 3 + apps/dotcom-worker/tsconfig.json | 17 +++-- apps/dotcom/tsconfig.json | 12 +++- apps/examples/tsconfig.json | 10 +-- apps/health-worker/tsconfig.json | 6 +- apps/huppy/tsconfig.json | 9 ++- apps/vscode/editor/tsconfig.json | 9 ++- apps/vscode/extension/tsconfig.json | 9 ++- package.json | 1 + packages/assets/tsconfig.json | 6 +- packages/editor/tsconfig.json | 20 ++++-- packages/state/tsconfig.json | 3 +- packages/store/tsconfig.json | 9 ++- packages/tldraw/tsconfig.json | 7 +- packages/tlschema/tsconfig.json | 15 ++++- packages/tlsync/tsconfig.json | 20 ++++-- packages/validate/tsconfig.json | 6 +- scripts/check-tsconfigs.ts | 100 ++++++++++++++++++++++++++++ 18 files changed, 228 insertions(+), 34 deletions(-) create mode 100644 scripts/check-tsconfigs.ts diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 1f8c36b80..780e08332 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -44,6 +44,9 @@ jobs: - name: Check scripts run: yarn check-scripts + - name: Check tsconfigs + run: yarn check-tsconfigs + - name: Lint run: yarn lint diff --git a/apps/dotcom-worker/tsconfig.json b/apps/dotcom-worker/tsconfig.json index 050479e54..b4d7ff869 100644 --- a/apps/dotcom-worker/tsconfig.json +++ b/apps/dotcom-worker/tsconfig.json @@ -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" + } ] } diff --git a/apps/dotcom/tsconfig.json b/apps/dotcom/tsconfig.json index d8a56ee6a..26a728317 100644 --- a/apps/dotcom/tsconfig.json +++ b/apps/dotcom/tsconfig.json @@ -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" + } ] } diff --git a/apps/examples/tsconfig.json b/apps/examples/tsconfig.json index d6995c8e8..95d93668c 100644 --- a/apps/examples/tsconfig.json +++ b/apps/examples/tsconfig.json @@ -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" + } ] } diff --git a/apps/health-worker/tsconfig.json b/apps/health-worker/tsconfig.json index 983cb56b9..f1ea36313 100644 --- a/apps/health-worker/tsconfig.json +++ b/apps/health-worker/tsconfig.json @@ -7,5 +7,9 @@ "emitDeclarationOnly": false, "types": ["@cloudflare/workers-types", "@types/node"] }, - "references": [] + "references": [ + { + "path": "../../packages/utils" + } + ] } diff --git a/apps/huppy/tsconfig.json b/apps/huppy/tsconfig.json index 24c60cfee..97159edff 100644 --- a/apps/huppy/tsconfig.json +++ b/apps/huppy/tsconfig.json @@ -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" + } + ] } diff --git a/apps/vscode/editor/tsconfig.json b/apps/vscode/editor/tsconfig.json index 626f28365..97fb59a53 100644 --- a/apps/vscode/editor/tsconfig.json +++ b/apps/vscode/editor/tsconfig.json @@ -23,5 +23,12 @@ "experimentalDecorators": true }, "include": ["src", "../messages", "scripts"], - "references": [{ "path": "../../../packages/tldraw" }] + "references": [ + { + "path": "../../../packages/assets" + }, + { + "path": "../../../packages/tldraw" + } + ] } diff --git a/apps/vscode/extension/tsconfig.json b/apps/vscode/extension/tsconfig.json index 626f28365..03cee5538 100644 --- a/apps/vscode/extension/tsconfig.json +++ b/apps/vscode/extension/tsconfig.json @@ -23,5 +23,12 @@ "experimentalDecorators": true }, "include": ["src", "../messages", "scripts"], - "references": [{ "path": "../../../packages/tldraw" }] + "references": [ + { + "path": "../../../packages/editor" + }, + { + "path": "../../../packages/tldraw" + } + ] } diff --git a/package.json b/package.json index 0bc6f1f07..d22f7b27f 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/packages/assets/tsconfig.json b/packages/assets/tsconfig.json index ff0df6ad9..5faa6c098 100644 --- a/packages/assets/tsconfig.json +++ b/packages/assets/tsconfig.json @@ -14,5 +14,9 @@ "rootDir": "src", "resolveJsonModule": false }, - "references": [{ "path": "../utils" }] + "references": [ + { + "path": "../utils" + } + ] } diff --git a/packages/editor/tsconfig.json b/packages/editor/tsconfig.json index 7d89ced08..1ed85b35b 100644 --- a/packages/editor/tsconfig.json +++ b/packages/editor/tsconfig.json @@ -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" + } ] } diff --git a/packages/state/tsconfig.json b/packages/state/tsconfig.json index 113ca5c0c..662a89b2d 100644 --- a/packages/state/tsconfig.json +++ b/packages/state/tsconfig.json @@ -5,6 +5,5 @@ "compilerOptions": { "outDir": "./.tsbuild", "rootDir": "src" - }, - "references": [{ "path": "../utils" }] + } } diff --git a/packages/store/tsconfig.json b/packages/store/tsconfig.json index 8693c77b1..0cba2f808 100644 --- a/packages/store/tsconfig.json +++ b/packages/store/tsconfig.json @@ -6,5 +6,12 @@ "outDir": "./.tsbuild", "rootDir": "src" }, - "references": [{ "path": "../utils" }, { "path": "../state" }] + "references": [ + { + "path": "../state" + }, + { + "path": "../utils" + } + ] } diff --git a/packages/tldraw/tsconfig.json b/packages/tldraw/tsconfig.json index 789a3554f..9bdffb10f 100644 --- a/packages/tldraw/tsconfig.json +++ b/packages/tldraw/tsconfig.json @@ -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" + } + ] } diff --git a/packages/tlschema/tsconfig.json b/packages/tlschema/tsconfig.json index e1f073aa0..5cdf61082 100644 --- a/packages/tlschema/tsconfig.json +++ b/packages/tlschema/tsconfig.json @@ -6,5 +6,18 @@ "outDir": "./.tsbuild", "rootDir": "src" }, - "references": [{ "path": "../store" }, { "path": "../validate" }, { "path": "../utils" }] + "references": [ + { + "path": "../state" + }, + { + "path": "../store" + }, + { + "path": "../utils" + }, + { + "path": "../validate" + } + ] } diff --git a/packages/tlsync/tsconfig.json b/packages/tlsync/tsconfig.json index 436778805..c5263ddda 100644 --- a/packages/tlsync/tsconfig.json +++ b/packages/tlsync/tsconfig.json @@ -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" + } ] } diff --git a/packages/validate/tsconfig.json b/packages/validate/tsconfig.json index 113ca5c0c..be43fd20f 100644 --- a/packages/validate/tsconfig.json +++ b/packages/validate/tsconfig.json @@ -6,5 +6,9 @@ "outDir": "./.tsbuild", "rootDir": "src" }, - "references": [{ "path": "../utils" }] + "references": [ + { + "path": "../utils" + } + ] } diff --git a/scripts/check-tsconfigs.ts b/scripts/check-tsconfigs.ts new file mode 100644 index 000000000..cfaf823a3 --- /dev/null +++ b/scripts/check-tsconfigs.ts @@ -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 = 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([...(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'), +})