mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-12 06:26:16 -08:00
fix(types): [PKG-FACTORY-01] preserve published factory compatibility
This commit is contained in:
1 parent
1117107032
commit
c4c7491e16
35 files changed
+670
-229
No files matched your search
@@ -50,8 +50,9 @@ async function main() {
|
||||
const runtimeFile = path.join(consumer, 'exports.cjs')
|
||||
fs.writeFileSync(runtimeFile, plugin.label === 'candidate'
|
||||
? `const assert = require('node:assert/strict');
|
||||
for (const name of ['artplayer-plugin-ambilight', 'artplayer-plugin-ambilight/legacy']) { const factory = require(name); assert.equal(typeof factory, 'function'); assert.equal(factory.default, factory); assert.equal(typeof factory(), 'function'); }
|
||||
import('artplayer-plugin-ambilight').then(module => { assert.deepEqual(Object.keys(module), ['default']); assert.equal(module.default.default, module.default); }).catch(error => { console.error(error); process.exitCode = 1; });`
|
||||
for (const name of ['artplayer-plugin-ambilight', 'artplayer-plugin-ambilight/legacy', 'artplayer-plugin-ambilight/runtime']) { const factory = require(name); assert.equal(typeof factory, 'function'); assert.equal(factory.default, factory); assert.equal(typeof factory(), 'function'); }
|
||||
assert.equal(require('artplayer-plugin-ambilight/runtime'), require('artplayer-plugin-ambilight'));
|
||||
Promise.all(['artplayer-plugin-ambilight', 'artplayer-plugin-ambilight/runtime', 'artplayer-plugin-ambilight/legacy'].map(name => import(name))).then(([root, runtime, legacy]) => { assert.deepEqual(Object.keys(root), ['default']); assert.deepEqual(Object.keys(runtime), ['default']); assert.equal(runtime.default, root.default); assert.equal(root.default.default, root.default); assert.equal(legacy.default, require('artplayer-plugin-ambilight/legacy')); }).catch(error => { console.error(error); process.exitCode = 1; });`
|
||||
: `const assert = require('node:assert/strict'); const value = require('artplayer-plugin-ambilight'); assert.equal(typeof value, '${plugin.version === '1.0.0' ? 'object' : 'function'}'); assert.equal(typeof (value.default || value)({}), 'function');`)
|
||||
fs.writeFileSync(path.join(output, `${plugin.label}-runtime.log`), run([runtimeFile], consumer))
|
||||
const modes = [[ts, 'node10-commonjs'], [ts, 'nodenext-cjs'], [ts, 'nodenext-esm'], [ts, 'bundler-esm'], [compat, 'node10-commonjs']]
|
||||
@@ -60,14 +61,24 @@ import('artplayer-plugin-ambilight').then(module => { assert.deepEqual(Object.ke
|
||||
for (const [compiler, mode] of modes) {
|
||||
const next = mode.startsWith('nodenext')
|
||||
const extension = next ? mode.endsWith('-cjs') ? 'cts' : 'mts' : 'ts'
|
||||
const source = fs.readFileSync(path.join(workspace, plugin.label === 'candidate' ? 'test/types/ambilight.ts' : 'refactor/fixtures/consumers/ambilight-published.ts'), 'utf8')
|
||||
let source = fs.readFileSync(path.join(workspace, plugin.label === 'candidate' ? 'test/types/ambilight.ts' : 'refactor/fixtures/consumers/ambilight-published.ts'), 'utf8')
|
||||
if (plugin.label === 'candidate' && mode === 'nodenext-esm') {
|
||||
source = source
|
||||
.replace('import ambilight from \'artplayer-plugin-ambilight\'', 'import ambilightModule from \'artplayer-plugin-ambilight\'\nconst ambilight = ambilightModule.default')
|
||||
.replace('import legacy from \'artplayer-plugin-ambilight/legacy\'', 'import legacyModule from \'artplayer-plugin-ambilight/legacy\'\nconst legacy = legacyModule.default')
|
||||
}
|
||||
if (mode.endsWith('-no-interop'))
|
||||
source = source.replace('import runtime from \'artplayer-plugin-ambilight/runtime\'', 'import runtime = require(\'artplayer-plugin-ambilight/runtime\')')
|
||||
const filename = path.join(consumer, `consumer.${extension}`)
|
||||
const historical = path.join(consumer, `historical.${extension}`)
|
||||
fs.copyFileSync(path.join(workspace, 'refactor/fixtures/consumers/ambilight-published.ts'), historical)
|
||||
const files = plugin.label === 'candidate' ? [filename, historical] : [filename]
|
||||
if (mode === 'nodenext-cjs' && plugin.label !== 'published-1.1.0') {
|
||||
const commonjs = path.join(consumer, 'commonjs.cts')
|
||||
fs.writeFileSync(commonjs, `import ambilight = require('artplayer-plugin-ambilight'); ambilight({ blur: '50px', opacity: 0.5, frequency: 10, zIndex: 9, duration: 0.3 }); ${plugin.label === 'candidate' ? 'ambilight.default();' : ''}`)
|
||||
const files = [filename]
|
||||
if (plugin.label === 'candidate' && mode !== 'bundler-esm' && mode !== 'nodenext-esm') {
|
||||
const commonjs = path.join(consumer, `commonjs.${extension}`)
|
||||
fs.writeFileSync(commonjs, `import ambilight = require('artplayer-plugin-ambilight'); import legacy = require('artplayer-plugin-ambilight/legacy'); import runtime = require('artplayer-plugin-ambilight/runtime');
|
||||
import type Artplayer from 'artplayer';
|
||||
const replacement: typeof ambilight.default = (_option) => (_art: Artplayer) => ({name: 'artplayerPluginAmbilight', start() {}, stop() {}});
|
||||
const legacyReplacement: typeof legacy.default = replacement;
|
||||
ambilight.default({}); legacy.default({}); runtime(); runtime.default();
|
||||
const option: runtime.Option = {}; runtime(option); void legacyReplacement;`)
|
||||
files.push(commonjs)
|
||||
}
|
||||
const options = { strict: true, noEmit: true, skipLibCheck: false, types: [], esModuleInterop: !mode.endsWith('-no-interop'), 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 }
|
||||
@@ -78,22 +89,43 @@ import('artplayer-plugin-ambilight').then(module => { assert.deepEqual(Object.ke
|
||||
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()))), `Types escaped installed consumer: ${actual}`)
|
||||
}
|
||||
return compiler.getPreEmitDiagnostics(program).map(item => ({ code: item.code, message: compiler.flattenDiagnosticMessageText(item.messageText, '\n') }))
|
||||
return compiler.getPreEmitDiagnostics(program).map(item => ({ code: item.code, line: item.file && item.start !== undefined ? item.file.getLineAndCharacterOfPosition(item.start).line + 1 : null, message: compiler.flattenDiagnosticMessageText(item.messageText, '\n').replaceAll(consumer.replaceAll('\\', '/'), '<consumer>') }))
|
||||
}
|
||||
const diagnostics = compile(source)
|
||||
const historicalFailure = plugin.label === 'published-1.1.0' && mode === 'nodenext-esm'
|
||||
assert.deepEqual(diagnostics.map(item => item.code), historicalFailure ? [2349, 2349, 2322, 2344] : [], `${plugin.label} ${compiler.version} ${mode}`)
|
||||
const invalid = plugin.label === 'candidate' ? compile(source.replaceAll(/\/\/ @ts-expect-error[^\n]*\n/g, '')) : []
|
||||
if (plugin.label === 'candidate')
|
||||
assert.equal(invalid.length, 10, 'Installed declarations must reject all invalid uses')
|
||||
matrix.push({ plugin: plugin.label, compiler: compiler.version, mode, historicalFailure, diagnostics, invalid })
|
||||
if (plugin.label === 'candidate') {
|
||||
assert.equal(invalid.length, 15, 'Installed declarations must reject all invalid uses')
|
||||
const expectedLines = []
|
||||
let line = 1
|
||||
for (const text of source.split('\n')) {
|
||||
if (text.startsWith('// @ts-expect-error'))
|
||||
expectedLines.push(line)
|
||||
else line++
|
||||
}
|
||||
assert.deepEqual(invalid.map(item => item.line).sort((a, b) => a - b), expectedLines, 'Each invalid statement must fail at its own line')
|
||||
}
|
||||
const published = plugin.label === 'candidate' ? compile(fs.readFileSync(path.join(workspace, 'refactor/fixtures/consumers/ambilight-published.ts'), 'utf8')) : diagnostics
|
||||
assert.deepEqual(published.map(item => item.code), (plugin.label === 'candidate' || plugin.label === 'published-1.1.0') && mode === 'nodenext-esm' ? [2349, 2349, 2322, 2344] : [], 'Preserve exact historical direct-consumer diagnostic codes')
|
||||
const namespace = plugin.label !== 'published-1.0.0' && mode === 'nodenext-esm'
|
||||
? compile(`import ambilight from 'artplayer-plugin-ambilight'; import type Artplayer from 'artplayer';
|
||||
const replacement: typeof ambilight.default = (_option) => (_art: Artplayer) => ({name: 'artplayerPluginAmbilight', start() {}, stop() {}});
|
||||
ambilight.default({}); void replacement;`)
|
||||
: []
|
||||
assert.deepEqual(namespace, [], 'Latest published NodeNext namespace remains valid')
|
||||
const olderRequire = mode !== 'bundler-esm' && mode !== 'nodenext-esm'
|
||||
? compile(`import ambilight = require('artplayer-plugin-ambilight'); ambilight({blur: '50px', opacity: 0.5, frequency: 10, zIndex: 9, duration: 0.3});`)
|
||||
: []
|
||||
assert.deepEqual(olderRequire.map(item => item.code), mode !== 'bundler-esm' && mode !== 'nodenext-esm' && plugin.label !== 'published-1.0.0' ? [2349] : [], 'Earlier export-assignment typing conflicts are retained and documented')
|
||||
matrix.push({ plugin: plugin.label, compiler: compiler.version, mode, historicalFailure, diagnostics, invalid, published, namespace, olderRequire })
|
||||
}
|
||||
}
|
||||
finally {
|
||||
removeConsumer(consumer)
|
||||
}
|
||||
}
|
||||
writeJson(path.join(output, 'report.json'), { suite: 'ambilight-isolated-package-types', introducedBy: 'PKG-AMBILIGHT-04', scope: 'Two actual published plugins and packed candidate installed outside workspace with packed candidate core; offline and frozen reinstall, exact bytes, compiler/default/import= contracts. Published 1.0.0 required option fields became optional in 1.1.0; candidate retains 1.1.0 extraction. This is not proxy, device or complete distribution acceptance.', packages, published: candidates.slice(0, 2).map(pkg => ({ label: pkg.label, archive: pkg.archive, sha256: pkg.sha256 })), matrix })
|
||||
writeJson(path.join(output, 'report.json'), { suite: 'ambilight-isolated-package-types', introducedBy: 'PKG-FACTORY-01', scope: 'Two actual published plugins and packed candidate installed outside workspace with packed candidate core; offline and frozen reinstall, exact bytes, full factory replacements and required root option; runtime optional calls and self alias. Published 1.1.0 NodeNext ESM namespace and four old direct-call diagnostics are retained. Published 1.0.0 export-assignment and required fields have documented migration. This is not proxy, device or complete distribution acceptance.', packages, published: candidates.slice(0, 2).map(pkg => ({ label: pkg.label, archive: pkg.archive, sha256: pkg.sha256 })), matrix })
|
||||
console.log(`Ambilight installed types passed: ${matrix.length} cases; ${output}`)
|
||||
}
|
||||
|
||||
|
||||
@@ -14,14 +14,23 @@ import { readMember } from './releases.mjs'
|
||||
|
||||
const modes = [[ts, 'node10-commonjs'], [ts, 'nodenext-cjs'], [ts, 'nodenext-esm'], [ts, 'bundler-esm'], [compat, 'node10-commonjs']]
|
||||
|
||||
test('Ambilight current types retain inference, optional runtime arguments and ten invalid-use rejections', () => {
|
||||
function publicSource(source, mode) {
|
||||
return mode === 'nodenext-esm'
|
||||
? source.replace('import ambilight from \'artplayer-plugin-ambilight\'', 'import ambilightModule from \'artplayer-plugin-ambilight\'\nconst ambilight = ambilightModule.default')
|
||||
.replace('import legacy from \'artplayer-plugin-ambilight/legacy\'', 'import legacyModule from \'artplayer-plugin-ambilight/legacy\'\nconst legacy = legacyModule.default')
|
||||
: source
|
||||
}
|
||||
|
||||
test('Ambilight retains published factory replacements and separates accurate optional runtime calls', () => {
|
||||
const source = fs.readFileSync('test/types/ambilight.ts', 'utf8')
|
||||
for (const [compiler, mode] of modes) {
|
||||
assert.deepEqual(checkConsumer(compiler, mode, source), [], `${compiler.version} ${mode}`)
|
||||
assert.equal(checkConsumer(compiler, mode, source.replaceAll(/\/\/ @ts-expect-error[^\n]*\n/g, '')).length, 10)
|
||||
const adapted = publicSource(source, mode)
|
||||
assert.deepEqual(checkConsumer(compiler, mode, adapted), [], `${compiler.version} ${mode}`)
|
||||
assert.equal(checkConsumer(compiler, mode, adapted.replaceAll(/\/\/ @ts-expect-error[^\n]*\n/g, '')).length, 15)
|
||||
}
|
||||
assert.deepEqual(checkConsumer(ts, 'nodenext-cjs', `import ambilight = require('artplayer-plugin-ambilight'); import legacy = require('artplayer-plugin-ambilight/legacy');
|
||||
const option: ambilight.Option = {}; ambilight(option); ambilight.default(option); legacy(); legacy.default(option);`), [])
|
||||
import runtime = require('artplayer-plugin-ambilight/runtime');
|
||||
const option: ambilight.Option = {}; ambilight.default(option); legacy.default(option); runtime(); runtime.default(option);`), [])
|
||||
})
|
||||
|
||||
test('Ambilight published calls compile against both exact historical declarations and candidate without new inference widening', async () => {
|
||||
@@ -53,7 +62,7 @@ test('Ambilight published calls compile against both exact historical declaratio
|
||||
}
|
||||
})
|
||||
|
||||
test('Ambilight editor globals are generated from the public namespace and retain negative checks', async () => {
|
||||
test('Ambilight editor globals retain the required root factory and negative checks', async () => {
|
||||
const source = fs.readFileSync('packages/artplayer-plugin-ambilight/types/artplayer-plugin-ambilight.d.ts', 'utf8')
|
||||
const generated = generatePluginEditorDeclaration(source, 'artplayerPluginAmbilight')
|
||||
const file = 'docs/assets/ts/artplayer-plugin-ambilight.d.ts'
|
||||
@@ -61,12 +70,17 @@ test('Ambilight editor globals are generated from the public namespace and retai
|
||||
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 = `const option: artplayerPluginAmbilight.Option = {}; artplayerPluginAmbilight(); artplayerPluginAmbilight.default(option);
|
||||
const consumer = `const option: artplayerPluginAmbilight.Option = {}; artplayerPluginAmbilight(option);
|
||||
const replacement: typeof artplayerPluginAmbilight = (_option) => (_art) => ({name: 'artplayerPluginAmbilight', start() {}, stop() {}});
|
||||
// @ts-expect-error Required root argument.
|
||||
artplayerPluginAmbilight();
|
||||
// @ts-expect-error Optional invocation belongs to the runtime module.
|
||||
artplayerPluginAmbilight.default(option);
|
||||
// @ts-expect-error Invalid blur.
|
||||
artplayerPluginAmbilight({ blur: 2 });`
|
||||
for (const compiler of [ts, compat]) {
|
||||
assert.deepEqual(checkPluginEditorDeclaration(generated, core, consumer, compiler), [])
|
||||
assert.equal(checkPluginEditorDeclaration(generated, core, consumer.replaceAll(/\/\/ @ts-expect-error[^\n]*\n/g, ''), compiler).length, 1)
|
||||
assert.equal(checkPluginEditorDeclaration(generated, core, consumer.replaceAll(/\/\/ @ts-expect-error[^\n]*\n/g, ''), compiler).length, 3)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ 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 ts from 'typescript'
|
||||
import compat from 'typescript-compat'
|
||||
import { checkFiles, packedFiles } from '../../scripts/package-check.mjs'
|
||||
@@ -9,7 +10,46 @@ import { consumerDirectory, removeConsumer, run, workspace, writeJson } from '..
|
||||
import { verifyCanvasContract } from './canvas-contract.mjs'
|
||||
import { hash, readMember } from './releases.mjs'
|
||||
|
||||
export function canvasConsumerSource(source, mode) {
|
||||
if (mode === 'nodenext-esm') {
|
||||
source = source.replace('import canvas from \'artplayer-proxy-canvas\'', 'import canvasModule from \'artplayer-proxy-canvas\'\nconst canvas = canvasModule.default')
|
||||
.replace('import legacy from \'artplayer-proxy-canvas/legacy\'', 'import legacyModule from \'artplayer-proxy-canvas/legacy\'\nconst legacy = legacyModule.default')
|
||||
.replace('namespace.default', 'namespace.default.default')
|
||||
}
|
||||
if (mode.endsWith('-no-interop'))
|
||||
source = source.replace('import runtime from \'artplayer-proxy-canvas/runtime\'', 'import runtime = require(\'artplayer-proxy-canvas/runtime\')')
|
||||
return source
|
||||
}
|
||||
|
||||
export const canvasNamespaceConsumer = `import type Artplayer from 'artplayer';
|
||||
import canvasModule from 'artplayer-proxy-canvas';
|
||||
import * as canvasNamespace from 'artplayer-proxy-canvas';
|
||||
import legacyModule from 'artplayer-proxy-canvas/legacy';
|
||||
type Callback = (ctx: CanvasRenderingContext2D, video: HTMLVideoElement) => void;
|
||||
type Factory = (option?: Callback) => (art: Artplayer) => HTMLCanvasElement;
|
||||
const replacement: Factory = (_option?: Callback) => (_art: Artplayer) => document.createElement('canvas');
|
||||
canvasModule.default(); canvasNamespace.default.default(); legacyModule.default();
|
||||
const fromOld: typeof canvasModule = { default: replacement };
|
||||
const toOld: { default: Factory } = canvasModule;
|
||||
const namespaceReplacement: typeof canvasNamespace.default = { default: replacement };
|
||||
const oldNamespace: { default: Factory } = canvasNamespace.default;
|
||||
const optional: Parameters<typeof canvasModule.default>[0] = undefined;
|
||||
const result: ReturnType<ReturnType<typeof canvasModule.default>> = document.createElement('canvas');
|
||||
void [fromOld, toOld, namespaceReplacement, oldNamespace, optional, result];`
|
||||
|
||||
export function checkInvalidCanvasStatements(source, diagnostics) {
|
||||
const expected = []
|
||||
let line = 1
|
||||
for (const text of source.split('\n')) {
|
||||
if (text.startsWith('// @ts-expect-error'))
|
||||
expected.push(line)
|
||||
else line++
|
||||
}
|
||||
assert.deepEqual(diagnostics.map(item => item.line).sort((a, b) => a - b), expected, 'Each invalid Canvas statement must fail at its own line')
|
||||
}
|
||||
|
||||
async function main() {
|
||||
assert.equal(process.version.slice(1), fs.readFileSync(path.join(workspace, '.node-version'), 'utf8').trim(), 'Use the repository Node version')
|
||||
assert.equal(process.env.npm_config_user_agent?.split(' ')[0], 'yarn/1.22.22', 'Use yarn test:canvas-types-package')
|
||||
const yarn = process.env.npm_execpath
|
||||
assert(yarn && fs.existsSync(yarn))
|
||||
@@ -50,8 +90,9 @@ async function main() {
|
||||
const runtimeFile = path.join(consumer, 'exports.cjs')
|
||||
fs.writeFileSync(runtimeFile, plugin.label === 'candidate'
|
||||
? `const assert = require('node:assert/strict');
|
||||
for (const name of ['artplayer-proxy-canvas', 'artplayer-proxy-canvas/legacy']) { const factory = require(name); assert.equal(typeof factory, 'function'); assert.equal(factory.default, factory); assert.equal(typeof factory(), 'function'); }
|
||||
import('artplayer-proxy-canvas').then(module => { assert.deepEqual(Object.keys(module), ['default']); assert.equal(module.default.default, module.default); }).catch(error => { console.error(error); process.exitCode = 1; });`
|
||||
for (const name of ['artplayer-proxy-canvas', 'artplayer-proxy-canvas/legacy', 'artplayer-proxy-canvas/runtime']) { const factory = require(name); assert.equal(typeof factory, 'function'); assert.equal(factory.default, factory); assert.equal(typeof factory(), 'function'); }
|
||||
assert.equal(require('artplayer-proxy-canvas/runtime'), require('artplayer-proxy-canvas'));
|
||||
Promise.all([import('artplayer-proxy-canvas'), import('artplayer-proxy-canvas/runtime')]).then(([module, runtime]) => { assert.deepEqual(Object.keys(module), ['default']); assert.equal(module.default.default, module.default); assert.equal(runtime.default, module.default); }).catch(error => { console.error(error); process.exitCode = 1; });`
|
||||
: `const assert = require('node:assert/strict'); const value = require('artplayer-proxy-canvas'); assert.equal(typeof value, '${plugin.version === '1.0.0' ? 'object' : 'function'}'); assert.equal(typeof (value.default || value)(() => {}), 'function');`)
|
||||
fs.writeFileSync(path.join(output, `${plugin.label}-runtime.log`), run([runtimeFile], consumer))
|
||||
const modes = [[ts, 'node10-commonjs'], [ts, 'nodenext-cjs'], [ts, 'nodenext-esm'], [ts, 'bundler-esm'], [compat, 'node10-commonjs']]
|
||||
@@ -60,14 +101,19 @@ import('artplayer-proxy-canvas').then(module => { assert.deepEqual(Object.keys(m
|
||||
for (const [compiler, mode] of modes) {
|
||||
const next = mode.startsWith('nodenext')
|
||||
const extension = next ? mode.endsWith('-cjs') ? 'cts' : 'mts' : 'ts'
|
||||
const source = fs.readFileSync(path.join(workspace, plugin.label === 'candidate' ? 'test/types/canvas.ts' : 'refactor/fixtures/consumers/canvas-published.ts'), 'utf8')
|
||||
const fixture = fs.readFileSync(path.join(workspace, plugin.label === 'candidate' ? 'test/types/canvas.ts' : 'refactor/fixtures/consumers/canvas-published.ts'), 'utf8')
|
||||
const source = plugin.label === 'candidate' ? canvasConsumerSource(fixture, mode) : fixture
|
||||
const filename = path.join(consumer, `consumer.${extension}`)
|
||||
const historical = path.join(consumer, `historical.${extension}`)
|
||||
fs.copyFileSync(path.join(workspace, 'refactor/fixtures/consumers/canvas-published.ts'), historical)
|
||||
fs.writeFileSync(historical, canvasConsumerSource(fs.readFileSync(path.join(workspace, 'refactor/fixtures/consumers/canvas-published.ts'), 'utf8'), mode))
|
||||
const files = plugin.label === 'candidate' ? [filename, historical] : [filename]
|
||||
if (mode === 'nodenext-cjs' && plugin.label !== 'published-1.1.0') {
|
||||
const commonjs = path.join(consumer, 'commonjs.cts')
|
||||
fs.writeFileSync(commonjs, `import canvas = require('artplayer-proxy-canvas'); canvas((ctx, video) => ctx.drawImage(video, 0, 0)); ${plugin.label === 'candidate' ? 'canvas.default();' : ''}`)
|
||||
if ((mode === 'nodenext-cjs' && plugin.label === 'published-1.0.0') || (plugin.label === 'candidate' && mode !== 'nodenext-esm' && mode !== 'bundler-esm')) {
|
||||
const commonjs = path.join(consumer, `commonjs.${extension}`)
|
||||
fs.writeFileSync(commonjs, plugin.label === 'candidate'
|
||||
? `import canvas = require('artplayer-proxy-canvas'); import legacy = require('artplayer-proxy-canvas/legacy'); import runtime = require('artplayer-proxy-canvas/runtime');
|
||||
canvas.default(); legacy.default(); runtime(); runtime.default();
|
||||
const callback: runtime.Option = (ctx, video) => ctx.drawImage(video, 0, 0); runtime(callback);`
|
||||
: `import canvas = require('artplayer-proxy-canvas'); canvas((ctx, video) => ctx.drawImage(video, 0, 0));`)
|
||||
files.push(commonjs)
|
||||
}
|
||||
const options = { strict: true, noEmit: true, skipLibCheck: false, types: [], esModuleInterop: !mode.endsWith('-no-interop'), 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 }
|
||||
@@ -78,7 +124,7 @@ import('artplayer-proxy-canvas').then(module => { assert.deepEqual(Object.keys(m
|
||||
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()))), `Types escaped installed consumer: ${actual}`)
|
||||
}
|
||||
return compiler.getPreEmitDiagnostics(program).map(item => ({ code: item.code, message: compiler.flattenDiagnosticMessageText(item.messageText, '\n') }))
|
||||
return compiler.getPreEmitDiagnostics(program).map(item => ({ file: item.file && path.relative(consumer, item.file.fileName), line: item.file && item.start !== undefined ? item.file.getLineAndCharacterOfPosition(item.start).line + 1 : null, code: item.code, message: compiler.flattenDiagnosticMessageText(item.messageText, '\n') }))
|
||||
}
|
||||
const diagnostics = compile(source)
|
||||
const historicalFailure = plugin.label === 'published-1.1.0' && mode === 'nodenext-esm'
|
||||
@@ -86,19 +132,28 @@ import('artplayer-proxy-canvas').then(module => { assert.deepEqual(Object.keys(m
|
||||
assert.deepEqual(diagnostics.map(item => item.code), historicalFailure ? [2349, 2349, 2322, 2344, 2344] : [], `${plugin.label} ${compiler.version} ${mode}`)
|
||||
const invalid = plugin.label === 'candidate' ? compile(source.replaceAll(/\/\/ @ts-expect-error[^\n]*\n/g, '')) : []
|
||||
if (plugin.label === 'candidate')
|
||||
assert.equal(invalid.length, 11, 'Installed declarations must reject all invalid uses')
|
||||
matrix.push({ plugin: plugin.label, compiler: compiler.version, mode, historicalFailure, diagnostics, invalid })
|
||||
checkInvalidCanvasStatements(source, invalid)
|
||||
const namespace = mode === 'nodenext-esm' && plugin.label !== 'published-1.0.0' ? compile(canvasNamespaceConsumer) : []
|
||||
assert.deepEqual(namespace, [], 'Latest published and candidate NodeNext ESM namespace shapes must accept plain module replacements')
|
||||
const exportAssignment = mode.includes('commonjs') || mode === 'nodenext-cjs'
|
||||
? compile(`import canvas = require('artplayer-proxy-canvas'); canvas((context: CanvasRenderingContext2D, video: HTMLVideoElement) => context.drawImage(video, 0, 0));`)
|
||||
: null
|
||||
if (exportAssignment)
|
||||
assert.deepEqual(exportAssignment.map(item => item.code), plugin.label === 'published-1.0.0' ? [] : [2349], 'Preserve the approved 1.0 export= versus latest default-export distinction')
|
||||
matrix.push({ plugin: plugin.label, compiler: compiler.version, mode, historicalFailure, diagnostics, invalid, namespace, exportAssignment })
|
||||
}
|
||||
}
|
||||
finally {
|
||||
removeConsumer(consumer)
|
||||
}
|
||||
}
|
||||
writeJson(path.join(output, 'report.json'), { suite: 'canvas-isolated-package-types', introducedBy: 'PKG-CANVAS-04', scope: 'Two actual published proxies and packed candidate installed outside workspace with packed candidate core; offline and frozen reinstall, exact bytes, compiler/default/import= contracts. Published 1.0.0 required callback became optional in 1.1.0; candidate retains optional Parameters and exact HTMLCanvasElement return inference. This is not native proxy media, device or complete distribution acceptance.', packages, published: candidates.slice(0, 2).map(pkg => ({ label: pkg.label, archive: pkg.archive, sha256: pkg.sha256 })), matrix })
|
||||
writeJson(path.join(output, 'report.json'), { suite: 'canvas-isolated-package-types', introducedBy: 'PKG-CANVAS-04', updatedBy: 'PKG-FACTORY-01', node: process.version, scope: 'Two actual published proxies and packed candidate installed outside workspace with packed candidate core; offline and frozen reinstall, exact bytes and root/legacy/runtime contracts. Root preserves published 1.1.0 pure factory, optional Parameters, exact HTMLCanvasElement result and NodeNext ESM namespace shape. Runtime adds accurate self identity with CommonJS/ESM and old Node10 resolution. Published 1.0.0 required callback/export= differences and 1.1.0 direct NodeNext ESM errors remain recorded. No native media, device or complete distribution acceptance.', packages, published: candidates.slice(0, 2).map(pkg => ({ label: pkg.label, archive: pkg.archive, sha256: pkg.sha256 })), matrix })
|
||||
console.log(`Canvas installed types passed: ${matrix.length} cases; ${output}`)
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error(error)
|
||||
process.exitCode = 1
|
||||
})
|
||||
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
||||
main().catch((error) => {
|
||||
console.error(error)
|
||||
process.exitCode = 1
|
||||
})
|
||||
}
|
||||
@@ -10,18 +10,21 @@ import compat from 'typescript-compat'
|
||||
import { checkPluginEditorDeclaration, generatePluginEditorDeclaration } from '../../scripts/plugin-editor-types.mjs'
|
||||
import { checkConsumer } from '../../scripts/typecheck.mjs'
|
||||
import { verifyCanvasContract } from './canvas-contract.mjs'
|
||||
import { canvasConsumerSource, canvasNamespaceConsumer, checkInvalidCanvasStatements } from './canvas-package-types.mjs'
|
||||
import { readMember } from './releases.mjs'
|
||||
|
||||
const modes = [[ts, 'node10-commonjs'], [ts, 'nodenext-cjs'], [ts, 'nodenext-esm'], [ts, 'bundler-esm'], [compat, 'node10-commonjs']]
|
||||
|
||||
test('Canvas current types preserve optional Parameters and exact Canvas result, while rejecting eleven invalid uses', () => {
|
||||
test('Canvas types preserve the latest plain factory and expose accurate runtime identity separately', () => {
|
||||
const source = fs.readFileSync('test/types/canvas.ts', 'utf8')
|
||||
for (const [compiler, mode] of modes) {
|
||||
assert.deepEqual(checkConsumer(compiler, mode, source), [], `${compiler.version} ${mode}`)
|
||||
assert.equal(checkConsumer(compiler, mode, source.replaceAll(/\/\/ @ts-expect-error[^\n]*\n/g, '')).length, 11)
|
||||
const consumer = canvasConsumerSource(source, mode)
|
||||
assert.deepEqual(checkConsumer(compiler, mode, consumer), [], `${compiler.version} ${mode}`)
|
||||
checkInvalidCanvasStatements(consumer, checkConsumer(compiler, mode, consumer.replaceAll(/\/\/ @ts-expect-error[^\n]*\n/g, '')))
|
||||
}
|
||||
assert.deepEqual(checkConsumer(ts, 'nodenext-cjs', `import canvas = require('artplayer-proxy-canvas'); import legacy = require('artplayer-proxy-canvas/legacy');
|
||||
const callback: canvas.Option = (ctx, video) => ctx.drawImage(video, 0, 0); canvas(callback); canvas.default(); legacy(); legacy.default(callback);`), [])
|
||||
assert.deepEqual(checkConsumer(ts, 'nodenext-cjs', `import canvas = require('artplayer-proxy-canvas'); import legacy = require('artplayer-proxy-canvas/legacy'); import runtime = require('artplayer-proxy-canvas/runtime');
|
||||
const callback: canvas.Option = (ctx, video) => ctx.drawImage(video, 0, 0); canvas.default(callback); legacy.default(); runtime(); runtime.default(callback);`), [])
|
||||
assert.deepEqual(checkConsumer(ts, 'nodenext-esm', canvasNamespaceConsumer), [])
|
||||
})
|
||||
|
||||
test('Canvas published declarations distinguish required 1.0.0 and optional 1.1.0 callbacks without changing Canvas return assignability', async () => {
|
||||
@@ -48,11 +51,17 @@ test('Canvas published declarations distinguish required 1.0.0 and optional 1.1.
|
||||
assert.deepEqual(historical(source), [])
|
||||
assert.deepEqual(historical(`${source}\n${optional}`), version === '1.0.0' ? [2322, 2554] : [])
|
||||
assert.deepEqual(checkConsumer(compiler, 'node10-commonjs', `${source}\n${optional}`), [])
|
||||
if (version === '1.1.0') {
|
||||
assert.deepEqual(historical(`${source}
|
||||
type Factory = (option?: (ctx: CanvasRenderingContext2D, video: HTMLVideoElement) => void) => (art: Artplayer) => HTMLCanvasElement;
|
||||
const replacement: Factory = () => () => document.createElement('canvas');
|
||||
const fromOld: typeof canvas = replacement; const toOld: Factory = canvas; void [fromOld, toOld];`), [])
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
test('Canvas editor globals are generated from public types with optional callback, media view and negative checking', async () => {
|
||||
test('Canvas editor globals preserve the plain root factory with an explicit accurate runtime type view', async () => {
|
||||
const source = fs.readFileSync('packages/artplayer-proxy-canvas/types/artplayer-proxy-canvas.d.ts', 'utf8')
|
||||
const generated = generatePluginEditorDeclaration(source, 'artplayerProxyCanvas')
|
||||
const file = 'docs/assets/ts/artplayer-proxy-canvas.d.ts'
|
||||
@@ -60,14 +69,19 @@ test('Canvas editor globals are generated from public types with optional callba
|
||||
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 = `artplayerProxyCanvas(); artplayerProxyCanvas.default((ctx, video) => ctx.drawImage(video, 0, 0));
|
||||
const consumer = `artplayerProxyCanvas(); artplayerProxyCanvas((ctx, video) => ctx.drawImage(video, 0, 0));
|
||||
const replacement: typeof artplayerProxyCanvas = (_option?: artplayerProxyCanvas.Option) => (_art: Artplayer) => document.createElement('canvas');
|
||||
const precise = artplayerProxyCanvas as artplayerProxyCanvas.RuntimeFactory; precise.default();
|
||||
const result: artplayerProxyCanvas.Result = document.createElement('canvas');
|
||||
const media = result as artplayerProxyCanvas.MediaCanvas; media.play();
|
||||
void replacement;
|
||||
// @ts-expect-error Root factory does not require or expose a self alias.
|
||||
artplayerProxyCanvas.default();
|
||||
// @ts-expect-error Callback must be a function in public types.
|
||||
artplayerProxyCanvas(42);`
|
||||
for (const compiler of [ts, compat]) {
|
||||
assert.deepEqual(checkPluginEditorDeclaration(generated, core, consumer, compiler), [])
|
||||
assert.equal(checkPluginEditorDeclaration(generated, core, consumer.replaceAll(/\/\/ @ts-expect-error[^\n]*\n/g, ''), compiler).length, 1)
|
||||
assert.equal(checkPluginEditorDeclaration(generated, core, consumer.replaceAll(/\/\/ @ts-expect-error[^\n]*\n/g, ''), compiler).length, 2)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
Reference in new issue
Block a user