mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-08 19:06:15 -08:00
feat(auto-thumbnail): [PKG-AUTO-THUMB-08] add compatible runtime type entry
This commit is contained in:
1 parent
7be628a983
commit
c4f5dcf17f
27 files changed
+2005
-40
No files matched your search
@@ -0,0 +1,43 @@
|
||||
import fs from 'node:fs'
|
||||
|
||||
export const publicInvalid = [
|
||||
'thumbnail()',
|
||||
'thumbnail(undefined)',
|
||||
'thumbnail({ width: "80" })',
|
||||
'thumbnail({ height: 90 })',
|
||||
'const asynchronous: Promise<{name: "artplayerPluginAutoThumbnail"}> = result',
|
||||
'thumbnail.default({})',
|
||||
]
|
||||
|
||||
export const runtimeInvalid = [
|
||||
'runtime()',
|
||||
'runtime(undefined)',
|
||||
'runtime({ url: 1 })',
|
||||
'runtime({ width: "80" })',
|
||||
'runtime({ number: false })',
|
||||
'runtime({ scale: "1" })',
|
||||
'runtime({ height: "90" })',
|
||||
'runtime({ unknown: true })',
|
||||
'runtime({})()',
|
||||
'pending.name',
|
||||
'runtime.default({})',
|
||||
'const synchronous: Result = pending',
|
||||
'const wrong: Factory = (_option) => (_art) => ({name: "artplayerPluginAutoThumbnail"})',
|
||||
]
|
||||
|
||||
export function publicSource(mode, raw = false) {
|
||||
let source = fs.readFileSync(new URL('../../test/types/auto-thumbnail-public.ts', import.meta.url), 'utf8')
|
||||
if (mode === 'nodenext-esm' && !raw) {
|
||||
source = source.replace('import thumbnail from \'artplayer-plugin-auto-thumbnail\'', 'import thumbnailModule from \'artplayer-plugin-auto-thumbnail\'\nconst thumbnail = thumbnailModule.default')
|
||||
.replace('import legacy from \'artplayer-plugin-auto-thumbnail/legacy\'', 'import legacyModule from \'artplayer-plugin-auto-thumbnail/legacy\'\nconst legacy = legacyModule.default')
|
||||
}
|
||||
return source
|
||||
}
|
||||
|
||||
export function runtimeSource(mode) {
|
||||
const source = fs.readFileSync(new URL('../../test/types/auto-thumbnail-runtime.ts', import.meta.url), 'utf8')
|
||||
return mode.endsWith('-no-interop')
|
||||
? source.replace('import runtime from \'artplayer-plugin-auto-thumbnail/runtime\'', 'import runtime = require(\'artplayer-plugin-auto-thumbnail/runtime\')')
|
||||
.replace('import type Artplayer from \'artplayer\'', 'type Core = typeof import(\'artplayer\'); type Artplayer = Core extends { default: { prototype: infer I } } ? I : Core extends { prototype: infer I } ? I : never')
|
||||
: source
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import process from 'node:process'
|
||||
import ts from 'typescript'
|
||||
import compat from 'typescript-compat'
|
||||
import { checkFiles, packedFiles } from '../../scripts/package-check.mjs'
|
||||
import { consumerDirectory, removeConsumer, run, workspace, writeJson } from '../../scripts/package-consumer.mjs'
|
||||
import { publicInvalid, publicSource, runtimeInvalid, runtimeSource } from './auto-thumbnail-consumer.mjs'
|
||||
import { verifyAutoThumbnailContract } from './auto-thumbnail-contract.mjs'
|
||||
import { hash, readMember } from './releases.mjs'
|
||||
|
||||
const name = 'artplayer-plugin-auto-thumbnail'
|
||||
assert.equal(process.version.slice(1), fs.readFileSync(path.join(workspace, '.node-version'), 'utf8').trim())
|
||||
assert.equal(process.env.npm_config_user_agent?.split(' ')[0], 'yarn/1.22.22')
|
||||
const yarn = process.env.npm_execpath
|
||||
assert(yarn && fs.existsSync(yarn))
|
||||
const output = fs.mkdtempSync(path.join(workspace, 'refactor/.cache/auto-thumbnail-package-types-'))
|
||||
console.log(`Auto Thumbnail installed consumer evidence: ${output}`)
|
||||
const contract = await verifyAutoThumbnailContract()
|
||||
const packages = []
|
||||
for (const packageName of ['artplayer', name]) {
|
||||
const archive = path.join(output, `${packageName}.tgz`)
|
||||
fs.writeFileSync(path.join(output, `${packageName}-pack.log`), run([yarn, 'pack', '--filename', archive], path.join(workspace, 'packages', packageName)))
|
||||
const manifest = JSON.parse(readMember(archive, 'package/package.json'))
|
||||
const files = packedFiles(archive)
|
||||
checkFiles(manifest, files, packageName === name ? Object.keys(contract.baseline.release.files) : [])
|
||||
packages.push({ name: packageName, manifest, archive, sha256: hash(fs.readFileSync(archive)), files: Object.fromEntries(files.map(member => [member, hash(readMember(archive, member))])) })
|
||||
}
|
||||
const [core, candidate] = packages
|
||||
const declaration = `package/types/${name}.d.ts`
|
||||
const normalize = value => value.toString().replaceAll('\r\n', '\n')
|
||||
assert.equal(normalize(readMember(candidate.archive, declaration)), normalize(readMember(contract.archives.get('1.1.0'), declaration)))
|
||||
const matrix = []
|
||||
function report() {
|
||||
return writeJson(path.join(output, 'report.json'), {
|
||||
task: 'PKG-AUTO-THUMB-08',
|
||||
node: process.version,
|
||||
packages,
|
||||
matrix,
|
||||
scope: 'Actual npm 1.1.0 and packed candidate installed outside the workspace. Root historical type shape and conditional runtime entry are checked with exact negative lines. Registration and module identities use installed artifacts; no native extraction or device acceptance is implied.',
|
||||
})
|
||||
}
|
||||
|
||||
for (const plugin of [{ ...contract.baseline.release, archive: contract.archives.get('1.1.0'), label: 'published-1.1.0' }, { ...candidate, label: 'candidate' }]) {
|
||||
const consumer = consumerDirectory()
|
||||
try {
|
||||
writeJson(path.join(consumer, 'package.json'), { name: 'auto-thumbnail-isolated-consumer', private: true, dependencies: Object.fromEntries([core, plugin].map(pkg => [pkg.name, `file:${pkg.archive.replaceAll('\\', '/')}`])) })
|
||||
fs.copyFileSync(path.join(workspace, 'yarn.lock'), path.join(consumer, 'yarn.lock'))
|
||||
fs.writeFileSync(path.join(output, `${plugin.label}-install.log`), run([yarn, 'install', '--offline', '--ignore-scripts', '--non-interactive'], consumer))
|
||||
const lock = fs.readFileSync(path.join(consumer, 'yarn.lock'))
|
||||
fs.writeFileSync(path.join(output, `${plugin.label}-frozen.log`), run([yarn, 'install', '--offline', '--frozen-lockfile', '--force', '--ignore-scripts', '--non-interactive'], consumer))
|
||||
assert.deepEqual(fs.readFileSync(path.join(consumer, 'yarn.lock')), lock)
|
||||
for (const pkg of [core, plugin]) {
|
||||
for (const member of packedFiles(pkg.archive)) {
|
||||
assert.deepEqual(fs.readFileSync(path.join(consumer, 'node_modules', pkg.name, member.replace(/^package\//, ''))), readMember(pkg.archive, member), member)
|
||||
}
|
||||
}
|
||||
fs.writeFileSync(path.join(output, `${plugin.label}-yarn.lock`), lock)
|
||||
for (const [compiler, mode] of [[ts, 'node10-commonjs'], [ts, 'nodenext-cjs'], [ts, 'nodenext-esm'], [ts, 'bundler-esm'], [compat, 'node10-commonjs']]) {
|
||||
const next = mode.startsWith('nodenext')
|
||||
const filename = path.join(consumer, `consumer.${next ? mode.endsWith('-cjs') ? 'cts' : 'mts' : 'ts'}`)
|
||||
const options = {
|
||||
strict: true,
|
||||
noEmit: true,
|
||||
skipLibCheck: false,
|
||||
types: [],
|
||||
esModuleInterop: true,
|
||||
target: compiler.ScriptTarget.ES2020,
|
||||
lib: ['lib.es2020.d.ts', 'lib.dom.d.ts'],
|
||||
module: next ? compiler.ModuleKind.NodeNext : mode === 'bundler-esm' ? compiler.ModuleKind.ESNext : compiler.ModuleKind.CommonJS,
|
||||
moduleResolution: next ? compiler.ModuleResolutionKind.NodeNext : mode === 'bundler-esm' ? compiler.ModuleResolutionKind.Bundler : compiler.ModuleResolutionKind.NodeJs,
|
||||
}
|
||||
let declarations = []
|
||||
const compile = (source, interop = true) => {
|
||||
fs.writeFileSync(filename, source)
|
||||
const program = compiler.createProgram([filename], { ...options, esModuleInterop: interop })
|
||||
for (const file of program.getSourceFiles()) {
|
||||
const actual = fs.realpathSync(file.fileName)
|
||||
assert(actual.startsWith(fs.realpathSync(consumer) + path.sep) || (program.isSourceFileDefaultLibrary(file) && path.dirname(actual) === fs.realpathSync(path.dirname(compiler.sys.getExecutingFilePath()))), `Type escaped consumer: ${actual}`)
|
||||
}
|
||||
declarations = program.getSourceFiles().filter(file => !program.isSourceFileDefaultLibrary(file)).map(file => path.relative(consumer, file.fileName).replaceAll('\\', '/')).sort()
|
||||
return compiler.getPreEmitDiagnostics(program).map(item => ({
|
||||
file: item.file ? path.relative(consumer, item.file.fileName).replaceAll('\\', '/') : null,
|
||||
code: item.code,
|
||||
line: item.file && item.start !== undefined ? item.file.getLineAndCharacterOfPosition(item.start).line + 1 : null,
|
||||
message: compiler.flattenDiagnosticMessageText(item.messageText, '\n'),
|
||||
}))
|
||||
}
|
||||
const entry = { plugin: plugin.label, compiler: compiler.version, mode }
|
||||
matrix.push(entry)
|
||||
let source = publicSource(mode)
|
||||
entry.public = compile(source)
|
||||
if (plugin.label === 'published-1.1.0' && mode === 'node10-commonjs') {
|
||||
const lines = source.split('\n')
|
||||
assert.deepEqual(entry.public.map(item => ({ code: item.code, line: item.line })), [
|
||||
{ code: 2307, line: lines.findIndex(line => line.includes('from \'artplayer-plugin-auto-thumbnail/legacy\'')) + 1 },
|
||||
...(compiler === compat ? [{ code: 2344, line: lines.findIndex(line => line.startsWith('type ExactLegacy')) + 1 }] : []),
|
||||
], 'Preserve the historical Node10 legacy subpath failure')
|
||||
source = source.replace('from \'artplayer-plugin-auto-thumbnail/legacy\'', 'from \'artplayer-plugin-auto-thumbnail\'')
|
||||
entry.rootOnly = compile(source)
|
||||
assert.deepEqual(entry.rootOnly, [])
|
||||
}
|
||||
else {
|
||||
assert.deepEqual(entry.public, [], `${plugin.label} ${compiler.version} ${mode}`)
|
||||
}
|
||||
entry.publicInvalid = compile(`${source}\n${publicInvalid.join('\n')}`)
|
||||
assert.deepEqual(entry.publicInvalid.map(item => item.line), publicInvalid.map((_, index) => source.split('\n').length + 1 + index))
|
||||
if (plugin.label === 'candidate') {
|
||||
const runtime = runtimeSource(mode)
|
||||
entry.runtime = compile(runtime)
|
||||
entry.runtimeDeclarations = declarations
|
||||
assert.deepEqual(entry.runtime, [], `${compiler.version} ${mode}`)
|
||||
assert(declarations.includes(`node_modules/${name}/types/runtime.${next ? mode.endsWith('-cjs') ? 'd.cts' : 'd.mts' : mode === 'bundler-esm' ? 'd.mts' : 'd.ts'}`))
|
||||
entry.runtimeInvalid = compile(`${runtime}\n${runtimeInvalid.join('\n')}`)
|
||||
assert.deepEqual(entry.runtimeInvalid.map(item => item.line), runtimeInvalid.map((_, index) => runtime.split('\n').length + 1 + index))
|
||||
if (mode === 'node10-commonjs' || mode === 'nodenext-cjs') {
|
||||
entry.noInterop = compile(runtimeSource(`${mode}-no-interop`), false)
|
||||
assert.deepEqual(entry.noInterop, [])
|
||||
}
|
||||
}
|
||||
report()
|
||||
}
|
||||
const probe = path.join(consumer, 'runtime.cjs')
|
||||
fs.writeFileSync(probe, `const assert = require('node:assert/strict');
|
||||
(async () => {
|
||||
const names = ${JSON.stringify([name, `${name}/legacy`, ...(plugin.label === 'candidate' ? [`${name}/runtime`] : [])])};
|
||||
const commonjs = names.map(name => require(name));
|
||||
const modules = await Promise.all(names.map(name => import(name)));
|
||||
for (const factory of [...commonjs, ...modules.map(module => module.default)]) {
|
||||
assert.equal(typeof factory, 'function'); assert.equal(factory.default, undefined);
|
||||
const callbacks = new Map();
|
||||
const art = { option: {url: '/never-loaded.mp4'}, on(name, callback) { callbacks.set(name, callback); }, off(name, callback) { if (callbacks.get(name) === callback) callbacks.delete(name); } };
|
||||
const pending = factory({})(art);
|
||||
assert(pending instanceof Promise); assert.equal(pending.name, undefined);
|
||||
assert.deepEqual(await pending, {name: 'artplayerPluginAutoThumbnail'});
|
||||
assert(callbacks.has('video:loadedmetadata'));
|
||||
${plugin.label === 'candidate' ? 'callbacks.get(\'destroy\')(); assert.equal(callbacks.size, 0);' : ''}
|
||||
}
|
||||
${plugin.label === 'candidate' ? 'assert.equal(commonjs[2], commonjs[0]); assert.equal(modules[2].default, modules[0].default);' : ''}
|
||||
console.log('Installed root/legacy/runtime module shapes and Promise registration verified; no native extraction.');
|
||||
})().catch(error => { console.error(error); process.exitCode = 1; });`)
|
||||
fs.writeFileSync(path.join(output, `${plugin.label}-runtime.log`), run([probe], consumer))
|
||||
}
|
||||
finally {
|
||||
report()
|
||||
removeConsumer(consumer)
|
||||
}
|
||||
}
|
||||
console.log(`Passed ${matrix.length} installed compiler profiles; ${output}`)
|
||||
@@ -0,0 +1,63 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import fs from 'node:fs'
|
||||
// eslint-disable-next-line test/no-import-node-test -- Repository compatibility runner.
|
||||
import test from 'node:test'
|
||||
import { ESLint } from 'eslint'
|
||||
import ts from 'typescript'
|
||||
import compat from 'typescript-compat'
|
||||
import { checkPluginEditorDeclaration, generatePluginEditorDeclaration } from '../../scripts/plugin-editor-types.mjs'
|
||||
import { checkConsumer } from '../../scripts/typecheck.mjs'
|
||||
import { publicInvalid, publicSource, runtimeInvalid, runtimeSource } from './auto-thumbnail-consumer.mjs'
|
||||
import { verifyAutoThumbnailContract } from './auto-thumbnail-contract.mjs'
|
||||
import { readMember } from './releases.mjs'
|
||||
|
||||
const modes = [[ts, 'node10-commonjs'], [ts, 'nodenext-cjs'], [ts, 'nodenext-esm'], [ts, 'bundler-esm'], [compat, 'node10-commonjs']]
|
||||
|
||||
test('Auto Thumbnail preserves actual npm 1.1.0 root bytes and historical factory extraction', async () => {
|
||||
const contract = await verifyAutoThumbnailContract()
|
||||
const current = fs.readFileSync('packages/artplayer-plugin-auto-thumbnail/types/artplayer-plugin-auto-thumbnail.d.ts', 'utf8')
|
||||
const previous = readMember(contract.archives.get('1.1.0'), 'package/types/artplayer-plugin-auto-thumbnail.d.ts').toString()
|
||||
assert.equal(current.replaceAll('\r\n', '\n'), previous.replaceAll('\r\n', '\n'))
|
||||
for (const [compiler, mode] of modes) {
|
||||
const source = publicSource(mode)
|
||||
assert.deepEqual(checkConsumer(compiler, mode, source), [], `${compiler.version} ${mode}`)
|
||||
const invalid = checkConsumer(compiler, mode, `${source}\n${publicInvalid.join('\n')}`)
|
||||
assert.deepEqual(invalid.map(item => item.line), publicInvalid.map((_, index) => source.split('\n').length + 1 + index))
|
||||
}
|
||||
})
|
||||
|
||||
test('Auto Thumbnail runtime types expose only the real asynchronous factory and options', () => {
|
||||
for (const [compiler, mode] of modes) {
|
||||
const source = runtimeSource(mode)
|
||||
assert.deepEqual(checkConsumer(compiler, mode, source), [], `${compiler.version} ${mode}`)
|
||||
const invalid = checkConsumer(compiler, mode, `${source}\n${runtimeInvalid.join('\n')}`)
|
||||
assert.deepEqual(invalid.map(item => item.line), runtimeInvalid.map((_, index) => source.split('\n').length + 1 + index))
|
||||
}
|
||||
assert.deepEqual(checkConsumer(ts, 'nodenext-cjs', `import runtime = require('artplayer-plugin-auto-thumbnail/runtime');
|
||||
import type Artplayer from 'artplayer'; declare const art: Artplayer;
|
||||
const option: runtime.Option = {}; const result: Promise<runtime.Result> = runtime(option)(art);
|
||||
const factory: runtime.Factory = runtime; void [result, factory];`), [])
|
||||
})
|
||||
|
||||
test('Auto Thumbnail generated editor globals replace conflicting export assignments and retain old calls', async () => {
|
||||
const source = fs.readFileSync('packages/artplayer-plugin-auto-thumbnail/types/artplayer-plugin-auto-thumbnail.d.ts', 'utf8')
|
||||
const generated = generatePluginEditorDeclaration(source, 'artplayerPluginAutoThumbnail')
|
||||
const file = 'docs/assets/ts/artplayer-plugin-auto-thumbnail.d.ts'
|
||||
const [formatted] = await new ESLint({ fix: true, fixTypes: ['layout'] }).lintText(generated, { filePath: file })
|
||||
assert.equal(formatted.errorCount, 0)
|
||||
assert.equal(fs.readFileSync(file, 'utf8').replaceAll('\r\n', '\n'), formatted.output || generated)
|
||||
const core = fs.readFileSync('docs/assets/ts/artplayer.d.ts', 'utf8')
|
||||
const consumer = `declare const art: Artplayer;
|
||||
type Option = Parameters<typeof artplayerPluginAutoThumbnail>[0];
|
||||
type Result = ReturnType<ReturnType<typeof artplayerPluginAutoThumbnail>>;
|
||||
const option: Option = { width: 80, number: 12 };
|
||||
const result: Result = artplayerPluginAutoThumbnail(option)(art);
|
||||
const replacement: typeof artplayerPluginAutoThumbnail = _option => _art => result;`
|
||||
for (const compiler of [ts, compat]) {
|
||||
assert.deepEqual(checkPluginEditorDeclaration(generated, core, consumer, compiler), [])
|
||||
const invalid = checkPluginEditorDeclaration(generated, core, `${consumer}\nartplayerPluginAutoThumbnail();\nconst bad: Promise<Result> = result;`, compiler)
|
||||
assert.deepEqual(invalid.map(item => item.code), [2554, 2739])
|
||||
const previous = `${source.replace(/^import.*$/gim, '')}\nexport = artplayerPluginAutoThumbnail;\nexport as namespace artplayerPluginAutoThumbnail;\n`
|
||||
assert(checkPluginEditorDeclaration(previous, core, '', compiler).some(item => item.code === 2309))
|
||||
}
|
||||
})
|
||||
Reference in new issue
Block a user