Repository navigation
Expand file tree
/
Copy pathverify-typecheck-coverage.ts
More file actions
87 lines (74 loc) · 4.2 KB
/
Copy pathverify-typecheck-coverage.ts
File metadata and controls
87 lines (74 loc) · 4.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
import { existsSync, readdirSync, readFileSync } from 'node:fs'
import { dirname, join, resolve } from 'node:path'
import process from 'node:process'
import { fileURLToPath } from 'node:url'
/**
* Guards the convention from AGENTS.md's "Development" section: *"every
* workspace package owns a `typecheck` script … add one to every new
* package so it can't silently skip type errors."*
*
* `pnpm typecheck` runs `turbo run typecheck`, which only runs the task in
* packages that **declare** it (`turbo.json`'s `typecheck` task fans out via
* `dependsOn: ["^typecheck"]`, but never invents the script). A workspace
* package with a `tsconfig.json` and no `typecheck` script is silently
* skipped instead of failing loud, so this script scans every workspace
* package for that gap and fails CI when it finds one that isn't a
* documented exception below.
*/
const rootDir = resolve(dirname(fileURLToPath(import.meta.url)), '..')
/**
* Workspace globs, mirrored from `pnpm-workspace.yaml`'s `packages:` list.
*/
const WORKSPACE_PATTERNS = ['packages/*', 'plugins/*', 'examples/*', 'starter', 'storybook', 'docs']
/**
* Packages with a `tsconfig.json` that intentionally don't have a
* `typecheck` script yet. Each entry is a `pnpm typecheck` blind spot, so
* keep this list short and remove an entry the moment its package gets a
* working script; the check below fails if an entry is stale (the package
* already has one).
*/
const EXCEPTIONS: Record<string, string> = {
'examples/custom-hub-next': 'packages/hub/src/node/host-terminals.ts types a child-process env as NodeJS.ProcessEnv, and Next.js\'s ambient types require a literal NODE_ENV on that interface once this app pulls hub into its program. See plans/README.md "Execution notes" for plan 001.',
'examples/hub-next': 'Same Next.js + hub NODE_ENV ambient conflict as examples/custom-hub-next; the minimal Next host pulls hub into its program too.',
'docs': 'Nuxt app extending the comark-docs layer: tsconfig.json only holds project references into generated `.nuxt/tsconfig.*.json`, which exist only after `nuxt prepare` resolves the layer, so type-checking it would drag a full Nuxt prepare into the Turbo graph.',
}
function expandPattern(pattern: string): string[] {
if (!pattern.endsWith('/*'))
return [pattern]
const base = pattern.slice(0, -2)
const baseDir = join(rootDir, base)
if (!existsSync(baseDir))
return []
return readdirSync(baseDir, { withFileTypes: true })
.filter(entry => entry.isDirectory())
.map(entry => `${base}/${entry.name}`)
}
function hasTypecheckScript(dir: string): boolean {
const pkgPath = join(rootDir, dir, 'package.json')
if (!existsSync(pkgPath))
return false
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'))
return Boolean(pkg.scripts?.typecheck)
}
const dirs = WORKSPACE_PATTERNS.flatMap(expandPattern)
const missing = dirs.filter((dir) => {
const pkgPath = join(rootDir, dir, 'package.json')
const tsconfigPath = join(rootDir, dir, 'tsconfig.json')
if (!existsSync(pkgPath) || !existsSync(tsconfigPath))
return false
return !hasTypecheckScript(dir) && !(dir in EXCEPTIONS)
})
const staleExceptions = Object.keys(EXCEPTIONS).filter(dir => hasTypecheckScript(dir))
if (missing.length > 0) {
console.error('The following workspace packages have a tsconfig.json but no `typecheck` script:\n')
for (const dir of missing) console.error(` - ${dir}`)
console.error('\nAdd `"typecheck": "tsc --noEmit"` to each package.json\'s scripts (see AGENTS.md), or, if it genuinely can\'t typecheck yet, add a documented exception to scripts/verify-typecheck-coverage.ts.')
}
if (staleExceptions.length > 0) {
console.error(`${missing.length > 0 ? '\n' : ''}The following exceptions in scripts/verify-typecheck-coverage.ts are stale; the package already has a \`typecheck\` script, so remove the entry:\n`)
for (const dir of staleExceptions) console.error(` - ${dir}`)
}
if (missing.length > 0 || staleExceptions.length > 0)
process.exit(1)
const covered = dirs.filter(dir => existsSync(join(rootDir, dir, 'tsconfig.json'))).length
console.log(`typecheck coverage OK: ${covered - Object.keys(EXCEPTIONS).length}/${covered} eligible packages covered, ${Object.keys(EXCEPTIONS).length} documented exception(s).`)