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 { spawnSync } = require('child_process');
const RELEASE = '2.0.0-rc.1';
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 REQUIRED_DOC_MARKERS = [
@ -320,7 +318,7 @@ function usage() {
console.log([
'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:',
' --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) {
return text.slice(0, index).split('\n').length;
}
@ -841,17 +861,19 @@ function buildReport(options = {}) {
const suiteRoot = options.suiteRoot ? path.resolve(options.suiteRoot) : '';
const skipProbe = Boolean(options.skipProbe);
const packageJson = safeParseJson(readText(rootDir, 'package.json')) || {};
const release = resolveRelease(packageJson, options);
const releasePaths = releasePathsFor(release);
const packageScripts = packageJson.scripts || {};
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 missingDocMarkers = REQUIRED_DOC_MARKERS.filter(marker => !manifest.includes(marker));
const forbiddenPaths = scanForbiddenPaths(rootDir, [
VIDEO_MANIFEST_PATH,
releasePaths.videoManifestPath,
HYPERGROWTH_DOC_PATH,
`docs/releases/${RELEASE}/preview-pack-manifest.md`,
`docs/releases/${RELEASE}/launch-checklist.md`,
releasePaths.previewManifestPath,
releasePaths.launchChecklistPath,
]);
const sourceAssets = inspectSourceAssets(sourceRoot, skipProbe);
const suiteArtifacts = inspectSuiteArtifacts(suiteRoot, skipProbe);
@ -875,7 +897,7 @@ function buildReport(options = {}) {
'video-suite-manifest-present',
manifest && missingDocMarkers.length === 0 ? 'pass' : 'fail',
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'}`,
'Restore the video production manifest and required production markers.'
),
@ -960,7 +982,7 @@ function buildReport(options = {}) {
return {
schema_version: SCHEMA_VERSION,
release: RELEASE,
release,
generatedAt: options.generatedAt || new Date().toISOString(),
root: rootDir,
sourceRootConfigured: Boolean(sourceRoot),
@ -1090,6 +1112,7 @@ module.exports = {
REQUIRED_SOURCE_ASSETS,
REQUIRED_SUITE_ARTIFACTS,
buildReport,
releasePathsFor,
parseArgs,
renderText,
summarizeReport,

View File

@ -19,6 +19,13 @@ const {
summarizeReport,
} = 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) {
return fs.mkdtempSync(path.join(os.tmpdir(), prefix));
}
@ -33,31 +40,39 @@ function writeFile(rootDir, relativePath, content = 'fixture') {
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 = {
'package.json': JSON.stringify({
name: 'ecc-universal',
version: release,
files: ['scripts/release-video-suite.js'],
scripts: {
'release:video-suite': 'node scripts/release-video-suite.js',
},
}, null, 2),
'docs/releases/2.0.0-rc.1/video-suite-production.md': [
'# 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'),
[`${releaseDir}/video-suite-production.md`]: videoManifestContent(),
'docs/releases/2.0.0/ecc-2-hypergrowth-release-command-center.md': [
'Keep raw absolute paths out of public docs',
'Pick final video cuts, upload after approval, and attach public URLs',
].join('\n'),
'docs/releases/2.0.0-rc.1/preview-pack-manifest.md': 'video-suite-production.md',
'docs/releases/2.0.0-rc.1/launch-checklist.md': 'release video suite',
[`${releaseDir}/preview-pack-manifest.md`]: 'video-suite-production.md',
[`${releaseDir}/launch-checklist.md`]: 'release video suite',
};
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.release, CURRENT_RELEASE);
assert.strictEqual(report.ready, true);
assert.strictEqual(report.mediaPathsRedacted, true);
assert.ok(report.checks.every(check => check.status === 'pass'));
@ -194,6 +210,33 @@ function runTests() {
}
})) 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', () => {
const publishVideos = REQUIRED_PUBLISH_CANDIDATES.filter(candidate => candidate.kind === 'video');
@ -231,18 +274,9 @@ function runTests() {
const suiteRoot = createTempDir('release-video-suite-');
try {
const releaseDir = releaseDirFor(CURRENT_RELEASE);
seedRepo(rootDir, {
'docs/releases/2.0.0-rc.1/video-suite-production.md': [
'# 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'),
[`${releaseDir}/video-suite-production.md`]: videoManifestContent('/Users/affoon/private-media'),
});
seedMedia(sourceRoot, suiteRoot);
@ -283,6 +317,7 @@ function runTests() {
const parsed = JSON.parse(output);
assert.strictEqual(parsed.ready, true);
assert.strictEqual(parsed.release, CURRENT_RELEASE);
assert.strictEqual(parsed.sourceRootConfigured, true);
assert.strictEqual(parsed.suiteRootConfigured, true);
assert.strictEqual(parsed.sourceAssetSummary.present, REQUIRED_SOURCE_ASSETS.length);