fix(release): derive video suite paths from version (#2384)

Co-authored-by: jan <jan@w-saxs001.local>
This commit is contained in:
Yang Cheng 2026-06-30 06:50:57 +08:00 committed by GitHub
parent 9896644dab
commit 73895b5d05
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 91 additions and 33 deletions

View File

@ -5,9 +5,7 @@ const fs = require('fs');
const path = require('path'); const path = require('path');
const { spawnSync } = require('child_process'); const { spawnSync } = require('child_process');
const RELEASE = '2.0.0-rc.1';
const SCHEMA_VERSION = 'ecc.release-video-suite.v1'; const SCHEMA_VERSION = 'ecc.release-video-suite.v1';
const VIDEO_MANIFEST_PATH = `docs/releases/${RELEASE}/video-suite-production.md`;
const HYPERGROWTH_DOC_PATH = 'docs/releases/2.0.0/ecc-2-hypergrowth-release-command-center.md'; const HYPERGROWTH_DOC_PATH = 'docs/releases/2.0.0/ecc-2-hypergrowth-release-command-center.md';
const REQUIRED_DOC_MARKERS = [ const REQUIRED_DOC_MARKERS = [
@ -320,7 +318,7 @@ function usage() {
console.log([ console.log([
'Usage: node scripts/release-video-suite.js [options]', 'Usage: node scripts/release-video-suite.js [options]',
'', '',
'Validates the ECC 2.0 release video production lane without committing raw media paths.', 'Validates the ECC 2.0 release video production lane for the package.json release version without committing raw media paths.',
'', '',
'Options:', 'Options:',
' --format <text|json> Output format (default: text)', ' --format <text|json> Output format (default: text)',
@ -455,6 +453,28 @@ function safeParseJson(text) {
} }
} }
function resolveRelease(packageJson, options = {}) {
if (typeof options.release === 'string' && options.release.trim()) {
return options.release.trim();
}
return typeof packageJson.version === 'string' ? packageJson.version.trim() : '';
}
function releaseDirFor(release) {
return `docs/releases/${release}`;
}
function releasePathsFor(release) {
const releaseDir = releaseDirFor(release);
return {
videoManifestPath: `${releaseDir}/video-suite-production.md`,
previewManifestPath: `${releaseDir}/preview-pack-manifest.md`,
launchChecklistPath: `${releaseDir}/launch-checklist.md`,
};
}
function lineNumberForIndex(text, index) { function lineNumberForIndex(text, index) {
return text.slice(0, index).split('\n').length; return text.slice(0, index).split('\n').length;
} }
@ -841,17 +861,19 @@ function buildReport(options = {}) {
const suiteRoot = options.suiteRoot ? path.resolve(options.suiteRoot) : ''; const suiteRoot = options.suiteRoot ? path.resolve(options.suiteRoot) : '';
const skipProbe = Boolean(options.skipProbe); const skipProbe = Boolean(options.skipProbe);
const packageJson = safeParseJson(readText(rootDir, 'package.json')) || {}; const packageJson = safeParseJson(readText(rootDir, 'package.json')) || {};
const release = resolveRelease(packageJson, options);
const releasePaths = releasePathsFor(release);
const packageScripts = packageJson.scripts || {}; const packageScripts = packageJson.scripts || {};
const packageFiles = Array.isArray(packageJson.files) ? packageJson.files : []; const packageFiles = Array.isArray(packageJson.files) ? packageJson.files : [];
const manifest = readText(rootDir, VIDEO_MANIFEST_PATH); const manifest = readText(rootDir, releasePaths.videoManifestPath);
const hypergrowth = readText(rootDir, HYPERGROWTH_DOC_PATH); const hypergrowth = readText(rootDir, HYPERGROWTH_DOC_PATH);
const missingDocMarkers = REQUIRED_DOC_MARKERS.filter(marker => !manifest.includes(marker)); const missingDocMarkers = REQUIRED_DOC_MARKERS.filter(marker => !manifest.includes(marker));
const forbiddenPaths = scanForbiddenPaths(rootDir, [ const forbiddenPaths = scanForbiddenPaths(rootDir, [
VIDEO_MANIFEST_PATH, releasePaths.videoManifestPath,
HYPERGROWTH_DOC_PATH, HYPERGROWTH_DOC_PATH,
`docs/releases/${RELEASE}/preview-pack-manifest.md`, releasePaths.previewManifestPath,
`docs/releases/${RELEASE}/launch-checklist.md`, releasePaths.launchChecklistPath,
]); ]);
const sourceAssets = inspectSourceAssets(sourceRoot, skipProbe); const sourceAssets = inspectSourceAssets(sourceRoot, skipProbe);
const suiteArtifacts = inspectSuiteArtifacts(suiteRoot, skipProbe); const suiteArtifacts = inspectSuiteArtifacts(suiteRoot, skipProbe);
@ -875,7 +897,7 @@ function buildReport(options = {}) {
'video-suite-manifest-present', 'video-suite-manifest-present',
manifest && missingDocMarkers.length === 0 ? 'pass' : 'fail', manifest && missingDocMarkers.length === 0 ? 'pass' : 'fail',
manifest && missingDocMarkers.length === 0 manifest && missingDocMarkers.length === 0
? `${VIDEO_MANIFEST_PATH} includes the required production markers` ? `${releasePaths.videoManifestPath} includes the required production markers`
: `missing markers: ${missingDocMarkers.join(', ') || 'manifest file missing'}`, : `missing markers: ${missingDocMarkers.join(', ') || 'manifest file missing'}`,
'Restore the video production manifest and required production markers.' 'Restore the video production manifest and required production markers.'
), ),
@ -960,7 +982,7 @@ function buildReport(options = {}) {
return { return {
schema_version: SCHEMA_VERSION, schema_version: SCHEMA_VERSION,
release: RELEASE, release,
generatedAt: options.generatedAt || new Date().toISOString(), generatedAt: options.generatedAt || new Date().toISOString(),
root: rootDir, root: rootDir,
sourceRootConfigured: Boolean(sourceRoot), sourceRootConfigured: Boolean(sourceRoot),
@ -1090,6 +1112,7 @@ module.exports = {
REQUIRED_SOURCE_ASSETS, REQUIRED_SOURCE_ASSETS,
REQUIRED_SUITE_ARTIFACTS, REQUIRED_SUITE_ARTIFACTS,
buildReport, buildReport,
releasePathsFor,
parseArgs, parseArgs,
renderText, renderText,
summarizeReport, summarizeReport,

View File

@ -19,6 +19,13 @@ const {
summarizeReport, summarizeReport,
} = require(SCRIPT); } = require(SCRIPT);
const CURRENT_RELEASE = require(path.join(__dirname, '..', '..', 'package.json')).version;
const RC_RELEASE = '2.0.0-rc.1';
function releaseDirFor(release) {
return `docs/releases/${release}`;
}
function createTempDir(prefix) { function createTempDir(prefix) {
return fs.mkdtempSync(path.join(os.tmpdir(), prefix)); return fs.mkdtempSync(path.join(os.tmpdir(), prefix));
} }
@ -33,31 +40,39 @@ function writeFile(rootDir, relativePath, content = 'fixture') {
fs.writeFileSync(targetPath, content); fs.writeFileSync(targetPath, content);
} }
function seedRepo(rootDir, overrides = {}) { function videoManifestContent(extra = '') {
return [
'# ECC 2.0 Video Suite Production Manifest',
'ECC_VIDEO_SOURCE_ROOT',
'ECC_VIDEO_RELEASE_SUITE_ROOT',
'Primary launch video',
'video-use compatible workflow',
'Self-Eval Gate',
'Do Not Publish If',
'Do not commit raw footage, transcript JSON, or timeline exports',
extra,
].join('\n');
}
function seedRepo(rootDir, overrides = {}, options = {}) {
const release = options.release || CURRENT_RELEASE;
const releaseDir = releaseDirFor(release);
const files = { const files = {
'package.json': JSON.stringify({ 'package.json': JSON.stringify({
name: 'ecc-universal', name: 'ecc-universal',
version: release,
files: ['scripts/release-video-suite.js'], files: ['scripts/release-video-suite.js'],
scripts: { scripts: {
'release:video-suite': 'node scripts/release-video-suite.js', 'release:video-suite': 'node scripts/release-video-suite.js',
}, },
}, null, 2), }, null, 2),
'docs/releases/2.0.0-rc.1/video-suite-production.md': [ [`${releaseDir}/video-suite-production.md`]: videoManifestContent(),
'# ECC 2.0 Video Suite Production Manifest',
'ECC_VIDEO_SOURCE_ROOT',
'ECC_VIDEO_RELEASE_SUITE_ROOT',
'Primary launch video',
'video-use compatible workflow',
'Self-Eval Gate',
'Do Not Publish If',
'Do not commit raw footage, transcript JSON, or timeline exports',
].join('\n'),
'docs/releases/2.0.0/ecc-2-hypergrowth-release-command-center.md': [ 'docs/releases/2.0.0/ecc-2-hypergrowth-release-command-center.md': [
'Keep raw absolute paths out of public docs', 'Keep raw absolute paths out of public docs',
'Pick final video cuts, upload after approval, and attach public URLs', 'Pick final video cuts, upload after approval, and attach public URLs',
].join('\n'), ].join('\n'),
'docs/releases/2.0.0-rc.1/preview-pack-manifest.md': 'video-suite-production.md', [`${releaseDir}/preview-pack-manifest.md`]: 'video-suite-production.md',
'docs/releases/2.0.0-rc.1/launch-checklist.md': 'release video suite', [`${releaseDir}/launch-checklist.md`]: 'release video suite',
}; };
for (const [relativePath, content] of Object.entries({ ...files, ...overrides })) { for (const [relativePath, content] of Object.entries({ ...files, ...overrides })) {
@ -171,6 +186,7 @@ function runTests() {
}); });
assert.strictEqual(report.schema_version, 'ecc.release-video-suite.v1'); assert.strictEqual(report.schema_version, 'ecc.release-video-suite.v1');
assert.strictEqual(report.release, CURRENT_RELEASE);
assert.strictEqual(report.ready, true); assert.strictEqual(report.ready, true);
assert.strictEqual(report.mediaPathsRedacted, true); assert.strictEqual(report.mediaPathsRedacted, true);
assert.ok(report.checks.every(check => check.status === 'pass')); assert.ok(report.checks.every(check => check.status === 'pass'));
@ -194,6 +210,33 @@ function runTests() {
} }
})) passed++; else failed++; })) passed++; else failed++;
if (test('release override keeps rc.1 video fixtures testable', () => {
const rootDir = createTempDir('release-video-rc-');
const sourceRoot = createTempDir('release-video-source-');
const suiteRoot = createTempDir('release-video-suite-');
try {
seedRepo(rootDir, {}, { release: RC_RELEASE });
seedMedia(sourceRoot, suiteRoot);
const report = buildReport({
root: rootDir,
release: RC_RELEASE,
sourceRoot,
suiteRoot,
skipProbe: true,
generatedAt: '2026-05-19T00:00:00.000Z',
});
assert.strictEqual(report.release, RC_RELEASE);
assert.strictEqual(report.ready, true);
} finally {
cleanup(rootDir);
cleanup(sourceRoot);
cleanup(suiteRoot);
}
})) passed++; else failed++;
if (test('publish candidate videos require visual blank-frame QA', () => { if (test('publish candidate videos require visual blank-frame QA', () => {
const publishVideos = REQUIRED_PUBLISH_CANDIDATES.filter(candidate => candidate.kind === 'video'); const publishVideos = REQUIRED_PUBLISH_CANDIDATES.filter(candidate => candidate.kind === 'video');
@ -231,18 +274,9 @@ function runTests() {
const suiteRoot = createTempDir('release-video-suite-'); const suiteRoot = createTempDir('release-video-suite-');
try { try {
const releaseDir = releaseDirFor(CURRENT_RELEASE);
seedRepo(rootDir, { seedRepo(rootDir, {
'docs/releases/2.0.0-rc.1/video-suite-production.md': [ [`${releaseDir}/video-suite-production.md`]: videoManifestContent('/Users/affoon/private-media'),
'# ECC 2.0 Video Suite Production Manifest',
'ECC_VIDEO_SOURCE_ROOT',
'ECC_VIDEO_RELEASE_SUITE_ROOT',
'Primary launch video',
'video-use compatible workflow',
'Self-Eval Gate',
'Do Not Publish If',
'Do not commit raw footage, transcript JSON, or timeline exports',
'/Users/affoon/private-media',
].join('\n'),
}); });
seedMedia(sourceRoot, suiteRoot); seedMedia(sourceRoot, suiteRoot);
@ -283,6 +317,7 @@ function runTests() {
const parsed = JSON.parse(output); const parsed = JSON.parse(output);
assert.strictEqual(parsed.ready, true); assert.strictEqual(parsed.ready, true);
assert.strictEqual(parsed.release, CURRENT_RELEASE);
assert.strictEqual(parsed.sourceRootConfigured, true); assert.strictEqual(parsed.sourceRootConfigured, true);
assert.strictEqual(parsed.suiteRootConfigured, true); assert.strictEqual(parsed.suiteRootConfigured, true);
assert.strictEqual(parsed.sourceAssetSummary.present, REQUIRED_SOURCE_ASSETS.length); assert.strictEqual(parsed.sourceAssetSummary.present, REQUIRED_SOURCE_ASSETS.length);