mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-20 04:01:20 -08:00
ci: [CI-01] verify identical tarballs on three Node runtimes
This commit is contained in:
1 parent
585e11c53c
commit
38355ae4cf
21 files changed
+839
-27
No files matched your search
@@ -22,7 +22,7 @@ test('Package checks reject missing files, missing wildcard exports and internal
|
||||
test('Actual isolated runtime rejects removed default exports and required files', async () => {
|
||||
const { dir } = await publishedConsumer()
|
||||
try {
|
||||
assert.equal(runtimeConsumer(dir, { baseline: true }).checks.length, 20 + Object.keys(emitterContracts).length)
|
||||
assert.equal(runtimeConsumer(dir, { baseline: true }).checks.length, 22 + Object.keys(emitterContracts).length)
|
||||
assert.throws(() => runtimeConsumer(dir), /Command failed/)
|
||||
const esm = path.join(dir, 'node_modules/artplayer-plugin-chapter/dist/artplayer-plugin-chapter.mjs')
|
||||
const original = fs.readFileSync(esm)
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import process from 'node:process'
|
||||
// eslint-disable-next-line test/no-import-node-test -- Verify isolated package runtime evidence and rejected inputs.
|
||||
import test from 'node:test'
|
||||
import { publishedConsumer } from '../scripts/package-check.mjs'
|
||||
import { removeConsumer } from '../scripts/package-consumer.mjs'
|
||||
import { checkPackageRuntime, runtimeConsumer, verifyInstalledFiles } from '../scripts/package-runtime.mjs'
|
||||
|
||||
test('Runtime evidence records the actual child Node version with published contracts', async () => {
|
||||
const { dir, releases } = await publishedConsumer()
|
||||
try {
|
||||
verifyInstalledFiles(dir, releases)
|
||||
const result = runtimeConsumer(dir, { baseline: true })
|
||||
assert.equal(result.node, process.versions.node)
|
||||
assert(result.checks.includes('DIST.esm'))
|
||||
assert(result.checks.includes('SSR.constructor-browser-only'))
|
||||
}
|
||||
finally { removeConsumer(dir) }
|
||||
})
|
||||
|
||||
test('Installed runtime verification rejects tampered bytes, traversal and workspace links', async () => {
|
||||
const { dir, releases } = await publishedConsumer()
|
||||
try {
|
||||
const manifest = path.join(dir, 'node_modules/artplayer/package.json')
|
||||
const original = fs.readFileSync(manifest)
|
||||
fs.appendFileSync(manifest, '\n')
|
||||
assert.throws(() => verifyInstalledFiles(dir, releases), /Installed package bytes differ/)
|
||||
fs.writeFileSync(manifest, original)
|
||||
const unsafe = structuredClone(releases)
|
||||
unsafe[0].files['package/../escape'] = '0'.repeat(64)
|
||||
assert.throws(() => verifyInstalledFiles(dir, unsafe), /Unsafe package member/)
|
||||
const root = path.join(dir, 'node_modules/artplayer')
|
||||
const target = path.join(dir, 'linked-artplayer')
|
||||
assert(path.dirname(target) === dir && path.dirname(path.dirname(root)) === dir)
|
||||
fs.renameSync(root, target)
|
||||
fs.symlinkSync(target, root, process.platform === 'win32' ? 'junction' : 'dir')
|
||||
assert.throws(() => verifyInstalledFiles(dir, releases), /workspace links/)
|
||||
}
|
||||
finally { removeConsumer(dir) }
|
||||
})
|
||||
|
||||
test('Runtime selection cannot silently fall back to a different Node version', () => {
|
||||
assert.throws(() => checkPackageRuntime({ expectedNode: '0.0.0' }), /requested exact Node/)
|
||||
})
|
||||
|
||||
test('Runtime package selection rejects other directories and stale source commits before installing', () => {
|
||||
const parent = path.resolve('refactor/.cache/packages')
|
||||
fs.mkdirSync(parent, { recursive: true })
|
||||
const directory = fs.mkdtempSync(path.join(parent, 'run-runtime-negative-'))
|
||||
try {
|
||||
fs.writeFileSync(path.join(directory, 'report.json'), JSON.stringify({ source: '0'.repeat(40) }))
|
||||
assert.throws(() => checkPackageRuntime({ expectedNode: process.versions.node, output: directory }), /another commit/)
|
||||
assert.throws(() => checkPackageRuntime({ expectedNode: process.versions.node, output: parent }), /direct package-check run/)
|
||||
}
|
||||
finally {
|
||||
assert.equal(path.dirname(fs.realpathSync(directory)), fs.realpathSync(parent))
|
||||
assert(path.basename(directory).startsWith('run-runtime-negative-'))
|
||||
fs.rmSync(directory, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
+16
-1
@@ -35,9 +35,24 @@ type failures; all five groups now require zero diagnostics. Exact known
|
||||
diagnostics are referenced by `known-types.json` and `test/types/known-diagnostics.json`.
|
||||
Unexpected errors and unexpectedly removed errors both require investigation.
|
||||
Remove a known case when its owning task fixes it; keep frozen release evidence intact.
|
||||
`yarn test:package:release` additionally rejects any remaining known type errors.
|
||||
`yarn test:package:release` additionally rejects remaining known type and runtime blockers.
|
||||
Passing this command alone is not authorization or sufficient evidence to publish.
|
||||
|
||||
`scripts/package-runtime.mjs` uses only Node built-ins and can reinstall the exact checked
|
||||
tarballs under a different Node. First run `yarn test:package` on the canonical toolchain,
|
||||
then use the selected executable with `scripts/package-runtime.mjs --expected-node 20.19.0`
|
||||
or `--expected-node 22.12.0`; `yarn test:package:runtime` verifies canonical Node.
|
||||
Each run checks HEAD, archive digests, the frozen offline install, installed bytes and
|
||||
the actual child version. Reports and failure logs are `runtime-node-<version>.*` in
|
||||
the same package output directory. Old reports without source/toolchain fields must
|
||||
be rebuilt. CI restores canonical Node before running browser tools.
|
||||
|
||||
CORE-25 remains an explicit defect: both published and candidate static defaults throw
|
||||
without navigator. The fixture tests that exact failure on every runtime, then uses an
|
||||
identical controlled language for default-value comparison and restores the global.
|
||||
`knownRuntimeBlockers=1` prevents strict release success; ordinary observation tests
|
||||
passing do not mean the defect is fixed. See [Node evidence](../../refactor/changes/2026-09-13-CI-01-node-consumers.md).
|
||||
|
||||
The optional core `artplayer/runtime` entry has eight additional strict consumer
|
||||
groups: TS 5.1.6 and 5.9.3 each check Node10 CommonJS, NodeNext CJS/ESM and Bundler.
|
||||
These fixtures cover accurate returns and getter/setter types, construction-stage
|
||||
|
||||
@@ -186,8 +186,24 @@ const vm = require('node:vm');
|
||||
checks.push(id)
|
||||
}
|
||||
const shape = value => Object.fromEntries(Object.entries(Object.getOwnPropertyDescriptors(value)).map(([key, d]) => [key, { enumerable: d.enumerable, configurable: d.configurable, writable: d.writable, get: typeof d.get, set: typeof d.set, value: typeof d.value }]))
|
||||
observations.api = { static: shape(core), prototype: shape(core.prototype), emitter: shape(core.Emitter.prototype), factory: shape(chapter), defaults: JSON.parse(JSON.stringify(core.option, (_, value) => typeof value === 'function' ? '$function' : value)) }
|
||||
process.stdout.write(JSON.stringify({ checks, observations }))
|
||||
const defaultNavigator = Object.getOwnPropertyDescriptor(globalThis, 'navigator')
|
||||
try {
|
||||
assert(delete globalThis.navigator)
|
||||
// CORE-25 tracks this reproduced published and candidate failure separately.
|
||||
assert.throws(() => core.option, { name: 'ReferenceError', message: 'navigator is not defined' })
|
||||
observations.defaultsWithoutNavigator = { name: 'ReferenceError', message: 'navigator is not defined', historical: !!expected.baseline, resolved: false }
|
||||
check('SSR.defaults-missing-navigator-known-failure', true)
|
||||
Object.defineProperty(globalThis, 'navigator', { configurable: true, value: { language: 'en-US' } })
|
||||
observations.api = { static: shape(core), prototype: shape(core.prototype), emitter: shape(core.Emitter.prototype), factory: shape(chapter), defaults: JSON.parse(JSON.stringify(core.option, (_, value) => typeof value === 'function' ? '$function' : value)) }
|
||||
check('API.defaults-controlled-language', observations.api.defaults.lang === 'en-us')
|
||||
}
|
||||
finally {
|
||||
if (defaultNavigator)
|
||||
Object.defineProperty(globalThis, 'navigator', defaultNavigator)
|
||||
else
|
||||
delete globalThis.navigator
|
||||
}
|
||||
process.stdout.write(JSON.stringify({ node: process.versions.node, checks, observations }))
|
||||
})().catch((error) => {
|
||||
console.error(error)
|
||||
process.exitCode = 1
|
||||
|
||||
Reference in new issue
Block a user