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
4 changes: 0 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
name: CI

on:
pull_request:
workflow_dispatch:

jobs:
Expand Down Expand Up @@ -43,6 +42,3 @@ jobs:

- name: Run compiler linker end-to-end tests
run: npm run test:e2e:compiler

- name: Run Firefox packaging smoke
run: npm run test:browser:firefox
4 changes: 0 additions & 4 deletions .github/workflows/release-on-version-change.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,6 @@ jobs:
if: steps.detect_version.outputs.changed == 'true'
run: npm run test:e2e

- name: Run Firefox packaging smoke
if: steps.detect_version.outputs.changed == 'true'
run: npm run test:browser:firefox

- name: Package release artifacts
if: steps.detect_version.outputs.changed == 'true'
run: npm run package:release
Expand Down
28 changes: 0 additions & 28 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -81,25 +81,6 @@ jobs:
node-version-file: .nvmrc
cache: npm

- name: Validate Firefox signing credentials
env:
AMO_JWT_ISSUER: ${{ secrets.AMO_JWT_ISSUER }}
AMO_JWT_SECRET: ${{ secrets.AMO_JWT_SECRET }}
run: |
missing=0
case "$AMO_JWT_ISSUER" in
(*[![:space:]]*) ;;
(*) echo "::error title=Missing Firefox signing secret::AMO_JWT_ISSUER is not configured for the protected release workflow."; missing=1 ;;
esac
case "$AMO_JWT_SECRET" in
(*[![:space:]]*) ;;
(*) echo "::error title=Missing Firefox signing secret::AMO_JWT_SECRET is not configured for the protected release workflow."; missing=1 ;;
esac
if [ "$missing" -ne 0 ]; then
echo "Configure both protected AMO Actions secrets before rerunning the release."
exit 1
fi

- name: Install dependencies
run: npm ci

Expand All @@ -124,18 +105,9 @@ jobs:
- name: Run end-to-end tests
run: npm run test:e2e

- name: Run Firefox packaging smoke
run: npm run test:browser:firefox

- name: Package release artifacts
run: npm run package:release

- name: Sign Firefox unlisted XPI
env:
AMO_JWT_ISSUER: ${{ secrets.AMO_JWT_ISSUER }}
AMO_JWT_SECRET: ${{ secrets.AMO_JWT_SECRET }}
run: npm run sign:firefox:unlisted

- name: Publish GitHub release assets
env:
GH_TOKEN: ${{ github.token }}
Expand Down
49 changes: 20 additions & 29 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ An in-browser **C++20 IDE** delivered as a Chrome / Chromium extension.
|---------|--------|
| Editor | Monaco Editor (the engine behind VS Code) |
| Compiler | WASM-native Clang (runs entirely in the browser, offline) |
| Terminal | xterm.js with a bash-like shell and live line input on Chromium and Firefox 153+ |
| File access | File System Access API on Chromium, fallback open/save/folder flows on Firefox |
| Terminal | xterm.js with a bash-like shell and live line input on Chromium; Firefox support is deprecated |
| File access | File System Access API on Chromium; Firefox fallback flows are deprecated |
| File I/O | `fstream` / `ifstream` / `ofstream` – read and write workspace files at runtime |
| Standards | C++14 · C++17 · **C++20** (selectable in the toolbar) |

Expand Down Expand Up @@ -239,7 +239,9 @@ Full feature parity is supported for desktop Chrome, Edge, Brave, and Chromium
when the browser is based on Chromium 105 or newer. Latest stable is recommended
for release testing.

Firefox desktop can load the extension, but support remains experimental:
Firefox desktop support is deprecated. Existing Firefox users may still load the
extension while migrating to a Chromium-family browser, but Firefox is not a
supported release or deployment target:

- compile/run, Monaco, and extension-runtime flows are supported
- Firefox 153+ uses WebAssembly JSPI for live, line-buffered `std::cin`,
Expand All @@ -249,8 +251,7 @@ Firefox desktop can load the extension, but support remains experimental:
Chromium File System Access APIs
- persistent folder write-back and directory-handle session restore may be
reduced compared with Chromium-family builds
- public AMO publication is manual; the protected release workflow generates the
Mozilla-signed unlisted XPI for self-distribution
- Firefox deployment, AMO publication, and automated XPI signing are disabled

Full parity requires:

Expand Down Expand Up @@ -290,7 +291,7 @@ npm run test:browser:brave
npm run test:browser:chromium
```

Run the Firefox packaging smoke separately:
Firefox smoke validation is manual-only during the deprecation period:

```bash
npm run test:browser:firefox
Expand Down Expand Up @@ -370,18 +371,17 @@ npm run package:release
This writes:

