mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-17 01:56:16 -08:00
refactor(core): [CORE-21] generate compatible public and editor declarations
This commit is contained in:
1 parent
ee895a558f
commit
faaa3e8c2d
123 files changed
+31305
-1712
No files matched your search
@@ -0,0 +1,90 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import process from 'node:process'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { ESLint } from 'eslint'
|
||||
import ts from 'typescript'
|
||||
|
||||
const workspace = fileURLToPath(new URL('../', import.meta.url))
|
||||
const normalize = value => value.replaceAll('\\', '/')
|
||||
|
||||
export async function generateCoreDeclarations({ root = workspace, write = false } = {}) {
|
||||
const packageRoot = path.join(root, 'packages/artplayer')
|
||||
const sourceRoot = path.join(packageRoot, 'public')
|
||||
const outputRoot = path.join(packageRoot, 'types')
|
||||
const files = ts.sys.readDirectory(sourceRoot, ['.ts', '.mts', '.cts']).sort()
|
||||
assert(files.length > 0, 'No public TypeScript sources found')
|
||||
assert(files.every(file => !/\.d\.[cm]?ts$/.test(file)), 'Public sources must be TypeScript, not copied declaration artifacts')
|
||||
const options = {
|
||||
strict: true,
|
||||
skipLibCheck: false,
|
||||
types: [],
|
||||
target: ts.ScriptTarget.ES2020,
|
||||
lib: ['lib.es2020.d.ts', 'lib.dom.d.ts', 'lib.dom.iterable.d.ts'],
|
||||
module: ts.ModuleKind.NodeNext,
|
||||
moduleResolution: ts.ModuleResolutionKind.NodeNext,
|
||||
declaration: true,
|
||||
emitDeclarationOnly: true,
|
||||
noEmitOnError: true,
|
||||
rootDir: sourceRoot,
|
||||
outDir: outputRoot,
|
||||
newLine: ts.NewLineKind.LineFeed,
|
||||
}
|
||||
const outputs = new Map()
|
||||
const program = ts.createProgram(files, options)
|
||||
const formatHost = { getCurrentDirectory: () => root, getCanonicalFileName: name => name, getNewLine: () => '\n' }
|
||||
const errors = ts.getPreEmitDiagnostics(program)
|
||||
assert.equal(errors.length, 0, ts.formatDiagnosticsWithColorAndContext(errors, formatHost))
|
||||
for (const source of program.getSourceFiles()) {
|
||||
if (program.isSourceFileDefaultLibrary(source))
|
||||
continue
|
||||
assert(files.includes(source.fileName), `Public declarations depend on a non-public source: ${normalize(path.relative(root, source.fileName))}`)
|
||||
}
|
||||
const emitted = program.emit(undefined, (file, text) => {
|
||||
const relative = normalize(path.relative(outputRoot, file))
|
||||
assert(!relative.startsWith('../') && !path.isAbsolute(relative) && /\.d\.[cm]?ts$/.test(relative), `Unexpected declaration output: ${file}`)
|
||||
outputs.set(relative, text)
|
||||
})
|
||||
assert.equal(emitted.emitSkipped, false, ts.formatDiagnosticsWithColorAndContext(emitted.diagnostics, formatHost))
|
||||
assert.equal(outputs.size, files.length, 'Each public source must emit one declaration')
|
||||
|
||||
const eslint = new ESLint({ cwd: workspace, fix: true, fixTypes: ['layout'] })
|
||||
const generated = new Map()
|
||||
for (const [relative, content] of outputs) {
|
||||
const source = `public/${relative.replace('.d.', '.')}`
|
||||
// Declaration emit omits line comments. This bridge intentionally merges
|
||||
// the constructor value, instance alias and CJS named-type namespace.
|
||||
const bridge = ['artplayer.d.cts', 'runtime.d.cts', 'runtime.d.ts'].includes(relative) ? '/* eslint-disable ts/no-redeclare -- Constructor, instance and named types share the export. */\n' : ''
|
||||
const code = `// Generated from ${source} by yarn build:types. Do not edit.\n${bridge}${content}`
|
||||
const [result] = await eslint.lintText(code, { filePath: path.join(workspace, 'packages/artplayer/types', relative) })
|
||||
if (result.errorCount) {
|
||||
const formatter = await eslint.loadFormatter('stylish')
|
||||
throw new Error(formatter.format([result]))
|
||||
}
|
||||
generated.set(relative, result.output || code)
|
||||
}
|
||||
const existing = ts.sys.readDirectory(outputRoot, ['.d.ts', '.d.mts', '.d.cts'])
|
||||
for (const file of existing)
|
||||
assert(generated.has(normalize(path.relative(outputRoot, file))), `Declaration has no public source: ${file}`)
|
||||
const changed = []
|
||||
for (const [relative, text] of generated) {
|
||||
const file = path.join(outputRoot, relative)
|
||||
const actual = fs.existsSync(file) ? fs.readFileSync(file, 'utf8').replaceAll('\r\n', '\n') : null
|
||||
if (actual === text)
|
||||
continue
|
||||
changed.push(relative)
|
||||
if (write) {
|
||||
fs.mkdirSync(path.dirname(file), { recursive: true })
|
||||
fs.writeFileSync(file, text)
|
||||
}
|
||||
}
|
||||
assert(write || changed.length === 0, `Generated core declarations are stale: ${changed.join(', ')}. Run yarn build:types.`)
|
||||
return { files: [...generated.keys()].sort(), changed }
|
||||
}
|
||||
|
||||
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
||||
assert(process.argv.length === 3 && ['--write', '--check'].includes(process.argv[2]), 'Use build-types.mjs --write or --check')
|
||||
const result = await generateCoreDeclarations({ write: process.argv[2] === '--write' })
|
||||
console.log(`Core declarations ${process.argv[2]}: ${result.files.length} files; ${result.changed.length} changed`)
|
||||
}
|
||||
Reference in new issue
Block a user