Skip to content

Commit 618010c

Browse files
committed
Fix changelog and 23.0.0 release archives
1 parent eea7ec8 commit 618010c

6 files changed

Lines changed: 1372 additions & 20 deletions

File tree

CHANGELOG.md

Lines changed: 1279 additions & 1 deletion
Large diffs are not rendered by default.

dist/webbrain-chrome-23.0.0.zip

-4 Bytes
Binary file not shown.

dist/webbrain-edge-23.0.0.zip

-4 Bytes
Binary file not shown.

dist/webbrain-firefox-23.0.0.zip

-3 Bytes
Binary file not shown.

scripts/build-zip.mjs

Lines changed: 57 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -20,24 +20,18 @@
2020
* dist/webbrain-edge-<version>.zip
2121
* dist/webbrain-firefox-<version>.zip
2222
*
23-
* <version> is read from package.json so a single npm-version bump
24-
* cascades into the right filenames.
23+
* <version> is read from package.json at HEAD, and every archived manifest
24+
* must match it. An uncommitted version bump is rejected instead of creating
25+
* a new-looking filename around an old manifest.
2526
*/
2627

2728
import { readFileSync, mkdirSync } from 'node:fs';
2829
import { execFileSync } from 'node:child_process';
29-
import { fileURLToPath } from 'node:url';
30+
import { fileURLToPath, pathToFileURL } from 'node:url';
3031
import path from 'node:path';
3132

3233
const __dirname = path.dirname(fileURLToPath(import.meta.url));
3334
const root = path.resolve(__dirname, '..');
34-
const pkg = JSON.parse(readFileSync(path.join(root, 'package.json'), 'utf8'));
35-
const version = pkg.version;
36-
37-
const distDir = path.join(root, 'dist');
38-
mkdirSync(distDir, { recursive: true });
39-
40-
console.log(`Building extension zips for v${version} …`);
4135