- `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, 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
- Firefox remains in release metadata as a deprecated, non-publishable target

Chrome, Edge, Brave, and Chromium still share the same MV3 payload. Firefox is
built from `dist-firefox/` as a separate payload because its manifest and
background model differ from Chromium; release distribution uses the signed XPI.
still built from `dist-firefox/` for transition testing because its manifest and
background model differ from Chromium, but no Firefox artifact is deployed.

Store submission notes should state:

Expand All @@ -402,16 +402,13 @@ Use `.github/workflows/release.yml` to publish one GitHub Release per
4. Cleans `dist/` and `release/`
5. Fetches the Clang toolchain
6. Runs lint, build, release validation, and E2E checks
7. Runs Firefox packaging smoke validation
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/`
7. Produces one Chromium-family ZIP plus checksums and release metadata
8. Creates or updates GitHub Release `v<version>` and uploads the maintained artifacts under `release/`

Use `workflow_dispatch` with `force=true` to rebuild and re-upload assets for an
existing release. The workflow does **not** publish directly to browser stores.
Store publication and Chromium distribution remain human-owned steps.
Public AMO publication also remains human-owned even though the unlisted Firefox
XPI is signed automatically during release.
Firefox deployment is deprecated and is not part of this workflow.

### Human-owned deployment instructions

Expand Down Expand Up @@ -471,19 +468,13 @@ Store compatibility plus Brave-specific validation.
There is no official Chromium extension store in this workflow; Chromium is a
manual/GitHub-distributed channel.

#### Firefox

1. Run `npm run test:browser:firefox`.
2. Review `amo/metadata/listed.json` and update it if the release changes
Firefox-facing product behavior or listing copy.
3. For public AMO publication, build the Firefox package from `dist-firefox/`, then upload it with the metadata manually
through the owner-managed listing workflow.
4. For self-distribution, verify that the protected release workflow produced a
signed artifact under `release/firefox-unlisted/`.
5. Install the signed XPI in Firefox and complete the manual QA checklist
in `docs/firefox-stdin-runtime-acceptance.md`, paying special attention to
JSPI live stdin and the documented
workspace-persistence limitations.
#### Firefox (deprecated)

Firefox deployment is suspended. Do not publish to AMO, configure AMO signing
secrets for normal releases, or distribute a Firefox XPI. If migration or
removal work requires a final compatibility check, run
`npm run test:browser:firefox` manually and use `dist-firefox/`; this is not a
release gate.

### Manual release QA checklist

Expand Down
75 changes: 39 additions & 36 deletions docs/release-playbook.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,45 +15,53 @@
- cleans the release workspace
- fetches the Clang WASM toolchain
- runs lint, build, and E2E checks
- runs Firefox packaging smoke checks
- packages one shared Chromium-family release ZIP and target metadata
- uploads the artifacts for review

## Manual Release Flow

- Review the uploaded artifacts and confirm the version bump is intentional.
- For Firefox, confirm the Firefox build and AMO/manual-submission metadata are ready before owner handoff.
- Use the existing tag/manual release workflow for final publication.
- Publish browser store listings and verify installed updates as required by the target browser.
- The protected release workflow signs the Firefox unlisted XPI with AMO credentials and uploads it with the other release assets.
- Firefox support and deployment are deprecated; it is not included in release artifacts or release gates.

### Firefox unlisted-signing credentials
### Firefox deprecation notice

The protected release workflow requires two GitHub Actions secrets before it
starts the release build:
**Status:** Deprecated as of 2026-09-05

- `AMO_JWT_ISSUER`
- `AMO_JWT_SECRET`
**Replacement:** Chrome, Edge, Brave, or Chromium builds

Create the AMO API credential pair in Mozilla Add-ons, then store the values as
repository or protected release-environment secrets with these exact names.
Keep them unavailable to pull-request workflows, do not put them in source,
local release artifacts, or logs, and grant only the permissions required for
Firefox signing. The workflow checks only that each value is present and
non-blank; it never prints either value.
**Removal date:** Advisory; Firefox runtime/build code remains temporarily while
existing users migrate and ownership is assessed.

Rotate both secrets through Mozilla and GitHub when the credential expires or
is suspected to be exposed. After updating them, use
`workflow_dispatch` with `force=true` to rerun the protected release. The
workflow must fail before dependency installation when either secret is absent;
do not bypass signing or publish an unsigned XPI. If the preflight passes but
signing fails, inspect the protected workflow's AMO/web-ext error, correct the
credential or AMO configuration, and rerun the forced release.
Firefox is no longer a supported deployment target. Do not configure AMO
credentials for the normal release workflow, publish a Firefox package, or
expect a signed XPI in GitHub Releases. The existing Firefox build and signing
commands are transition tooling only and are not automated release steps.

## Firefox Verification Test Plan
If a maintainer needs a final compatibility check during migration, run
`npm run test:browser:firefox` manually. It is not a CI or release gate.

Use this plan before declaring Firefox support release-ready or bumping the
project version for a Firefox-supporting release.
The future removal follow-up must verify active Firefox usage and ownership,
provide migration guidance, then remove the Firefox runtime/build paths, tests,
signing tools, and documentation together.

### General CI

The general CI workflow is manual-only (`workflow_dispatch`) during this
transition. It does not run automatically on pull requests and does not run the
deprecated Firefox smoke test. Run it manually when validating a branch.

