Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 8 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ npm run test:browser:firefox
```

For release candidates triggered by a `manifest.json` version bump, GitHub Actions
uploads browser-labeled release artifacts for same-repo pull requests only. The
uploads the shared Chromium-family release artifact for same-repo pull requests only. The
release workflow uses `manifest.json` as the version source of truth and expects
`package.json` to stay in sync with that value. See `docs/release-playbook.md`
for the full release flow and the remaining human-owned publish steps.
Expand Down Expand Up @@ -337,26 +337,22 @@ check in every target browser:

### Release packages

Create browser-labeled release ZIPs from the built `dist/` contents:
Create the shared Chromium-family release ZIP from the built `dist/` contents:

```bash
npm run package:release
```

This writes:

- `release/browser-cpp-chrome-v<version>.zip` for the Chrome Web Store listing
- `release/browser-cpp-edge-v<version>.zip` for Microsoft Edge Add-ons
- `release/browser-cpp-brave-v<version>.zip` for Brave validation/distribution
- `release/browser-cpp-chromium-v<version>.zip` for Chromium/GitHub distribution
- `release/browser-cpp-chromium-family-v<version>.zip` for Chrome, Edge, Brave, and Chromium
- `release/firefox-unlisted/*.xpi` after the protected release workflow signs the Firefox unlisted build
- `release/SHA256SUMS-v<version>.txt`
- `release/release-manifest-v<version>.json`

The release manifest tracks the browser package matrix:

- Chrome is the canonical Chromium-family payload
- Edge, Brave, and Chromium currently reuse that payload under browser-labeled filenames
- Chrome, Edge, Brave, and Chromium map to the same Chromium-family ZIP
- Firefox has its own manifest, background entry, smoke-tested temporary package, and signing metadata

Chrome, Edge, Brave, and Chromium still share the same MV3 payload. Firefox is
Expand All @@ -383,7 +379,7 @@ Use `.github/workflows/release.yml` to publish one GitHub Release per
5. Fetches the Clang toolchain
6. Runs lint, build, release validation, and E2E checks
7. Runs Firefox packaging smoke validation
8. Produces the Chromium-family ZIPs plus checksums and release metadata
8. Produces one Chromium-family ZIP plus checksums and release metadata
9. Signs the Firefox unlisted XPI with protected AMO credentials
10. Creates or updates GitHub Release `v<version>` and uploads all files under `release/`

Expand All @@ -403,7 +399,7 @@ assets, published GitHub Release `v<version>`.

1. Verify you still have access to the existing Chrome Web Store item for
browser.cpp.
2. Upload `release/browser-cpp-chrome-v<version>.zip`.
2. Upload `release/browser-cpp-chromium-family-v<version>.zip`.
3. Update listing copy, screenshots, privacy details, and reviewer notes if the
release changes user-visible behavior, permissions, or file-access guidance.
4. Confirm the listing still describes the extension as a local-only WASI/WebAssembly
Expand All @@ -417,7 +413,7 @@ assets, published GitHub Release `v<version>`.
1. Verify the Microsoft Partner Center account is active, or create it before
the first Edge release.
2. Create the Edge Add-ons listing if one does not exist yet.
3. Upload `release/browser-cpp-edge-v<version>.zip`.
3. Upload `release/browser-cpp-chromium-family-v<version>.zip`.
4. Complete the store listing fields, availability/market settings, privacy
links, and any certification notes requested by Partner Center.
5. Submit for certification and respond to reviewer feedback.
Expand All @@ -440,7 +436,7 @@ Store compatibility plus Brave-specific validation.
#### Chromium

1. Verify that GitHub Release `v<version>` includes:
- `release/browser-cpp-chromium-v<version>.zip`
- `release/browser-cpp-chromium-family-v<version>.zip`
- `release/SHA256SUMS-v<version>.txt`
- `release/release-manifest-v<version>.json`
2. Publish manual installation instructions for Chromium users, including that
Expand Down
4 changes: 2 additions & 2 deletions docs/release-playbook.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
- fetches the Clang WASM toolchain
- runs lint, build, and E2E checks
- runs Firefox packaging smoke checks
- packages browser-labeled release artifacts
- packages one shared Chromium-family release ZIP and target metadata
- uploads the artifacts for review

## Manual Release Flow
Expand Down Expand Up @@ -74,7 +74,7 @@ Passing these gates proves that:

- Firefox-specific manifest generation succeeds
- the Firefox extension package passes `web-ext` lint/build smoke
- release packaging emits the Chromium-family artifacts and release manifest
- release packaging emits one Chromium-family ZIP and a release manifest that maps Chrome, Edge, Brave, and Chromium to it
- manifest/package metadata stay version-synchronized

### Manual Firefox runtime QA
Expand Down
2 changes: 1 addition & 1 deletion manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"name": "browser.cpp",
"short_name": "browser.cpp",
"description": "In-browser C++20 IDE powered by Monaco Editor and WASM Clang",
"version": "0.4.3",
"version": "0.4.4",
"minimum_chrome_version": "105",
"icons": {
"16": "icons/icon16.png",
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "browser.cpp",
"version": "0.4.3",
"version": "0.4.4",
"description": "In-browser C++20 IDE with WASM Clang toolchain",
"private": true,
"scripts": {
Expand Down
48 changes: 36 additions & 12 deletions scripts/e2e-release-packaging.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const { synchronizeVersionFromManifest } = require('./sync-version-from-manifest
const { validateReleaseVersionSync } = require('./check-release-version-sync.js');
const { cleanReleaseWorkspace } = require('./clean-release-workspace.js');
const {
getReleaseArtifactTargets,
getPublishableReleaseTargets,
getReleaseTarget,
getReleaseTargets,
Expand Down Expand Up @@ -224,18 +225,19 @@ test('e2e: release packaging removes stale Firefox ZIP artifacts', () => {
);
});

test('e2e: release packaging creates Chromium-family browser artifacts and metadata', () => {
test('e2e: release packaging creates one Chromium-family artifact and target mappings', () => {
const repoRoot = makeRepoFixture();
const result = createReleaseArtifacts({ repoRoot });

const publishableTargets = getPublishableReleaseTargets();
assert.equal(result.artifacts.length, publishableTargets.length);
const artifactTargets = getReleaseArtifactTargets();
assert.equal(result.artifacts.length, artifactTargets.length);
assert.equal(result.artifacts.length, 1);

const manifest = JSON.parse(fs.readFileSync(result.releaseManifestPath, 'utf8'));
assert.equal(manifest.version, '1.2.3');
assert.deepEqual(
manifest.artifacts.map((artifact) => artifact.target),
publishableTargets.map((target) => target.key)
artifactTargets.map((target) => target.key)
);
assert.deepEqual(
manifest.targets.map((target) => target.target),
Expand All @@ -246,7 +248,10 @@ test('e2e: release packaging creates Chromium-family browser artifacts and metad
.readFileSync(result.checksumPath, 'utf8')
.trim()
.split('\n');
assert.equal(checksumLines.length, publishableTargets.length);
assert.equal(checksumLines.length, artifactTargets.length);
assert.deepEqual(checksumLines, [
`${manifest.artifacts[0].sha256} browser-cpp-chromium-family-v1.2.3.zip`,
]);

const firefoxTarget = manifest.targets.find((target) => target.target === 'firefox');
assert.equal(firefoxTarget.publishable, false);
Expand All @@ -259,18 +264,29 @@ test('e2e: release packaging creates Chromium-family browser artifacts and metad
assert.equal(firefoxTarget.signing.listed, 'manual-owner-submission');
assert.equal(firefoxTarget.signing.unlisted, 'required-release-artifact');

const edgeArtifact = manifest.artifacts.find((artifact) => artifact.target === 'edge');
assert.equal(edgeArtifact.packageStrategy, 'shared-with:chrome');
assert.equal(edgeArtifact.payloadGroup, 'chromium-mv3');
const chromiumTargets = ['chrome', 'edge', 'brave', 'chromium'];
for (const targetKey of chromiumTargets) {
const target = manifest.targets.find((item) => item.target === targetKey);
assert.equal(target.artifactKey, 'chromium-family');
assert.equal(target.fileName, 'browser-cpp-chromium-family-v1.2.3.zip');
}

assert.equal(
manifest.artifacts.some((artifact) => artifact.target === 'firefox'),
false
);

for (const artifact of result.artifacts) {
assert.equal(fs.existsSync(artifact.filePath), true);
assert.match(artifact.fileName, /^browser-cpp-(chrome|edge|brave|chromium)-v1\.2\.3\.zip$/);
const [artifact] = result.artifacts;
assert.equal(fs.existsSync(artifact.filePath), true);
assert.equal(artifact.fileName, 'browser-cpp-chromium-family-v1.2.3.zip');
assert.equal(artifact.artifactKey, 'chromium-family');
assert.equal(artifact.compatibleTargets.join(','), chromiumTargets.join(','));

for (const targetKey of chromiumTargets) {
assert.equal(
fs.existsSync(path.join(repoRoot, 'release', `browser-cpp-${targetKey}-v1.2.3.zip`)),
false
);
}
});

Expand All @@ -288,7 +304,7 @@ test('e2e: release packaging refuses mismatched built manifest versions', () =>
);
});

test('e2e: release-target metadata includes Firefox and shared Chromium payloads', () => {
test('e2e: release-target metadata maps Chromium targets to one artifact and preserves Firefox', () => {
assert.deepEqual(
TARGETS.map((target) => target.key),
['chrome', 'edge', 'firefox', 'brave', 'chromium']
Expand All @@ -304,6 +320,14 @@ test('e2e: release-target metadata includes Firefox and shared Chromium payloads
publishableTargets.map((target) => target.key),
['chrome', 'edge', 'brave', 'chromium']
);

assert.deepEqual(
getReleaseArtifactTargets().map((target) => target.key),
['chromium']
);
for (const target of publishableTargets) {
assert.equal(target.artifactKey, 'chromium-family');
}
});

test('e2e: manifest version change detection reports same-repo version bumps', () => {
Expand Down
22 changes: 21 additions & 1 deletion scripts/package-extension-release.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const { validateReleaseVersionSync } = require('./check-release-version-sync');
const { BASE_URL, FILES } = require('./fetch-clang-wasm');
const {
getArtifactFileName,
getReleaseArtifactTargets,
getPublishableReleaseTargets,
getReleaseTargets,
} = require('./release-targets');
Expand Down Expand Up @@ -172,6 +173,16 @@ function removeStaleFirefoxZipArtifacts(releaseDir) {
}
}

function removeStaleChromiumZipArtifacts(releaseDir) {
if (!fs.existsSync(releaseDir)) return;

for (const name of fs.readdirSync(releaseDir)) {
if (!/^browser-cpp-(?:chrome|edge|brave|chromium|chromium-family)-v.+\.zip$/.test(name)) continue;
const filePath = path.join(releaseDir, name);
if (fs.statSync(filePath).isFile()) fs.unlinkSync(filePath);
}
}

function createReleaseArtifacts(options = {}) {
const repoRoot = options.repoRoot || path.resolve(__dirname, '..');
const distDir = options.distDir || path.join(repoRoot, 'dist');
Expand All @@ -183,6 +194,7 @@ function createReleaseArtifacts(options = {}) {
releaseTag: options.releaseTag,
});
const publishableTargets = getPublishableReleaseTargets();
const artifactTargets = getReleaseArtifactTargets();
const payloadCache = new Map();
const payloadSourceDirs = new Map([
['chromium-mv3', distDir],
Expand All @@ -191,8 +203,9 @@ function createReleaseArtifacts(options = {}) {

fs.mkdirSync(releaseDir, { recursive: true });
removeStaleFirefoxZipArtifacts(releaseDir);
removeStaleChromiumZipArtifacts(releaseDir);

const artifacts = publishableTargets.map((target) => {
const artifacts = artifactTargets.map((target) => {
const payloadGroup = target.payloadGroup;
const sourceDir = payloadSourceDirs.get(payloadGroup);
if (!sourceDir) {
Expand Down Expand Up @@ -225,6 +238,7 @@ function createReleaseArtifacts(options = {}) {
channel: target.channel,
notes: target.notes,
packageStrategy: target.packageStrategy,
artifactKey: target.artifactKey,
payloadGroup: target.payloadGroup,
signing: target.signing || null,
fileName,
Expand All @@ -233,6 +247,9 @@ function createReleaseArtifacts(options = {}) {
bytes: payload.zipBuffer.length,
sha256: payload.sha256,
sharedPayload: publishableTargets.filter((item) => item.payloadGroup === payloadGroup).length > 1,
compatibleTargets: publishableTargets
.filter((item) => item.artifactKey === target.artifactKey)
.map((item) => item.key),
sourceDir: path.relative(repoRoot, payload.sourceDir),
};
});
Expand Down Expand Up @@ -273,6 +290,7 @@ function createReleaseArtifacts(options = {}) {
label: target.label,
channel: target.channel,
packageStrategy: target.packageStrategy,
artifactKey: target.artifactKey,
payloadGroup: target.payloadGroup,
publishable: target.publishable,
blockReason: target.blockReason || null,
Expand All @@ -286,6 +304,7 @@ function createReleaseArtifacts(options = {}) {
channel: artifact.channel,
notes: artifact.notes,
packageStrategy: artifact.packageStrategy,
artifactKey: artifact.artifactKey,
payloadGroup: artifact.payloadGroup,
signing: artifact.signing,
fileName: artifact.fileName,
Expand All @@ -294,6 +313,7 @@ function createReleaseArtifacts(options = {}) {
bytes: artifact.bytes,
sha256: artifact.sha256,
sharedPayload: artifact.sharedPayload,
compatibleTargets: artifact.compatibleTargets,
})),
};
fs.writeFileSync(releaseManifestPath, `${JSON.stringify(releaseManifest, null, 2)}\n`, 'utf8');
Expand Down
Loading
Loading