4236
const targets = [
4337
{ packageName: 'chrome', sourceDir: 'chrome' },
@@ -46,14 +40,58 @@ const targets = [
4640
{ packageName: 'firefox', sourceDir: 'firefox' },
4741
];
4842

49-
for (const { packageName, sourceDir } of targets) {
50-
const out = path.join(distDir, `webbrain-${packageName}-${version}.zip`);
51-
// -o writes directly to the file; avoids needing shell redirection,
52-
// so this runs identically on bash, zsh, cmd, and PowerShell.
53-
execFileSync(
54-
'git',
55-
['archive', '--format=zip', '-o', out, `HEAD:src/${sourceDir}`],
56-
{ stdio: 'inherit', cwd: root }
43+
export function assertMatchingArchiveVersion(expected, actual, label) {
44+
if (actual !== expected) {
45+
throw new Error(`${label} is ${actual}, but the release package version is ${expected}.`);
46+
}
47+
}
48+
49+
function readJsonAtHead(relativePath) {
50+
const json = execFileSync('git', ['show', `HEAD:${relativePath}`], {
51+
cwd: root,
52+
encoding: 'utf8',
53+
stdio: ['ignore', 'pipe', 'pipe'],
54+
});
55+
return JSON.parse(json);
56+
}
57+
58+
function runCli() {
59+
const workingPackage = JSON.parse(readFileSync(path.join(root, 'package.json'), 'utf8'));
60+
const headPackage = readJsonAtHead('package.json');
61+
assertMatchingArchiveVersion(
62+
headPackage.version,
63+
workingPackage.version,
64+
'Working-tree package.json version'
5765
);
58-
console.log(` ✓ dist/webbrain-${packageName}-${version}.zip`);
66+
67+
const version = headPackage.version;
68+
for (const { sourceDir } of targets) {
69+
const manifest = readJsonAtHead(`src/${sourceDir}/manifest.json`);
70+
assertMatchingArchiveVersion(version, manifest.version, `HEAD src/${sourceDir}/manifest.json version`);
71+
}
72+
73+
const distDir = path.join(root, 'dist');
74+
mkdirSync(distDir, { recursive: true });
75+
console.log(`Building extension zips for v${version} from HEAD …`);
76+
77+
for (const { packageName, sourceDir } of targets) {
78+
const out = path.join(distDir, `webbrain-${packageName}-${version}.zip`);
79+
// -o writes directly to the file; avoids needing shell redirection,
80+
// so this runs identically on bash, zsh, cmd, and PowerShell.
81+
execFileSync(
82+
'git',
83+
['archive', '--format=zip', '-o', out, `HEAD:src/${sourceDir}`],
84+
{ stdio: 'inherit', cwd: root }
85+
);
86+
console.log(` ✓ dist/webbrain-${packageName}-${version}.zip`);
87+
}
88+
}
89+
90+
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
91+
try {
92+
runCli();
93+
} catch (error) {
94+
console.error(`build-zip: ${error.message}`);
95+
process.exit(1);
96+
}
5997
}

test/run.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,9 @@ const { bumpSemver, rewriteVersionInJsonText, rewriteVersionByAnchor, isReleaseB
266266
const { normalizeChangelogBody, buildChangelogSection, insertChangelogEntry } = await import(
267267
'file://' + path.join(ROOT, 'scripts/update-changelog.mjs').replace(/\\/g, '/')
268268
);
269+
const { assertMatchingArchiveVersion } = await import(
270+
'file://' + path.join(ROOT, 'scripts/build-zip.mjs').replace(/\\/g, '/')
271+
);
269272

270273
// providers/manager.js — pure ESM at module load (chrome.* only inside
271274
// methods). We import the class so we can exercise the static categoryFor()
@@ -3342,6 +3345,11 @@ All notable changes to WebBrain are documented in this file.
33423345
});
33433346
assert.match(after, /^# Changelog[\s\S]*## \[1\.3\.0\] - 2026-06-28[\s\S]*## \[1\.2\.3\] - 2026-06-01/);
33443347
assert.match(after, /### Added\n- Added a release workflow\./);
3348+
assert.equal(
3349+
after.slice(after.indexOf('## [1.2.3]')),
3350+
before.slice(before.indexOf('## [1.2.3]')),
3351+
'inserting a release must preserve the existing changelog byte-for-byte'
3352+
);
33453353
});
33463354

33473355
test('update-changelog: normalizes plain notes and fenced AI output', () => {
@@ -3365,6 +3373,26 @@ test('update-changelog: rejects duplicate versions and nested release headings',
33653373
assert.throws(() => buildChangelogSection('1.4.0', '2026-06-28', '## [1.4.0] - 2026-06-28'), /must not include/);
33663374
});
33673375

3376+
test('repository changelog retains complete, non-empty release history', () => {
3377+
const changelog = fs.readFileSync(path.join(ROOT, 'CHANGELOG.md'), 'utf8');
3378+
const packageVersion = JSON.parse(fs.readFileSync(path.join(ROOT, 'package.json'), 'utf8')).version;
3379+
const releases = [...changelog.matchAll(/^## \[(\d+\.\d+\.\d+)\] - \d{4}-\d{2}-\d{2}$/gm)];
3380+
const versions = releases.map((match) => match[1]);
3381+
3382+
assert.equal(versions[0], packageVersion, 'the newest changelog entry must match package.json');
3383+
assert.ok(versions.length >= 100, `expected the full changelog history, found only ${versions.length} releases`);
3384+
assert.ok(versions.includes('0.7.0'), 'the changelog must retain its earliest documented release');
3385+
assert.equal(new Set(versions).size, versions.length, 'changelog release versions must be unique');
3386+
3387+
for (let i = 0; i < releases.length; i += 1) {
3388+
const bodyStart = releases[i].index + releases[i][0].length;
3389+
const bodyEnd = releases[i + 1]?.index ?? changelog.length;
3390+
const body = changelog.slice(bodyStart, bodyEnd);
3391+
assert.match(body, /^### (Added|Changed|Fixed|Tests)$/m, `${versions[i]} must have a change section`);
3392+
assert.match(body, /^- /m, `${versions[i]} must have at least one change`);
3393+
}
3394+
});
3395+
33683396
test('rejects bad current version', () => {
33693397
assert.throws(() => bumpSemver('not-a-version', 'patch'), /not MAJOR\.MINOR\.PATCH/);
33703398
assert.throws(() => bumpSemver('1.2', 'patch'), /not MAJOR\.MINOR\.PATCH/);
@@ -3529,6 +3557,14 @@ test('submissionZipRemoveCommand tolerates missing first Edge artifact', () => {
35293557
);
35303558
});
35313559

3560+
test('build-zip rejects filenames that would disagree with archived manifests', () => {
3561+
assert.doesNotThrow(() => assertMatchingArchiveVersion('23.0.0', '23.0.0', 'Chrome manifest'));
3562+
assert.throws(
3563+
() => assertMatchingArchiveVersion('23.0.0', '22.4.5', 'Chrome manifest'),
3564+
/Chrome manifest is 22\.4\.5, but the release package version is 23\.0\.0/
3565+
);
3566+
});
3567+
35323568
test('firefox manifest uses the AMO extension id', () => {
35333569
const manifest = JSON.parse(fs.readFileSync(path.join(ROOT, 'src/firefox/manifest.json'), 'utf8'));
35343570
assert.equal(manifest.browser_specific_settings?.gecko?.id, 'webbrain@esokullu.com');

0 commit comments

Comments
 (0)