Files
ArtPlayer/refactor/scripts/distribution.mjs
T

68 lines
5.0 KiB
JavaScript

import assert from 'node:assert/strict'
import fs from 'node:fs'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
import { hash, refactorDir } from './releases.mjs'
const root = path.resolve(refactorDir, '..')
const filename = path.join(refactorDir, 'baselines/distribution.json')
function files(dir) {
if (!fs.existsSync(dir)) return []
return fs.readdirSync(dir, { withFileTypes: true }).flatMap(entry => entry.isDirectory() ? files(path.join(dir, entry.name)) : [path.join(dir, entry.name)])
}
function targets(value, field) {
if (typeof value === 'string') return [{ field, path: value }]
if (value && typeof value === 'object') return Object.entries(value).flatMap(([key, child]) => targets(child, `${field}.${key}`))
return []
}
export function captureDistribution() {
const initial = JSON.parse(fs.readFileSync(path.join(refactorDir, 'package-inventory.json'), 'utf8'))
return { schemaVersion: 1, task: 'BASE-05', capturedAt: new Date().toISOString(), sourceCommit: '2b7054d8',
note: 'Workspace manifest and observed resource inventory. Existing dist files may be stale; presence and hashes are not proof of a fresh build or npm publication. Only core/chapter published tarballs are verified in releases.json and consumers.json.',
packages: initial.packages.map(pkg => {
const dir = path.join(root, 'packages', pkg.name)
const manifestBytes = fs.readFileSync(path.join(dir, 'package.json'))
const manifest = JSON.parse(manifestBytes)
const resources = ['dist', 'types'].flatMap(name => files(path.join(dir, name))).map(file => ({ path: path.relative(dir, file).replaceAll('\\', '/'), bytes: fs.statSync(file).size, sha256: hash(fs.readFileSync(file)) })).sort((a, b) => a.path.localeCompare(b.path))
const declaredTargets = ['main', 'module', 'legacy', 'types', 'typings', 'exports', 'typesVersions', 'browser', 'unpkg', 'jsdelivr'].flatMap(field => targets(manifest[field], field)).map(target => {
const normalized = target.path.replace(/^\.\//, '')
const regex = new RegExp(`^${normalized.split('*').map(part => part.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('.*')}$`)
return { ...target, observedMatches: resources.filter(resource => regex.test(resource.path)).map(resource => resource.path) }
})
const site = pkg.name === 'artplayer-vitepress'
return { name: pkg.name, kind: site ? 'documentation-site' : 'npm-library', manifest, manifestSha256Lf: hash(manifestBytes.toString().replaceAll('\r\n', '\n')), declaredTargets, resources,
sourceAssets: files(path.join(dir, 'src')).filter(file => !/\.[cm]?[jt]sx?$/.test(file)).map(file => path.relative(dir, file).replaceAll('\\', '/')).sort(),
buildOutputs: site ? ['docs/document/** via vitepress outDir'] : [`dist/${pkg.name}.js`, `dist/${pkg.name}.legacy.js`, `dist/${pkg.name}.mjs`, `docs/compiled/${pkg.name}.{js,legacy.js,mjs} copied by root build`],
distributionStatus: site ? 'Actual workflow builds a static site; no library entrypoints. Manifest lacks private:true; publication intent needs SITE/REL confirmation, not an automatic npm release.' : 'Workspace inventory; per-package published contract remains required unless listed in releases.json.',
}
}),
}
}
export function validateDistribution(report) {
const initial = JSON.parse(fs.readFileSync(path.join(refactorDir, 'package-inventory.json'), 'utf8'))
assert.equal(report.schemaVersion, 1)
assert.deepEqual(report.packages.map(pkg => pkg.name), initial.packages.map(pkg => pkg.name), 'Distribution package coverage changed')
for (const pkg of report.packages) {
assert.equal(pkg.manifest.name, pkg.name)
if (pkg.kind === 'npm-library') assert(pkg.resources.length > 0 && pkg.declaredTargets.length > 0, `Missing library resources: ${pkg.name}`)
for (const resource of pkg.resources) assert(!resource.path.startsWith('/') && !resource.path.split('/').includes('..') && /^[a-f0-9]{64}$/.test(resource.sha256), 'Invalid resource provenance')
}
assert.equal(report.packages.filter(pkg => pkg.kind === 'documentation-site').length, 1)
const thumbnail = report.packages.find(pkg => pkg.name === 'artplayer-tool-thumbnail')
assert(thumbnail.declaredTargets.some(target => target.path.endsWith('.esm.js') && !target.observedMatches.length), 'Missing thumbnail entrypoint discrepancy')
}
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
if (process.argv.includes('--capture')) {
assert(!fs.existsSync(filename), 'Do not overwrite the historical distribution inventory')
const report = captureDistribution()
validateDistribution(report)
fs.writeFileSync(filename, `${JSON.stringify(report, null, 2)}\n`)
}
else {
assert(process.argv.slice(2).every(arg => arg === '--check'), 'Use --capture or --check')
const report = JSON.parse(fs.readFileSync(filename, 'utf8'))
validateDistribution(report)
console.log(`Frozen distribution inventory: ${report.packages.length} packages; current build and npm contents are not revalidated by this command`)
}
}