fix: harden release surface version and packaging sync (#1388)

* fix: keep ecc release surfaces version-synced

* fix: keep lockfile release version in sync

* fix: remove release version drift from locks and tests

* fix: keep root release metadata version-synced

* fix: keep codex marketplace metadata version-synced

* fix: gate release workflows on full metadata sync

* fix: ship all versioned release metadata

* fix: harden manual release path

* fix: keep localized release docs version-synced

* fix: sync install architecture version examples

* test: cover shipped plugin metadata in npm pack

* fix: verify final npm payload in release script

* fix: ship opencode lockfile in npm package

* docs: sync localized release highlights

* fix: stabilize windows ci portability

* fix: tighten release script version sync

* fix: prefer repo-relative hook file paths

* fix: make npm pack test shell-safe on windows
This commit is contained in:
Affaan Mustafa
2026-04-12 22:33:32 -07:00
committed by GitHub
parent fc5921a521
commit 28edd197c2
13 changed files with 487 additions and 25 deletions

View File

@@ -20,6 +20,20 @@ const path = require('path');
const repoRoot = path.resolve(__dirname, '..');
const repoRootWithSep = `${repoRoot}${path.sep}`;
const packageJsonPath = path.join(repoRoot, 'package.json');
const packageLockPath = path.join(repoRoot, 'package-lock.json');
const rootAgentsPath = path.join(repoRoot, 'AGENTS.md');
const trAgentsPath = path.join(repoRoot, 'docs', 'tr', 'AGENTS.md');
const zhCnAgentsPath = path.join(repoRoot, 'docs', 'zh-CN', 'AGENTS.md');
const ptBrReadmePath = path.join(repoRoot, 'docs', 'pt-BR', 'README.md');
const trReadmePath = path.join(repoRoot, 'docs', 'tr', 'README.md');
const rootZhCnReadmePath = path.join(repoRoot, 'README.zh-CN.md');
const agentYamlPath = path.join(repoRoot, 'agent.yaml');
const versionFilePath = path.join(repoRoot, 'VERSION');
const zhCnReadmePath = path.join(repoRoot, 'docs', 'zh-CN', 'README.md');
const selectiveInstallArchitecturePath = path.join(repoRoot, 'docs', 'SELECTIVE-INSTALL-ARCHITECTURE.md');
const opencodePackageJsonPath = path.join(repoRoot, '.opencode', 'package.json');
const opencodePackageLockPath = path.join(repoRoot, '.opencode', 'package-lock.json');
let passed = 0;
let failed = 0;
@@ -64,6 +78,86 @@ function assertSafeRepoRelativePath(relativePath, label) {
);
}
const rootPackage = loadJsonObject(packageJsonPath, 'package.json');
const packageLock = loadJsonObject(packageLockPath, 'package-lock.json');
const opencodePackageLock = loadJsonObject(opencodePackageLockPath, '.opencode/package-lock.json');
const expectedVersion = rootPackage.version;
test('package.json has version field', () => {
assert.ok(expectedVersion, 'Expected package.json version field');
});
test('package-lock.json root version matches package.json', () => {
assert.strictEqual(packageLock.version, expectedVersion);
assert.ok(packageLock.packages && packageLock.packages[''], 'Expected package-lock root package entry');
assert.strictEqual(packageLock.packages[''].version, expectedVersion);
});
test('AGENTS.md version line matches package.json', () => {
const agentsSource = fs.readFileSync(rootAgentsPath, 'utf8');
const match = agentsSource.match(/^\*\*Version:\*\* ([0-9]+\.[0-9]+\.[0-9]+)$/m);
assert.ok(match, 'Expected AGENTS.md to declare a top-level version line');
assert.strictEqual(match[1], expectedVersion);
});
test('docs/tr/AGENTS.md version line matches package.json', () => {
const agentsSource = fs.readFileSync(trAgentsPath, 'utf8');
const match = agentsSource.match(/^\*\*Sürüm:\*\* ([0-9]+\.[0-9]+\.[0-9]+)$/m);
assert.ok(match, 'Expected docs/tr/AGENTS.md to declare a top-level version line');
assert.strictEqual(match[1], expectedVersion);
});
test('docs/zh-CN/AGENTS.md version line matches package.json', () => {
const agentsSource = fs.readFileSync(zhCnAgentsPath, 'utf8');
const match = agentsSource.match(/^\*\*版本:\*\* ([0-9]+\.[0-9]+\.[0-9]+)$/m);
assert.ok(match, 'Expected docs/zh-CN/AGENTS.md to declare a top-level version line');
assert.strictEqual(match[1], expectedVersion);
});
test('agent.yaml version matches package.json', () => {
const agentYamlSource = fs.readFileSync(agentYamlPath, 'utf8');
const match = agentYamlSource.match(/^version:\s*([0-9]+\.[0-9]+\.[0-9]+)$/m);
assert.ok(match, 'Expected agent.yaml to declare a top-level version field');
assert.strictEqual(match[1], expectedVersion);
});
test('VERSION file matches package.json', () => {
const versionFile = fs.readFileSync(versionFilePath, 'utf8').trim();
assert.ok(versionFile, 'Expected VERSION file to be non-empty');
assert.strictEqual(versionFile, expectedVersion);
});
test('docs/SELECTIVE-INSTALL-ARCHITECTURE.md repoVersion example matches package.json', () => {
const source = fs.readFileSync(selectiveInstallArchitecturePath, 'utf8');
const match = source.match(/"repoVersion":\s*"([0-9]+\.[0-9]+\.[0-9]+)"/);
assert.ok(match, 'Expected docs/SELECTIVE-INSTALL-ARCHITECTURE.md to declare a repoVersion example');
assert.strictEqual(match[1], expectedVersion);
});
test('docs/pt-BR/README.md latest release heading matches package.json', () => {
const source = fs.readFileSync(ptBrReadmePath, 'utf8');
assert.ok(
source.includes(`### v${expectedVersion} `),
'Expected docs/pt-BR/README.md to advertise the current release heading',
);
});
test('docs/tr/README.md latest release heading matches package.json', () => {
const source = fs.readFileSync(trReadmePath, 'utf8');
assert.ok(
source.includes(`### v${expectedVersion} `),
'Expected docs/tr/README.md to advertise the current release heading',
);
});
test('README.zh-CN.md latest release heading matches package.json', () => {
const source = fs.readFileSync(rootZhCnReadmePath, 'utf8');
assert.ok(
source.includes(`### v${expectedVersion} `),
'Expected README.zh-CN.md to advertise the current release heading',
);
});
// ── Claude plugin manifest ────────────────────────────────────────────────────
console.log('\n=== .claude-plugin/plugin.json ===\n');
@@ -80,6 +174,10 @@ test('claude plugin.json has version field', () => {
assert.ok(claudePlugin.version, 'Expected version field');
});
test('claude plugin.json version matches package.json', () => {
assert.strictEqual(claudePlugin.version, expectedVersion);
});
test('claude plugin.json uses short plugin slug', () => {
assert.strictEqual(claudePlugin.name, 'ecc');
});
@@ -156,6 +254,10 @@ test('claude marketplace.json has plugins array with a short ecc plugin entry',
assert.strictEqual(claudeMarketplace.plugins[0].name, 'ecc');
});
test('claude marketplace.json plugin version matches package.json', () => {
assert.strictEqual(claudeMarketplace.plugins[0].version, expectedVersion);
});
// ── Codex plugin manifest ─────────────────────────────────────────────────────
// Per official docs: https://platform.openai.com/docs/codex/plugins
// - .codex-plugin/plugin.json is the required manifest
@@ -183,6 +285,10 @@ test('codex plugin.json has version field', () => {
assert.ok(codexPlugin.version, 'Expected version field');
});
test('codex plugin.json version matches package.json', () => {
assert.strictEqual(codexPlugin.version, expectedVersion);
});
test('codex plugin.json skills is a string (not array) per official spec', () => {
assert.strictEqual(
typeof codexPlugin.skills,
@@ -268,6 +374,7 @@ test('marketplace.json exists at .agents/plugins/', () => {
});
const marketplace = loadJsonObject(marketplacePath, '.agents/plugins/marketplace.json');
const opencodePackage = loadJsonObject(opencodePackageJsonPath, '.opencode/package.json');
test('marketplace.json has name field', () => {
assert.ok(marketplace.name, 'Expected name field');
@@ -284,6 +391,7 @@ test('marketplace.json has plugins array with at least one entry', () => {
test('marketplace.json plugin entries have required fields', () => {
for (const plugin of marketplace.plugins) {
assert.ok(plugin.name, `Plugin entry missing name`);
assert.ok(plugin.version, `Plugin "${plugin.name}" missing version`);
assert.ok(plugin.source && plugin.source.source, `Plugin "${plugin.name}" missing source.source`);
assert.ok(plugin.policy && plugin.policy.installation, `Plugin "${plugin.name}" missing policy.installation`);
assert.ok(plugin.category, `Plugin "${plugin.name}" missing category`);
@@ -294,6 +402,10 @@ test('marketplace.json plugin entry uses short plugin slug', () => {
assert.strictEqual(marketplace.plugins[0].name, 'ecc');
});
test('marketplace.json plugin version matches package.json', () => {
assert.strictEqual(marketplace.plugins[0].version, expectedVersion);
});
test('marketplace local plugin path resolves to the repo-root Codex bundle', () => {
for (const plugin of marketplace.plugins) {
if (!plugin.source || plugin.source.source !== 'local') {
@@ -317,6 +429,30 @@ test('marketplace local plugin path resolves to the repo-root Codex bundle', ()
}
});
test('.opencode/package.json version matches package.json', () => {
assert.strictEqual(opencodePackage.version, expectedVersion);
});
test('.opencode/package-lock.json root version matches package.json', () => {
assert.strictEqual(opencodePackageLock.version, expectedVersion);
assert.ok(opencodePackageLock.packages && opencodePackageLock.packages[''], 'Expected .opencode/package-lock root package entry');
assert.strictEqual(opencodePackageLock.packages[''].version, expectedVersion);
});
test('README version row matches package.json', () => {
const readme = fs.readFileSync(path.join(repoRoot, 'README.md'), 'utf8');
const match = readme.match(/^\| \*\*Version\*\* \| Plugin \| Plugin \| Reference config \| ([0-9][0-9.]*) \|$/m);
assert.ok(match, 'Expected README version summary row');
assert.strictEqual(match[1], expectedVersion);
});
test('docs/zh-CN/README.md version row matches package.json', () => {
const readme = fs.readFileSync(zhCnReadmePath, 'utf8');
const match = readme.match(/^\| \*\*版本\*\* \| 插件 \| 插件 \| 参考配置 \| ([0-9][0-9.]*) \|$/m);
assert.ok(match, 'Expected docs/zh-CN/README.md version summary row');
assert.strictEqual(match[1], expectedVersion);
});
// ── Summary ───────────────────────────────────────────────────────────────────
console.log(`\nPassed: ${passed}`);
console.log(`Failed: ${failed}`);