v1: freeze the API surface, move to Biome, update every dependency - #77
Merged
Merged
Conversation
Broaden CSS support from the fixed 'tailwind' | 'vanilla' | 'none' enum to an extensible pipeline any styling toolchain can plug into. Core: - New src/core/builder/css.ts defines CssProcessor / CssTransform / CssTransformContext and a processStylesheet pipeline (base processor → onCssTransform plugin chain → write). Commands run without a shell and fall back to a copy on failure. - config.css now accepts a custom processor object (inline transform or CLI command) in addition to the three presets; schema validated. - New onCssTransform plugin hook (types, runner, fireCssTransform). CSS types re-exported from both extforge and extforge/plugins. - Presets behave correctly: 'vanilla'/'none' copy through instead of invoking the Tailwind CLI when it happens to be installed. - Scaffold: non-tailwind css no longer ships @tailwind directives in globals.css (new globals.vanilla.css.tpl). Docs: - New Styling guide; updated configuration + plugin guides, README, index, and the auto-generated config reference (now handles union types and safely quotes frontmatter descriptions). Tests: css pipeline + onCssTransform chain + schema cases (519 passing). https://claude.ai/code/session_01NxAV3rCRLn29a7JZSkRACq
Toolchain - Swap ESLint (flat config + typescript-eslint) and Prettier for Biome 2.5.11, which handles both linting and formatting in one pass. Every ESLint suppression is ported to its Biome equivalent with the original rationale preserved; the `no-console` whitelist survives as per-file overrides in biome.json. - Rules kept deliberately in line with the previous config: noExplicitAny warns (as before), noNonNullAssertion and noConfusingVoidType are off because the codebase uses both idioms on purpose. - Formatter settings match the code as written (single quotes, semicolons, 2-space indent, 100-column width), so the reformat is mechanical. Genuine findings fixed along the way - Storage's private `useChrome()` renamed to `hasChromeStorage()`. It is not a React hook; only its name made it look like one. - `getLogger()` and the SWC refresh-plugin template getters no longer memoize via assignment-in-expression. - `forEach` callbacks in tests no longer return a value. - Dropped an unused `Browser` import and an unused loop binding in tests/manifest.test.ts, and a void-returning `return` in the config reference generator. - The `defaultValue`-excluded-from-deps note in storage/react.ts becomes a real suppression now that the rule is actually enforced; ESLint never had the react-hooks plugin installed. Dependencies - Every npm dependency moved to its latest release across the root package, docs-site, tests-e2e and the examples. This supersedes the open dependabot PRs (#49-#54, #62-#64, #69-#76). - TypeScript stays on 6.0.3. TypeScript 7 typechecks the project cleanly but breaks the .d.ts bundle: tsup's rollup-plugin-dts binds to the TypeScript compiler API, which the Go-based TS 7 does not expose. - Peer dependency ranges are untouched. They are compatibility floors for consumers, not versions to keep current. - Starlight 0.39 removed the `{ label, autogenerate }` sidebar shorthand; docs-site/astro.config.mjs migrates to nested `items`. - New pnpm overrides pin postcss, nanoid, js-yaml and svgo to patched versions. Astro/Starlight pull vulnerable copies transitively, which would otherwise fail the `pnpm audit --prod` CI gate. CI - All SHA-pinned actions bumped: actions/checkout v7.0.0, actions/setup-node v6.4.0, github/codeql-action v4.36.2, and pnpm/action-setup to its current v4.4.0 commit. - dependabot.yml drops the eslint/typescript-eslint groups for a biome one. Verified: typecheck, lint, 519 tests, tsup build, docs build, and `pnpm audit --prod` (0 vulnerabilities) all pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AXZCPnbtT51ASiFHLMXQTZ
Closes the remaining pre-v1 gates from #35. API freeze - New reference page assigns every export, command, flag, config field and runtime global to a tier: Stable, Experimental, or Internal. If it isn't listed there, it's internal. - Tagged the symbols that are exported only because the CLI needs them across module boundaries: createBuildContext, classifyChange, generateHMRClientCode, writeManifest, and PluginRunner. Each `@internal` tag says what to use instead. - Answers the three open questions from the issue: - `extforge/testing` ships **experimental**. The Chrome fakes mirror a surface MV3 keeps extending, so freezing them would mean either an incomplete mock forever or a major release per Chrome API. They're also the lowest-blast-radius thing to change — a test suite breaks, not a shipped extension. - HMR globals split. `__EXTFORGE_HMR_QUIET__` is a documented user opt-out, so it's stable. `__EXTFORGE_HMR__` is the binding target the dev-mode transform emits against and moves with the wire protocol, so it's internal. - `extforge/plugins` is **stable**. It already carries an `apiVersion: 1` discriminator, so an incompatible future plugin API can ship as `apiVersion: 2` alongside rather than breaking existing plugins. - Node policy written down: >=22.12, supported through Active and Maintenance LTS, and dropping an EOL major is a minor release — holding the floor down until the next major would mean shipping against an unpatched runtime. Cross-browser gate - `pnpm check:cross-browser` builds all three examples for Chrome, Firefox, Edge and Safari and asserts each emitted manifest is well-formed and carries that browser's shape (Firefox gets `background.scripts` and `browser_specific_settings`; the others get `service_worker` and no addon id). All 12 combinations pass. - Runs as its own CI job. This is a build gate, not a behavioural one: live-browser e2e is still Chromium-only. Docs and changelog - Migration guide for 0.x → 1.0. There is nothing to codemod, so it leads with that and covers what actually bites: the Node 22.12 floor and the logger's number formatting, both from 0.6.0. - Removed the stale `[Unreleased]` section. Everything in it — HMR v3, SWC + React Fast Refresh, content-script HMR scaffolding, centralized logging — shipped in 0.3.0 and 0.4.0, so it was claiming released work was pending. Verified: typecheck, lint, 519 tests, build, docs build, `pnpm audit --prod` (0 vulnerabilities) and the new cross-browser check all pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AXZCPnbtT51ASiFHLMXQTZ
…ation
CodeQL flagged three high-severity "incomplete string escaping" alerts in
the plugin reference generator, and it is right.
`escapeType` and `escapeDoc` added backslashes without escaping backslashes
first. Given input that already contains one, the output is wrong:
escapeType('a\|b') -> 'a\\|b'
Markdown reads `\\` as a literal backslash, which leaves the `|` as an
unescaped cell delimiter and breaks the table row. For `escapeDoc` the
failure is worse: `a\{b` emits `a\\{b`, and an unescaped `{` in MDX opens a
JSX expression, so the docs build fails outright.
Escaping backslashes first fixes both. Nothing in the current plugin types
or JSDoc contains a backslash, so the generated output is byte-identical —
verified by diffing docs-site/src/content/docs/reference/plugins before and
after. This is hardening against the input that would break it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AXZCPnbtT51ASiFHLMXQTZ
Deploying extforge with
|
| Latest commit: |
a4e4c6f
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://f9e90b1f.extforge.pages.dev |
| Branch Preview URL: | https://claude-issue-35-v1-readiness.extforge.pages.dev |
Picks up #67, which squash-merged to main as 2bda0bc. This branch already contained that work (commit 34955a9, merged here earlier), so the squash produced conflicts against the Biome-formatted copies rather than any real divergence. Verified `git diff 34955a9 origin/main` is empty before resolving — main carries nothing this branch lacks — so every conflict was resolved in favour of this branch, whose files are the same content with Biome's import ordering and the new @internal tags on top. Git auto-merged src/core/plugins/index.ts without flagging a conflict and duplicated the CSS type re-export block, which broke typecheck with TS2300. Deduped. The merged tree is byte-identical to the pre-merge head (594213a), which is the expected invariant given main added nothing new. Verified: typecheck, lint, 519 tests, build, docs build, cross-browser check, and `pnpm audit --prod` all pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AXZCPnbtT51ASiFHLMXQTZ
CodeQL still flagged escapeType/escapeDoc after the previous commit. The
two-pass form was functionally correct — it escaped backslashes before
delimiters — but the backslash pass lived in a separate helper, and
js/incomplete-sanitization does not follow the escaping across that call, so
it still read each `.replace(/\|/g, ...)` as escaping without handling
backslashes.
Folding the backslash into the character class fixes it for both readers.
One pass over `[\\|]` (and `[\\{}]`) escapes the backslash alongside the
delimiter, so there is no ordering to get wrong and no cross-function
dataflow for the query to lose track of.
Verified equivalent to the two-pass version across plain input, bare
backslashes, bare delimiters, backslash-delimiter pairs and doubled
backslashes. Generated output under docs-site reference/plugins is
byte-identical again.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AXZCPnbtT51ASiFHLMXQTZ
arshad-shah
pushed a commit
that referenced
this pull request
Sep 1, 2026
This was referenced Sep 1, 2026
arshad-shah
added a commit
that referenced
this pull request
Sep 1, 2026
The 1.0.0 and 1.1.0 releases published to npm but created no git tag and no GitHub release, while the workflow reported success. changesets/action v1 detects what was published by regex-matching `changeset publish` stdout for `New tag: <pkg>@<version>`. #77 upgraded @changesets/cli to v3, whose clack-style output no longer prints that line, so the action concluded nothing had been published and skipped both the tag push and the release creation without failing. Tags were created on the runner and discarded with it — which is why `git ls-remote --tags` still stops at 0.6.0. Upstream replaced stdout parsing with a CHANGESETS_OUTPUT file in v2 (changesets/action#678) and v2 now rejects the CLI v2/action v1 mismatch outright (changesets/action#699). v2 renamed every input, so this is not a bare SHA bump: version -> version-script, publish -> publish-script, commit -> commit-message, title -> pr-title. Both env vars are dropped. GITHUB_TOKEN is no longer read from the environment (changesets/action#674); the `github-token` input defaults to `github.token`, which is what was being passed. NPM_TOKEN is no longer used to write an .npmrc (changesets/action#695) — publishing already goes through OIDC trusted publishing, confirmed by the SLSA provenance attestation on the published 1.1.0 tarball. v2 pushes commits and tags through the GitHub API rather than the git CLI, so `persist-credentials: false` stays safe and tags are signed with GitHub's GPG key. Claude-Session: https://claude.ai/code/session_01AZSFjNFjuoeXUjuFkA6Cha Co-authored-by: Arshad shah <arshad.shah@hmhco.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes the remaining pre-v1 gates from #35 and brings the whole dependency tree to latest, so
1.0.0can ship off a clean build. Adds amajorchangeset — there are no breaking changes from0.6.x; what v1 adds is the promise that the documented surface stays put.Type of change
Linked issues
Closes #35. Supersedes #44, and the dependabot PRs #49, #50, #51, #52, #53, #54, #62, #63, #64, #69, #70, #71, #72, #73, #74, #75, #76.
Contains the commit from #67 — merge that one first and this fast-forwards over it cleanly.
Implementation notes
API freeze
New API stability page assigns every export, command, flag, config field and runtime global to a tier: Stable, Experimental, or Internal. If it isn't listed there, it's internal. Symbols exported only because the CLI needs them across module boundaries (
createBuildContext,classifyChange,generateHMRClientCode,writeManifest,PluginRunner) now carry@internaltags that say what to use instead.The issue's three open questions, answered:
extforge/testingships experimental. The Chrome fakes mirror a surface MV3 keeps extending, so freezing them means either an incomplete mock forever or a major release every time Chrome adds an API. They're also the lowest-blast-radius thing to change — a fake that shifts breaks a test suite, not a shipped extension.__EXTFORGE_HMR_QUIET__is a documented user opt-out, so it's stable.__EXTFORGE_HMR__is the binding target the dev-mode transform emits against and moves with the wire protocol, so it's internal.extforge/pluginsis stable. It already carries anapiVersion: 1discriminator, so an incompatible future plugin API can ship asapiVersion: 2alongside rather than breaking existing plugins.Node policy is written down too:
>=22.12, supported through Active and Maintenance LTS, and dropping an EOL major is a minor release — holding the floor down until the next major would mean shipping against an unpatched runtime.ESLint + Prettier → Biome
One tool for both linting and formatting. Every ESLint suppression is ported to its Biome equivalent with the original rationale intact; the
no-consolewhitelist survives as per-file overrides inbiome.json. Rule levels are deliberately kept in line with the old config (noExplicitAnywarns;noNonNullAssertionandnoConfusingVoidTypeare off, because the codebase uses both idioms on purpose), so the result is 0 errors and the same 59anywarnings as before rather than a new wall of noise.Biome's stricter preset did surface real things, fixed here rather than silenced:
useChrome()→hasChromeStorage(). It is not a React hook; only its name madeuseHookAtTopLevelthink so.getLogger()and the SWC refresh-plugin getters no longer memoize via assignment-in-expression.forEachcallbacks in tests no longer return a value.tests/manifest.test.ts, and a void-returningreturnin the config reference generator.defaultValuefrom deps" note instorage/react.tsbecomes a real suppression — ESLint never had the react-hooks plugin installed, so that rule was never actually enforced.Dependencies
Every npm dependency across the root package,
docs-site,tests-e2eand the examples is on its latest release, plus all SHA-pinned actions (checkout v7, setup-node v6.4, codeql v4.36.2, pnpm/action-setup).Two judgment calls worth a look:
.d.tsbundle: tsup'srollup-plugin-dtsbinds to the TypeScript compiler API, which the Go-based TS 7 doesn't expose.typescript-eslintalso caps at<6.1.0, though Biome makes that moot.react >=18.0.0to^19would drop React 18 users for no reason.Astro 6 → 7 needed a Starlight migration: 0.39 removed the
{ label, autogenerate }sidebar shorthand, so those entries are nested underitemsnow. Astro/Starlight also pull vulnerablepostcss,nanoid,js-yamlandsvgotransitively, which would fail thepnpm audit --prodgate — newpnpm.overridespin them to patched versions, following the pattern already used foresbuildanddevalue.Cross-browser gate
pnpm check:cross-browserbuilds all three examples for Chrome, Firefox, Edge and Safari and asserts each emitted manifest is well-formed and carries that browser's shape (Firefox getsbackground.scripts+browser_specific_settings; the others getservice_workerand no addon id). All 12 combinations pass, and it runs as its own CI job.This is a build gate, not a behavioural one. Live-browser e2e is still Chromium-only — see below.
Changelog
Removed the stale
[Unreleased]section. Everything in it (HMR v3, SWC + React Fast Refresh, content-script HMR scaffolding, centralized logging) shipped in0.3.0and0.4.0, so it was advertising released work as pending.Testing
pnpm typecheck && pnpm lint && pnpm test && pnpm buildpasses locally — 519 tests, 57 filespnpm audit --prodreports 0 vulnerabilitiespnpm docs:buildpasses on Astro 7 / Starlight 0.41 — 50 pagespnpm check:cross-browserpasses — 3 examples × 4 targetscdn.playwright.devis blocked by the environment's network policy). CI installs it, so the e2e job will be the first real signal. The jump is1.59.1→1.62.1, so the risk is low, but it is unverified.Documentation
reference/stability.mdxandguides/migration-v1.mdx, both wired into the sidebarpnpm docs:genre-run as part ofdocs:build@internalTSDoc to the newly-classified internal surfacemajorchangesetBreaking change notes
None. No config field was renamed, no export removed, no CLI flag changed between
0.6.xand1.0.0. The migration guide leads with exactly that, then covers what does bite people skipping releases: the Node 22.12 floor and the logger's number formatting, both from0.6.0.The one contributor-facing change is
pnpm lintnow running Biome instead of ESLint.CONTRIBUTING.mdis updated.Checklist
src/core/—@biomejs/biomeis a devDependencyCONTRIBUTING.mdStill open after this
Two gates from #35 aren't closed by code, flagged rather than quietly dropped:
fix-*changesets against core". A process gate;0.6.0shipped with zero pending changesets, so the clock is running, but it's your call whether it's served.Both nice-to-haves (performance baseline, bundle size budget) are also still open.
Generated by Claude Code