### Deprecated Firefox signing credentials

No AMO credentials are required by the normal release workflow. If the
transition tooling is used manually, keep any credentials outside the repository
and never expose them in pull-request workflows, source, artifacts, or logs.

## Deprecated Firefox Verification Test Plan

Use this only for migration or eventual removal work. It does not qualify
Firefox for release.

### Automated gates

Expand All @@ -67,13 +75,10 @@ Run these commands from a clean checkout in order:
6. `npm run test:preflight-clang`
7. `npm run version:check`
8. `npm run release:check-version`
9. `npm run test:browser:firefox`
10. `npm run package:release`
9. `npm run package:release`

Passing these gates proves that:

- Firefox-specific manifest generation succeeds
- the Firefox extension package passes `web-ext` lint/build smoke
- 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

Expand All @@ -97,27 +102,25 @@ real Firefox desktop build:
persistence behavior matches reality.
8. Restart Firefox and verify session/workspace restore behavior matches the
documented limitations.
9. Install the signed Firefox XPI and repeat the compile/run sanity check.
9. Do not install or distribute a signed Firefox XPI; deployment is deprecated.

### Release decision

The Firefox release gate passes only when:
The deprecated Firefox migration check passes only when:

- every automated gate above succeeds
- every applicable automated gate above succeeds
- manual Firefox runtime QA succeeds
- no new Firefox-only regressions are found in startup, compile/run, file
flows, or packaging
- no migration-blocking Firefox regressions are found in startup, compile/run,
or file flows
- remaining Firefox limitations are already documented and match observed
behavior

Do **not** cut the version bump for a Firefox-supporting release if only the
packaging smoke passes. Runtime validation in Firefox is still required.
Do **not** present this validation as evidence that Firefox is release-supported.

## Validation

- `npm run lint`
- `npm run build`
- `npm run test:e2e`
- `npm run test:browser:firefox`
- `npm run release:check-version`
- `npm run package:release`
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.7",
"version": "0.4.8",
"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.7",
"version": "0.4.8",
"description": "In-browser C++20 IDE with WASM Clang toolchain",
"private": true,
"scripts": {
Expand Down
32 changes: 29 additions & 3 deletions scripts/e2e-release-packaging.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,20 @@ const releaseWorkflowPath = path.join(
'workflows',
'release.yml'
);
const ciWorkflowPath = path.join(
path.dirname(fileURLToPath(import.meta.url)),
'..',
'.github',
'workflows',
'ci.yml'
);
const releaseCandidateWorkflowPath = path.join(
path.dirname(fileURLToPath(import.meta.url)),
'..',
'.github',
'workflows',
'release-on-version-change.yml'
);

function writeJson(filePath, value) {
fs.mkdirSync(path.dirname(filePath), { recursive: true });
Expand Down Expand Up @@ -124,6 +138,17 @@ test('e2e: release workflow uploads files from nested release directories', () =
assert.doesNotMatch(workflow, /gh release (?:upload|create)[\s\S]*release\/\*/);
});

test('e2e: deprecated Firefox is excluded from automated release workflows', () => {
const releaseWorkflow = fs.readFileSync(releaseWorkflowPath, 'utf8');
const releaseCandidateWorkflow = fs.readFileSync(releaseCandidateWorkflowPath, 'utf8');
const ciWorkflow = fs.readFileSync(ciWorkflowPath, 'utf8');

assert.doesNotMatch(releaseWorkflow, /AMO_JWT_|sign:firefox|test:browser:firefox/);
assert.doesNotMatch(releaseCandidateWorkflow, /test:browser:firefox/);
assert.match(ciWorkflow, /on:\n {2}workflow_dispatch:/);
assert.doesNotMatch(ciWorkflow, /pull_request:|test:browser:firefox/);
});

test('e2e: release version sync fails on source manifest mismatch', () => {
const repoRoot = makeRepoFixture();
writeJson(path.join(repoRoot, 'manifest.json'), {
Expand Down Expand Up @@ -255,14 +280,15 @@ test('e2e: release packaging creates one Chromium-family artifact and target map

const firefoxTarget = manifest.targets.find((target) => target.target === 'firefox');
assert.equal(firefoxTarget.publishable, false);
assert.equal(firefoxTarget.deprecated, true);
assert.equal(firefoxTarget.packageStrategy, 'distinct');
assert.equal(firefoxTarget.fileName, null);
assert.equal(
firefoxTarget.blockReason,
'Firefox unsigned ZIP generation is disabled; use the signed unlisted XPI.'
'Firefox support and deployment are deprecated; no release artifact is generated.'
);
assert.equal(firefoxTarget.signing.listed, 'manual-owner-submission');
assert.equal(firefoxTarget.signing.unlisted, 'required-release-artifact');
assert.equal(firefoxTarget.signing.listed, 'deprecated');
assert.equal(firefoxTarget.signing.unlisted, 'deprecated');

const chromiumTargets = ['chrome', 'edge', 'brave', 'chromium'];
for (const targetKey of chromiumTargets) {
Expand Down
Loading
Loading