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 - name: Check scripts
run: yarn check-scripts run: yarn check-scripts
- name: Check tsconfigs
run: yarn check-tsconfigs
- name: Lint - name: Lint
run: yarn lint run: yarn lint

View file

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

View file

@ -25,8 +25,14 @@
"include": ["**/*.ts", "**/*.tsx"], "include": ["**/*.ts", "**/*.tsx"],
"exclude": ["node_modules", "_archive"], "exclude": ["node_modules", "_archive"],
"references": [ "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" "outDir": "./.tsbuild"
}, },
"references": [ "references": [
{ "path": "../../packages/tldraw" }, {
{ "path": "../../packages/utils" }, "path": "../../packages/assets"
{ "path": "../../packages/assets" }, },
{ "path": "../../packages/validate" } {
"path": "../../packages/tldraw"
}
] ]
} }

View file

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

View file

@ -19,5 +19,12 @@
}, },
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
"exclude": ["node_modules", "_archive"], "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 "experimentalDecorators": true
}, },
"include": ["src", "../messages", "scripts"], "include": ["src", "../messages", "scripts"],
"references": [{ "path": "../../../packages/tldraw" }] "references": [
{
"path": "../../../packages/assets"
},
{
"path": "../../../packages/tldraw"
}
]
} }

View file

@ -23,5 +23,12 @@
"experimentalDecorators": true "experimentalDecorators": true
}, },
"include": ["src", "../messages", "scripts"], "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}\"", "format": "prettier --write --cache \"**/*.{ts,tsx,js,jsx,json}\"",
"typecheck": "yarn refresh-assets && tsx scripts/typecheck.ts", "typecheck": "yarn refresh-assets && tsx scripts/typecheck.ts",
"check-scripts": "tsx scripts/check-scripts.ts", "check-scripts": "tsx scripts/check-scripts.ts",
"check-tsconfigs": "tsx scripts/check-tsconfigs.ts",
"api-check": "lazy api-check", "api-check": "lazy api-check",
"test-ci": "lazy test-ci", "test-ci": "lazy test-ci",
"test": "lazy test", "test": "lazy test",

View file

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

View file

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

View file

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

View file

@ -6,5 +6,12 @@
"outDir": "./.tsbuild", "outDir": "./.tsbuild",
"rootDir": "src" "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"], "exclude": ["node_modules", "dist", "**/*.css", ".tsbuild*", "./scripts/legacy-translations"],
"compilerOptions": { "compilerOptions": {
"outDir": "./.tsbuild", "outDir": "./.tsbuild",
// TODO: Enable noImplicitReturns
"noImplicitReturns": false, "noImplicitReturns": false,
"rootDir": "src" "rootDir": "src"
}, },
"references": [{ "path": "../editor" }] "references": [
{
"path": "../editor"
}
]
} }

View file

@ -6,5 +6,18 @@
"outDir": "./.tsbuild", "outDir": "./.tsbuild",
"rootDir": "src" "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" "rootDir": "src"
}, },
"references": [ "references": [
{ "path": "../tldraw" }, {
{ "path": "../tlschema" }, "path": "../state"
{ "path": "../state" }, },
{ "path": "../store" }, {
{ "path": "../utils" } "path": "../store"
},
{
"path": "../tldraw"
},
{
"path": "../tlschema"
},
{
"path": "../utils"
}
] ]
} }

View file

@ -6,5 +6,9 @@
"outDir": "./.tsbuild", "outDir": "./.tsbuild",
"rootDir": "src" "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'),
})