/
checkBuiltTypes.ts
100 lines (88 loc) · 2.49 KB
/
checkBuiltTypes.ts
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
88
89
90
91
92
93
94
95
96
97
98
99
100
/**
* Checks whether the built files depend on devDependencies types.
* We shouldn't depend on them.
*/
import { dirname, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
import { readFileSync } from 'node:fs'
import colors from 'picocolors'
import type { ParseResult } from '@babel/parser'
import type { File, SourceLocation } from '@babel/types'
import { parse } from '@babel/parser'
import { walkDir } from './util'
const dir = dirname(fileURLToPath(import.meta.url))
const distDir = resolve(dir, '../dist')
const pkgJson = JSON.parse(
readFileSync(resolve(dir, '../package.json'), 'utf-8'),
)
const deps = new Set(Object.keys(pkgJson.dependencies))
type SpecifierError = {
loc: SourceLocation | null | undefined
value: string
file: string
}
const errors: SpecifierError[] = []
walkDir(distDir, (file) => {
if (!file.endsWith('.d.ts')) return
const specifiers = collectImportSpecifiers(file)
const notAllowedSpecifiers = specifiers.filter(
({ value }) =>
!(
value.startsWith('./') ||
value.startsWith('../') ||
value.startsWith('node:') ||
deps.has(value)
),
)
errors.push(...notAllowedSpecifiers)
})
if (errors.length <= 0) {
console.log(colors.green(colors.bold(`passed built types check`)))
} else {
console.log(colors.red(colors.bold(`failed built types check`)))
console.log()
errors.forEach((error) => {
const pos = error.loc
? `${colors.yellow(error.loc.start.line)}:${colors.yellow(
error.loc.start.column,
)}`
: ''
console.log(
`${colors.cyan(error.file)}:${pos} - importing ${colors.bold(
JSON.stringify(error.value),
)} is not allowed in built files`,
)
})
console.log()
}
function collectImportSpecifiers(file: string) {
const content = readFileSync(file, 'utf-8')
let ast: ParseResult<File>
try {
ast = parse(content, {
sourceType: 'module',
plugins: ['typescript', 'classProperties'],
})
} catch (e) {
console.log(colors.red(`failed to parse ${file}`))
throw e
}
const result: SpecifierError[] = []
for (const statement of ast.program.body) {
if (
statement.type === 'ImportDeclaration' ||
statement.type === 'ExportNamedDeclaration' ||
statement.type === 'ExportAllDeclaration'
) {
const source = statement.source
if (source?.value) {
result.push({
loc: source.loc,
value: source.value,
file,
})
}
}
}
return result
}