@hominis/fireforge 0.30.1 → 0.32.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +36 -0
- package/README.md +22 -0
- package/dist/src/commands/export-all.js +9 -16
- package/dist/src/commands/export-flow.d.ts +6 -0
- package/dist/src/commands/export-flow.js +6 -1
- package/dist/src/commands/export-placement-gate.d.ts +38 -0
- package/dist/src/commands/export-placement-gate.js +105 -0
- package/dist/src/commands/export-shared.d.ts +28 -0
- package/dist/src/commands/export-shared.js +46 -1
- package/dist/src/commands/export.js +52 -113
- package/dist/src/commands/furnace/chrome-doc-templates.d.ts +0 -13
- package/dist/src/commands/furnace/chrome-doc-templates.js +1 -1
- package/dist/src/commands/furnace/create-dry-run.d.ts +1 -1
- package/dist/src/commands/furnace/create.d.ts +1 -2
- package/dist/src/commands/furnace/deploy.js +36 -114
- package/dist/src/commands/furnace/refresh.js +52 -32
- package/dist/src/commands/furnace/sync.js +2 -0
- package/dist/src/commands/import.js +108 -73
- package/dist/src/commands/lint-per-patch.d.ts +3 -1
- package/dist/src/commands/lint-per-patch.js +265 -74
- package/dist/src/commands/lint.d.ts +1 -58
- package/dist/src/commands/lint.js +193 -88
- package/dist/src/commands/patch/compact.d.ts +5 -2
- package/dist/src/commands/patch/compact.js +85 -25
- package/dist/src/commands/patch/delete.js +17 -17
- package/dist/src/commands/patch/index.js +2 -0
- package/dist/src/commands/patch/lint-ignore.js +3 -16
- package/dist/src/commands/patch/move-files.js +2 -0
- package/dist/src/commands/patch/patch-context.d.ts +41 -0
- package/dist/src/commands/patch/patch-context.js +53 -0
- package/dist/src/commands/patch/rename.js +10 -15
- package/dist/src/commands/patch/reorder.d.ts +0 -2
- package/dist/src/commands/patch/reorder.js +18 -19
- package/dist/src/commands/patch/split-plan.d.ts +66 -0
- package/dist/src/commands/patch/split-plan.js +178 -0
- package/dist/src/commands/patch/split.d.ts +30 -0
- package/dist/src/commands/patch/split.js +283 -0
- package/dist/src/commands/patch/staged-dependency.d.ts +1 -7
- package/dist/src/commands/patch/staged-dependency.js +4 -17
- package/dist/src/commands/patch/tier.js +4 -17
- package/dist/src/commands/re-export-files.js +4 -1
- package/dist/src/commands/re-export-scan.js +8 -1
- package/dist/src/commands/re-export.js +8 -1
- package/dist/src/commands/rebase/summary.d.ts +1 -5
- package/dist/src/commands/rebase/summary.js +1 -1
- package/dist/src/commands/status-output.js +77 -68
- package/dist/src/commands/test-diagnose.d.ts +23 -0
- package/dist/src/commands/test-diagnose.js +210 -0
- package/dist/src/commands/test-run.d.ts +68 -0
- package/dist/src/commands/test-run.js +97 -0
- package/dist/src/commands/test.js +214 -263
- package/dist/src/commands/token.js +15 -1
- package/dist/src/commands/wire.js +109 -78
- package/dist/src/core/build-audit.d.ts +1 -1
- package/dist/src/core/build-audit.js +2 -46
- package/dist/src/core/build-baseline-types.d.ts +38 -0
- package/dist/src/core/build-baseline-types.js +10 -0
- package/dist/src/core/build-baseline.d.ts +1 -31
- package/dist/src/core/build-prepare.d.ts +1 -1
- package/dist/src/core/build-prepare.js +2 -45
- package/dist/src/core/config-paths.d.ts +0 -8
- package/dist/src/core/config-paths.js +4 -4
- package/dist/src/core/config-state.d.ts +0 -6
- package/dist/src/core/config-state.js +1 -1
- package/dist/src/core/config-validate-patch-policy.js +12 -13
- package/dist/src/core/config-validate.js +74 -28
- package/dist/src/core/engine-changes.d.ts +24 -0
- package/dist/src/core/engine-changes.js +64 -0
- package/dist/src/core/firefox-cache.d.ts +0 -5
- package/dist/src/core/firefox-cache.js +1 -1
- package/dist/src/core/firefox-download.d.ts +0 -6
- package/dist/src/core/firefox-download.js +1 -1
- package/dist/src/core/furnace-apply-helpers.d.ts +1 -8
- package/dist/src/core/furnace-apply-helpers.js +11 -20
- package/dist/src/core/furnace-apply.d.ts +1 -1
- package/dist/src/core/furnace-apply.js +1 -1
- package/dist/src/core/furnace-checksum-utils.d.ts +7 -0
- package/dist/src/core/furnace-checksum-utils.js +15 -0
- package/dist/src/core/furnace-config-validate.d.ts +31 -0
- package/dist/src/core/furnace-config-validate.js +133 -0
- package/dist/src/core/furnace-config.d.ts +4 -32
- package/dist/src/core/furnace-config.js +15 -111
- package/dist/src/core/furnace-constants.d.ts +0 -10
- package/dist/src/core/furnace-constants.js +2 -2
- package/dist/src/core/furnace-css-fragments.d.ts +79 -0
- package/dist/src/core/furnace-css-fragments.js +243 -0
- package/dist/src/core/furnace-jsconfig.d.ts +63 -0
- package/dist/src/core/furnace-jsconfig.js +191 -0
- package/dist/src/core/furnace-validate-helpers.d.ts +16 -14
- package/dist/src/core/furnace-validate-helpers.js +40 -1
- package/dist/src/core/furnace-validate-registration.js +16 -1
- package/dist/src/core/furnace-validate.js +54 -2
- package/dist/src/core/git-base.d.ts +15 -0
- package/dist/src/core/git-base.js +32 -0
- package/dist/src/core/git-diff.d.ts +8 -0
- package/dist/src/core/git-diff.js +224 -59
- package/dist/src/core/git-file-ops.d.ts +39 -12
- package/dist/src/core/git-file-ops.js +84 -3
- package/dist/src/core/lint-cache.d.ts +0 -13
- package/dist/src/core/lint-cache.js +5 -5
- package/dist/src/core/mach.d.ts +22 -1
- package/dist/src/core/mach.js +27 -2
- package/dist/src/core/manifest-register.d.ts +5 -16
- package/dist/src/core/manifest-register.js +3 -1
- package/dist/src/core/patch-lint-checkjs.d.ts +75 -21
- package/dist/src/core/patch-lint-checkjs.js +263 -71
- package/dist/src/core/patch-lint-css.d.ts +23 -0
- package/dist/src/core/patch-lint-css.js +172 -0
- package/dist/src/core/patch-lint-jsdoc.js +63 -4
- package/dist/src/core/patch-lint-observer.d.ts +37 -0
- package/dist/src/core/patch-lint-observer.js +168 -0
- package/dist/src/core/patch-lint.d.ts +34 -11
- package/dist/src/core/patch-lint.js +24 -161
- package/dist/src/core/patch-manifest-io.d.ts +16 -0
- package/dist/src/core/patch-manifest-io.js +44 -2
- package/dist/src/core/patch-manifest-validate.d.ts +1 -8
- package/dist/src/core/patch-manifest-validate.js +1 -1
- package/dist/src/core/patch-manifest.d.ts +1 -1
- package/dist/src/core/patch-manifest.js +1 -1
- package/dist/src/core/patch-policy.d.ts +0 -4
- package/dist/src/core/patch-policy.js +10 -4
- package/dist/src/core/register-browser-content.d.ts +1 -1
- package/dist/src/core/register-module.d.ts +1 -1
- package/dist/src/core/register-result.d.ts +21 -0
- package/dist/src/core/register-result.js +9 -0
- package/dist/src/core/register-shared-css.d.ts +1 -1
- package/dist/src/core/register-test-manifest.d.ts +1 -1
- package/dist/src/core/test-harness-crash.d.ts +61 -0
- package/dist/src/core/test-harness-crash.js +140 -0
- package/dist/src/core/test-stale-check.d.ts +1 -1
- package/dist/src/core/test-stale-check.js +2 -46
- package/dist/src/core/test-xpcshell-retry.d.ts +9 -2
- package/dist/src/core/test-xpcshell-retry.js +10 -3
- package/dist/src/core/token-dark-mode.js +14 -26
- package/dist/src/core/token-manager.d.ts +4 -0
- package/dist/src/core/token-manager.js +70 -16
- package/dist/src/core/typecheck-shim.d.ts +3 -22
- package/dist/src/core/typecheck-shim.js +69 -7
- package/dist/src/core/wire-utils.js +37 -44
- package/dist/src/types/commands/index.d.ts +1 -1
- package/dist/src/types/commands/options.d.ts +122 -0
- package/dist/src/types/config.d.ts +11 -2
- package/dist/src/types/furnace.d.ts +12 -1
- package/dist/src/utils/elapsed.d.ts +0 -2
- package/dist/src/utils/elapsed.js +1 -1
- package/dist/src/utils/fs.d.ts +0 -5
- package/dist/src/utils/fs.js +1 -1
- package/dist/src/utils/regex.d.ts +0 -6
- package/dist/src/utils/regex.js +3 -3
- package/dist/src/utils/validation.d.ts +0 -8
- package/dist/src/utils/validation.js +2 -2
- package/package.json +6 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,41 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.32.0
|
|
4
|
+
|
|
5
|
+
- Fixed `fireforge lint <files>` to evaluate the `large-patch-files` rule against each file's resolved owning patch instead of the ad-hoc file-list cardinality, so a cross-patch selection no longer synthesizes a phantom oversized patch (e.g. eight files across four patches no longer report `Patch affects 8 files` when no single owner exceeds the threshold). The size rules now run per owning patch — using its real `filesAffected` count, diff, and `tier` — and unowned files are evaluated together as one prospective new patch.
|
|
6
|
+
- Taught the ad-hoc `lint <files>` path to honour each file's owning-patch `lintIgnore`, so a check waived via `fireforge patch lint-ignore` is suppressed consistently across `lint <files>`, `lint --per-patch`, and `re-export --dry-run` — the three invocation modes now agree on the same warning set for the same files.
|
|
7
|
+
- Added `fireforge lint --per-patch --patches <name…>` to lint a named subset of the queue (matched by filename or manifest `name`) instead of forcing a full queue run to verify a few touched patches; queue-level policy and cross-patch findings are scoped to files the subset touches. The "`--per-patch` cannot be combined with explicit file paths" error now points at the flag.
|
|
8
|
+
- Rebuilt the per-patch `checkJs` pass to construct the queue-wide TypeScript program **once per `lint --per-patch` run** (lazily, on first cache miss) and attribute each finding to its owning patch, instead of rebuilding the whole-queue program for every patch — a single queue-wide type regression now surfaces once against its owner rather than duplicated once per patch, and full runs no longer pay to recompile the same program ~N times.
|
|
9
|
+
- Resolved cross-patch `resource:///` / `chrome://` imports during `export`/`re-export` lint by threading the whole-queue ownership context into the isolated patch's `checkJs` pass (the export/re-export half of the cross-patch resolution that 0.31.0 landed for the full-queue path), while scoping reported diagnostics to the patch under export. Re-exporting a widget-runtime patch whose module imports another patch's `resource:///` module now type-checks against the real owning sources **without** a hand-generated ambient `declare module` stub shim. `patchLint.checkJsCompilerOptions` additionally accepts a reviewed `paths` mapping (host-resolved against the engine directory, so no `baseUrl` is needed — TS5090-safe), and `extraShim` / `typecheck.extraShim` now inline triple-slash `/// <reference>` directives instead of silently dropping them at the synthetic shim path.
|
|
10
|
+
- Fixed `furnace sync` to emit `./`-prefixed relative `compilerOptions.paths` values (e.g. `./components/custom/moz-widget/moz-widget.mjs`) so a synced jsconfig type-checks under TypeScript without `baseUrl` (no TS5090) and without `ignoreDeprecations` on TS6 (no TS5101); the sync reconciler now treats a leading `./` as insignificant, so neither a freshly-synced value nor a hand-written prefix churns as "stale" on the next run. Removes the downstream `baseUrl: "."` + `ignoreDeprecations: "6.0"` workaround.
|
|
11
|
+
- Made `fireforge test` auto-dispatch a single-suite run to the suite-specific mach command (`mach xpcshell-test` / `mach mochitest`), which degrade a broken macOS mozlog resource monitor to a warning instead of crashing generic `mach test` at startup — so a sharded single-suite run reaches its tests instead of burning the whole `--harness-retries` budget on a startup traceback. Mixed runs are still rejected and a path-less "run all" stays on `mach test`; `--generic-mach-test` forces the generic command.
|
|
12
|
+
- Extended the harness-crash classifier and `--harness-retries` budget to cover the pre-test `--build` step, so `fireforge test --build` retries a `mach build faster` that dies with the same resource-monitor startup crash instead of hard-failing with a bare "Pre-test build failed". Non-crash build failures are not retried.
|
|
13
|
+
|
|
14
|
+
## 0.31.0
|
|
15
|
+
|
|
16
|
+
- Made `patch compact` range-aware: with `patchPolicy.ranges` configured, each category range compacts independently (anchored at its first occupied ordinal, treating reserved orders as non-gaps), so a mid-range gap under `allowGaps: false` can finally be closed without projecting patches across category boundaries. Reserved-range patches and out-of-range strays are left in place with a warning. Without ranges the historical whole-queue renumber from 1 is unchanged.
|
|
17
|
+
- Rewrote `stagedDependencies.forwardImports[].owner` references during every patch renumber (compact, reorder, placement export, rename) and in reorder's dry-run projection, so staged-dependency declarations survive renumbering instead of dangling and surfacing pre-existing forward imports as new errors. `patch delete` now warns when other patches still name the deleted patch as an owner.
|
|
18
|
+
- Added `fireforge patch split <source> --files <paths...> --name <name>` to move files out of a patch into a brand-new patch as one transaction: worktree-derived shrink of the source, new-patch creation with `--order`/`--before`/`--after` placement (default: after the source), and staged-dependency owner rewrites in dependent patches — validated against the final projection only, with dry-run support and reverse-order rollback. `patch move-files` now points at it for the move-to-new-patch case.
|
|
19
|
+
- Scoped plain `re-export --scan` to the patch's exact directory footprint: git pathspecs recurse, so a claimed file in a shallow directory used to sweep every unmanaged file in the subtree into the scan candidates of whichever patch was exported first. Deeper paths now require an explicit `--scan-file` / `--scan-files` assignment; the broad-scan confirmation guard is unchanged.
|
|
20
|
+
- Resolved imports of patch-owned modules to their real sources in the per-patch `checkJs` pass (unique-basename matching for `chrome://`/`resource://` specifiers, with a `.mjs` → `.sys.mjs` fallback), so JSDoc `value is …` type-guard predicates and `@template` generics survive module boundaries instead of degrading to `any`. Unknown or ambiguous specifiers keep the loose ambient-wildcard typing. Note: previously invisible cross-module type errors in patch-owned modules may now surface.
|
|
21
|
+
- Added `ChromeUtils.getClassName`, `ChromeUtils.defineLazyGetter`, and the `Localization` constructor to the shipped Firefox-globals typecheck shim, so per-patch lint accepts these stable chrome globals without local casts. Projects can still extend the ambient set via `patchLint.checkJsExtraShim` / `typecheck.extraShim`.
|
|
22
|
+
- Fixed the exported-method JSDoc `@param` extractor to scan balanced braces, so inline object types containing nested generics (e.g. `@param {{ id: string, args?: Record<string, string | number | boolean> }} message`) no longer fail `jsdoc-class-method-param-mismatch`; optional `[name]` and defaulted `[name=x]` forms now parse too.
|
|
23
|
+
- Rewrote the `observer-topic-naming` check to parse balanced multi-line call sites and inspect the actual topic argument (the second) instead of the first string literal on the line, and added a known-Firefox-topics allowlist (`idle-daily`, the `quit-application` family, lifecycle and `http-on-*` topics) so simulating upstream notifications in tests is never flagged. Constant-named topics remain exempt.
|
|
24
|
+
- Taught `fireforge test` to classify harness runs from their output instead of trusting exit codes or summary lines: recognized harness-crash shapes (macOS mozlog resource-monitor/psutil startup tracebacks, pre-test no-output hangs that still print `Passed: 0`, and post-green "Application shut down (without crashing) in the middle of a test!" re-entries after a focus stall) are retried with a bounded budget (`--harness-retries <n>`, default 2), and a zero-exit run with no `TEST-START` now fails instead of passing silently.
|
|
25
|
+
- Sharded multi-path `fireforge test` invocations into sequential single-file harness runs with per-shard retries, per-shard diagnosis, and an aggregate PASS/FAIL/CRASH summary, avoiding the cross-file profile/pref bleed that destabilized later files in combined runs. `--no-shard` restores the single combined invocation.
|
|
26
|
+
- Added `fireforge test --perf-samples <path>`, which publishes the resolved artifact path to the harness process as `<BINARYNAME>_PERF_SAMPLE_JSON` so downstream perf-budget checkers no longer maintain their own env contract.
|
|
27
|
+
- Fixed named `furnace deploy <component>` to run the same pipeline as deploy-all, so renaming or deleting a component file now prunes the stale engine copy, its `jar.mn` line, and (for a removed main module) the `customElements.js` registration, instead of leaving orphans a later re-export would capture. Unchanged components now skip instead of force-reapplying, and `furnace validate` flags engine-side orphans (`orphaned-engine-file`) left by pre-0.31.0 deploys.
|
|
28
|
+
- Added shared CSS fragments for Furnace widgets: a `/* @fireforge-include <fragment>.css */` directive in a widget stylesheet expands the named `components/shared/` fragment into the deployed copy (fenced, idempotent), keeping the workspace single-sourced across shadow-DOM-isolated widgets. Fragment edits surface as ordinary component drift and redeploy refreshes every consumer; `furnace validate` reports `missing-fragment` and `stale-fragment-expansion`.
|
|
29
|
+
- Added automatic jsconfig `paths` maintenance for multi-file components: with `furnace.json#typecheckJsconfig` set, deploy and sync keep `compilerOptions.paths` entries mapping each registered module's `chrome://global/content/elements/<file>.mjs` URL to its workspace source (no `baseUrl` needed), pruning entries for removed helpers and preserving all hand-written configuration; `furnace validate` reports drift as `jsconfig-paths-drift`.
|
|
30
|
+
- Fixed `token add` double-prefixing bare names that already start with the configured `tokenPrefix` text; such names are now treated as fully qualified with an informational note.
|
|
31
|
+
- Added `token add --create-category`, which declares the missing category banner inside the `:root` block and inserts the token in the same single write; the "Category not found" error now advertises the flag.
|
|
32
|
+
- Added two static release gates: `npm run deadcode:check` (knip, with a `knip.json` tuned to the project's entry points) fails on unused exports/files/dependencies, and `npm run cycles:check` (dpdm) fails on circular imports. Both run in `release:check` and the pre-push hook.
|
|
33
|
+
- Internalized 45 exports that had no consumers outside their defining module, and untangled the seven type-only import cycles (`BuildBaseline`, `RegisterResult`, `ResolvedTestStyle`, and `LintCommandOptions` moved to leaf/type modules) so the cycle gate starts from zero.
|
|
34
|
+
- Refactored the twelve functions with cyclomatic complexity above 30 (worst: `exportCommand` at 48) into focused helpers — behavior-preserving statement moves, verified by the existing suites — and now enforce `complexity: ["error", 30]` via ESLint. The long-standing `max-lines-per-function` suppression on `exportCommand` is gone with the split.
|
|
35
|
+
- Deduplicated four copied sequences: the patch-subcommand preamble (seven commands now share `requirePatchQueue`/`requirePatchTarget`), the changed-since-baseline collector (`build-audit`/`build-prepare`/`test-stale-check` share `collectChangedEnginePaths`), the export supersede+overlap gate (`export`/`export-all` share `runSupersedeAndOverlapGates`), and two same-file clones in `token-dark-mode.ts` and `config-validate-patch-policy.ts`.
|
|
36
|
+
- Restricted `process.exit()` to `bin/fireforge.ts` via lint (`no-restricted-properties`), turning the previously comment-enforced invariant into a build failure.
|
|
37
|
+
- Bumped the TypeScript `target`/`lib` from ES2022 to ES2023 (Node ≥ 22.22.1 fully implements it) and dropped the redundant `@typescript-eslint/eslint-plugin` / `@typescript-eslint/parser` devDependencies already provided by the `typescript-eslint` meta-package.
|
|
38
|
+
|
|
3
39
|
## 0.30.0
|
|
4
40
|
|
|
5
41
|
- Added safe repo-local per-patch lint result caching for `lint --per-patch`, plus `--no-cache` and `lint cache clear` escape hatches while preserving release-gate severity accounting and queue-wide checks. Warm cache hits now skip per-patch diff generation as well as lint rule execution, guarded by patch, config, engine content, queue ownership, and engine HEAD inputs.
|
package/README.md
CHANGED
|
@@ -82,6 +82,19 @@ assignment before refreshing the patch. For release whitespace checks, use
|
|
|
82
82
|
`npm run whitespace:check`; it still checks source diffs while excluding generated
|
|
83
83
|
`patches/*.patch` diff syntax.
|
|
84
84
|
|
|
85
|
+
Queue maintenance lives under `fireforge patch`: `patch compact` closes ordinal gaps
|
|
86
|
+
(range-aware when `patchPolicy.ranges` is configured), `patch reorder` moves a patch, and
|
|
87
|
+
`patch split <source> --files <paths...> --name <name>` carves files out of a patch into a
|
|
88
|
+
new one as a single transaction — including staged-dependency owner rewrites — with
|
|
89
|
+
`--dry-run` support.
|
|
90
|
+
|
|
91
|
+
`fireforge test` runs multiple test paths as sequential per-file shards by default
|
|
92
|
+
(`--no-shard` restores one combined invocation), retries recognized harness crashes up to
|
|
93
|
+
`--harness-retries <n>` times (default 2), and can publish a perf-sample artifact path to
|
|
94
|
+
the harness via `--perf-samples <path>` (exported as `<BINARYNAME>_PERF_SAMPLE_JSON`).
|
|
95
|
+
Design tokens are managed with `fireforge token add`; pass `--create-category` to declare a
|
|
96
|
+
new category banner and insert the token in one step.
|
|
97
|
+
|
|
85
98
|
## Rebasing Firefox Source
|
|
86
99
|
|
|
87
100
|
When Mozilla publishes a new Firefox source release you need to update the configured version/product, download the new source code and reapply the patches:
|
|
@@ -109,6 +122,15 @@ npx fireforge furnace preview
|
|
|
109
122
|
|
|
110
123
|
Use `fireforge furnace --help` for the full set of component commands.
|
|
111
124
|
|
|
125
|
+
Cross-widget CSS can be single-sourced as shared fragments: place the fragment in
|
|
126
|
+
`components/shared/` and reference it from a widget stylesheet with a
|
|
127
|
+
`/* @fireforge-include <fragment>.css */` comment — deploy expands it into the deployed
|
|
128
|
+
copy only, and editing the fragment surfaces as component drift until the next deploy.
|
|
129
|
+
For typed cross-module imports of multi-file components, set
|
|
130
|
+
`furnace.json#typecheckJsconfig` to a consumer-owned jsconfig and deploy will maintain
|
|
131
|
+
`compilerOptions.paths` entries mapping each deployed
|
|
132
|
+
`chrome://global/content/elements/<file>.mjs` URL to its workspace source.
|
|
133
|
+
|
|
112
134
|
## Roadmap
|
|
113
135
|
|
|
114
136
|
- **Docker builds** Reproducible builds using Docker containers.
|
|
@@ -5,7 +5,7 @@ import { hasChanges, isGitRepository } from '../core/git.js';
|
|
|
5
5
|
import { getAllDiff, getDiffForFilesAgainstHead } from '../core/git-diff.js';
|
|
6
6
|
import { expandUntrackedDirectoryEntries, getWorkingTreeStatus } from '../core/git-status.js';
|
|
7
7
|
import { extractAffectedFiles } from '../core/patch-apply.js';
|
|
8
|
-
import { commitExportedPatch
|
|
8
|
+
import { commitExportedPatch } from '../core/patch-export.js';
|
|
9
9
|
import { buildPatchQueueContext, collectNewFileCreatorsByPath, detectNewFilesInDiff, } from '../core/patch-lint.js';
|
|
10
10
|
import { collectPatchRegistrationReferences } from '../core/patch-registration-refs.js';
|
|
11
11
|
import { buildPatchSourceMetadata } from '../core/patch-source-metadata.js';
|
|
@@ -14,7 +14,7 @@ import { ensureDir, pathExists } from '../utils/fs.js';
|
|
|
14
14
|
import { info, intro, outro, spinner } from '../utils/logger.js';
|
|
15
15
|
import { pickDefined } from '../utils/options.js';
|
|
16
16
|
import { renderDryRunPreview } from './export-flow.js';
|
|
17
|
-
import { autoFixLicenseHeaders,
|
|
17
|
+
import { autoFixLicenseHeaders, promptExportPatchMetadata, runPatchLint, runSupersedeAndOverlapGates, } from './export-shared.js';
|
|
18
18
|
async function checkBrandingManagedFiles(paths, config) {
|
|
19
19
|
const changedFiles = await getWorkingTreeStatus(paths.engine);
|
|
20
20
|
const brandingManagedFiles = changedFiles
|
|
@@ -248,7 +248,10 @@ export async function exportAllCommand(projectRoot, options = {}) {
|
|
|
248
248
|
try {
|
|
249
249
|
// Extract affected files from diff
|
|
250
250
|
const filesAffected = extractAffectedFiles(diff);
|
|
251
|
-
await
|
|
251
|
+
const patchQueueCtx = (await pathExists(paths.patches))
|
|
252
|
+
? await buildPatchQueueContext(paths.patches)
|
|
253
|
+
: undefined;
|
|
254
|
+
await runPatchLint(paths.engine, filesAffected, diff, config, options.skipLint, patchQueueCtx);
|
|
252
255
|
// Dry-run: enumerate filename, metadata, and supersede coverage without
|
|
253
256
|
// writing. Mirrors `fireforge export --dry-run` so the same preview
|
|
254
257
|
// surface is available for both targeted and aggregate exports. Runs
|
|
@@ -272,25 +275,15 @@ export async function exportAllCommand(projectRoot, options = {}) {
|
|
|
272
275
|
outro('Dry run complete — no changes made');
|
|
273
276
|
return;
|
|
274
277
|
}
|
|
275
|
-
|
|
276
|
-
const shouldProceed = await confirmSupersedePatches(paths.patches, filesAffected, options.supersede, isInteractive, s);
|
|
277
|
-
if (!shouldProceed)
|
|
278
|
-
return;
|
|
279
|
-
// Overlap gate — see the matching comment in `export.ts`. The same
|
|
280
|
-
// cross-patch ownership problem applies to `export-all` because a
|
|
281
|
-
// mixed aggregate diff often touches shared files like manifest
|
|
282
|
-
// fragments that other patches already claim.
|
|
283
|
-
const willSupersede = await findAllPatchesForFiles(paths.patches, filesAffected);
|
|
284
|
-
const supersedingFilenames = new Set(willSupersede.map((p) => p.filename));
|
|
285
|
-
const shouldProceedPastOverlap = await guardOwnershipOverlap({
|
|
278
|
+
const shouldProceedPastGates = await runSupersedeAndOverlapGates({
|
|
286
279
|
patchesDir: paths.patches,
|
|
287
280
|
filesAffected,
|
|
288
|
-
|
|
281
|
+
supersede: options.supersede,
|
|
289
282
|
allowOverlap: options.allowOverlap === true,
|
|
290
283
|
isInteractive,
|
|
291
284
|
s,
|
|
292
285
|
});
|
|
293
|
-
if (!
|
|
286
|
+
if (!shouldProceedPastGates)
|
|
294
287
|
return;
|
|
295
288
|
// Get Firefox version for metadata
|
|
296
289
|
const { patchFilename, superseded } = await commitExportedPatch({
|
|
@@ -19,6 +19,12 @@ export interface PlacementPlan {
|
|
|
19
19
|
newFilename: string;
|
|
20
20
|
renameMap: Map<string, PatchRenameEntry>;
|
|
21
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* Structural equality for placement plans — used by placement-mode export
|
|
24
|
+
* and `patch split` to verify the queue did not change between the
|
|
25
|
+
* confirmed preview and the under-lock commit.
|
|
26
|
+
*/
|
|
27
|
+
export declare function placementPlansEqual(left: PlacementPlan, right: PlacementPlan): boolean;
|
|
22
28
|
/**
|
|
23
29
|
* Computes the shift map that moves existing patches out of the requested
|
|
24
30
|
* slot to make room for a new patch at `requestedOrder`.
|
|
@@ -33,7 +33,12 @@ function prefixWidthForPatches(manifestPatches, requestedOrder) {
|
|
|
33
33
|
function getSortedRenameEntries(renameMap) {
|
|
34
34
|
return Array.from(renameMap.entries()).sort((a, b) => a[1].newOrder - b[1].newOrder);
|
|
35
35
|
}
|
|
36
|
-
|
|
36
|
+
/**
|
|
37
|
+
* Structural equality for placement plans — used by placement-mode export
|
|
38
|
+
* and `patch split` to verify the queue did not change between the
|
|
39
|
+
* confirmed preview and the under-lock commit.
|
|
40
|
+
*/
|
|
41
|
+
export function placementPlansEqual(left, right) {
|
|
37
42
|
if (left.insertionOrder !== right.insertionOrder || left.newFilename !== right.newFilename) {
|
|
38
43
|
return false;
|
|
39
44
|
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Placement-flag gating for `fireforge export`, split out of `export.ts`
|
|
3
|
+
* to keep the command body inside the per-function and per-file line
|
|
4
|
+
* budgets. `gatePlacementPlan` owns every policy/confirmation decision a
|
|
5
|
+
* placement export makes before the locked commit in `export.ts` runs.
|
|
6
|
+
*/
|
|
7
|
+
import type { ExportOptions, PatchMetadata } from '../types/commands/index.js';
|
|
8
|
+
import type { FireForgeConfig } from '../types/config.js';
|
|
9
|
+
import type { SpinnerHandle } from '../utils/logger.js';
|
|
10
|
+
import { type PlacementPlan } from './export-flow.js';
|
|
11
|
+
/**
|
|
12
|
+
* Spreadable optional metadata (`tier`, `lintIgnore`) derived from the
|
|
13
|
+
* export flags. Every manifest-row construction site in this command
|
|
14
|
+
* shares this shape; with exactOptionalPropertyTypes the keys must be
|
|
15
|
+
* omitted entirely (not set to undefined) when the flags are absent.
|
|
16
|
+
*/
|
|
17
|
+
export declare function patchMetadataExtras(options: ExportOptions): Partial<Pick<PatchMetadata, 'tier' | 'lintIgnore'>>;
|
|
18
|
+
/**
|
|
19
|
+
* Resolves and gates the placement plan when any placement flag
|
|
20
|
+
* (`--order`/`--before`/`--after`) was given: rejects the `--supersede`
|
|
21
|
+
* combination, enforces reserved ranges and patch policy against the
|
|
22
|
+
* projected manifest, and routes destructive renumbers (or dry-runs)
|
|
23
|
+
* through `confirmDestructive`. Returns the plan to commit, or `'stop'`
|
|
24
|
+
* when the command should end here (dry-run rendered or operator
|
|
25
|
+
* cancelled — the corresponding outro has already been printed).
|
|
26
|
+
*/
|
|
27
|
+
export declare function gatePlacementPlan(args: {
|
|
28
|
+
patchesDir: string;
|
|
29
|
+
options: ExportOptions;
|
|
30
|
+
selectedCategory: string;
|
|
31
|
+
patchName: string;
|
|
32
|
+
description: string;
|
|
33
|
+
filesAffected: string[];
|
|
34
|
+
diff: string;
|
|
35
|
+
config: FireForgeConfig;
|
|
36
|
+
isDryRun: boolean;
|
|
37
|
+
s: SpinnerHandle;
|
|
38
|
+
}): Promise<PlacementPlan | 'stop'>;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* Placement-flag gating for `fireforge export`, split out of `export.ts`
|
|
4
|
+
* to keep the command body inside the per-function and per-file line
|
|
5
|
+
* budgets. `gatePlacementPlan` owns every policy/confirmation decision a
|
|
6
|
+
* placement export makes before the locked commit in `export.ts` runs.
|
|
7
|
+
*/
|
|
8
|
+
import { confirmDestructive } from '../core/destructive.js';
|
|
9
|
+
import { loadPatchesManifest } from '../core/patch-manifest.js';
|
|
10
|
+
import { applyRenameMapToManifest, buildProjectedManifest, enforcePatchPolicy, } from '../core/patch-policy.js';
|
|
11
|
+
import { buildPatchSourceMetadata } from '../core/patch-source-metadata.js';
|
|
12
|
+
import { InvalidArgumentError } from '../errors/base.js';
|
|
13
|
+
import { outro } from '../utils/logger.js';
|
|
14
|
+
import { placementSummary, projectPlacementForLint, resolvePlacementPlan, } from './export-flow.js';
|
|
15
|
+
import { assertPlacementPreservesReservedRanges } from './export-placement-policy.js';
|
|
16
|
+
/**
|
|
17
|
+
* Spreadable optional metadata (`tier`, `lintIgnore`) derived from the
|
|
18
|
+
* export flags. Every manifest-row construction site in this command
|
|
19
|
+
* shares this shape; with exactOptionalPropertyTypes the keys must be
|
|
20
|
+
* omitted entirely (not set to undefined) when the flags are absent.
|
|
21
|
+
*/
|
|
22
|
+
export function patchMetadataExtras(options) {
|
|
23
|
+
return {
|
|
24
|
+
...(options.tier !== undefined ? { tier: options.tier } : {}),
|
|
25
|
+
...(options.lintIgnore !== undefined && options.lintIgnore.length > 0
|
|
26
|
+
? { lintIgnore: options.lintIgnore }
|
|
27
|
+
: {}),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Resolves and gates the placement plan when any placement flag
|
|
32
|
+
* (`--order`/`--before`/`--after`) was given: rejects the `--supersede`
|
|
33
|
+
* combination, enforces reserved ranges and patch policy against the
|
|
34
|
+
* projected manifest, and routes destructive renumbers (or dry-runs)
|
|
35
|
+
* through `confirmDestructive`. Returns the plan to commit, or `'stop'`
|
|
36
|
+
* when the command should end here (dry-run rendered or operator
|
|
37
|
+
* cancelled — the corresponding outro has already been printed).
|
|
38
|
+
*/
|
|
39
|
+
export async function gatePlacementPlan(args) {
|
|
40
|
+
const { patchesDir, options, selectedCategory, patchName, description, filesAffected, diff, config, isDryRun, s, } = args;
|
|
41
|
+
if (options.supersede) {
|
|
42
|
+
throw new InvalidArgumentError('Placement flags (--order/--before/--after) cannot be combined with --supersede.', 'export placement');
|
|
43
|
+
}
|
|
44
|
+
const placementPlan = await resolvePlacementPlan(patchesDir, options, selectedCategory, patchName);
|
|
45
|
+
const currentManifest = await loadPatchesManifest(patchesDir);
|
|
46
|
+
if (currentManifest !== null) {
|
|
47
|
+
assertPlacementPreservesReservedRanges(placementPlan, currentManifest.patches, config, selectedCategory);
|
|
48
|
+
}
|
|
49
|
+
const conflicts = await projectPlacementForLint(patchesDir, placementPlan, diff);
|
|
50
|
+
const renamed = currentManifest !== null
|
|
51
|
+
? applyRenameMapToManifest(currentManifest, placementPlan.renameMap)
|
|
52
|
+
: buildProjectedManifest(null, []);
|
|
53
|
+
enforcePatchPolicy({
|
|
54
|
+
config,
|
|
55
|
+
manifest: buildProjectedManifest(renamed, [
|
|
56
|
+
...renamed.patches,
|
|
57
|
+
{
|
|
58
|
+
filename: placementPlan.newFilename,
|
|
59
|
+
order: placementPlan.insertionOrder,
|
|
60
|
+
category: selectedCategory,
|
|
61
|
+
name: patchName,
|
|
62
|
+
description,
|
|
63
|
+
createdAt: new Date().toISOString(),
|
|
64
|
+
...buildPatchSourceMetadata(config.firefox),
|
|
65
|
+
filesAffected,
|
|
66
|
+
...patchMetadataExtras(options),
|
|
67
|
+
},
|
|
68
|
+
]),
|
|
69
|
+
command: 'export',
|
|
70
|
+
forceUnsafe: options.forceUnsafe === true,
|
|
71
|
+
});
|
|
72
|
+
const summary = placementSummary(placementPlan);
|
|
73
|
+
const renameCount = placementPlan.renameMap.size;
|
|
74
|
+
// Route through confirmDestructive when the operation is destructive
|
|
75
|
+
// enough to warrant a prompt (more than one rename) OR when the user
|
|
76
|
+
// asked for a dry-run. The dry-run branch must always print the
|
|
77
|
+
// placement summary — previously, single-rename/no-rename dry-runs
|
|
78
|
+
// exited silently with no filename or projected layout.
|
|
79
|
+
if (renameCount > 1 || isDryRun) {
|
|
80
|
+
s.stop();
|
|
81
|
+
const decision = await confirmDestructive({
|
|
82
|
+
operation: 'export-order',
|
|
83
|
+
title: `Export with placement at order ${placementPlan.insertionOrder}`,
|
|
84
|
+
summary,
|
|
85
|
+
yes: options.yes === true,
|
|
86
|
+
dryRun: isDryRun,
|
|
87
|
+
unsafeOverride: options.forceUnsafe === true,
|
|
88
|
+
conflicts,
|
|
89
|
+
});
|
|
90
|
+
if (decision === 'dry-run') {
|
|
91
|
+
outro('Dry run complete — no changes made');
|
|
92
|
+
return 'stop';
|
|
93
|
+
}
|
|
94
|
+
if (decision === 'cancelled') {
|
|
95
|
+
outro('Export cancelled');
|
|
96
|
+
return 'stop';
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
else if (conflicts && options.forceUnsafe !== true) {
|
|
100
|
+
s.stop();
|
|
101
|
+
throw new InvalidArgumentError(`Refusing to run export: ${conflicts.reason}. Pass --force-unsafe to override.`, '--force-unsafe');
|
|
102
|
+
}
|
|
103
|
+
return placementPlan;
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=export-placement-gate.js.map
|
|
@@ -94,3 +94,31 @@ export declare function guardOwnershipOverlap(args: {
|
|
|
94
94
|
isInteractive: boolean;
|
|
95
95
|
s: SpinnerHandle;
|
|
96
96
|
}): Promise<boolean>;
|
|
97
|
+
/**
|
|
98
|
+
* Runs the two pre-commit gates shared by `export` and `export-all` in
|
|
99
|
+
* order: the supersede confirmation, then the cross-patch ownership
|
|
100
|
+
* overlap guard. The overlap guard receives the filenames of the patches
|
|
101
|
+
* the export would fully supersede so it does not flag a file claimed by
|
|
102
|
+
* a patch that is about to be removed (pre-0.16.0 `export` only caught
|
|
103
|
+
* FULL-coverage supersedes, so a second export targeting a shared file
|
|
104
|
+
* like `browser/themes/shared/jar.inc.mn` created a queue where two
|
|
105
|
+
* patches both claimed the file and `verify` failed immediately).
|
|
106
|
+
*
|
|
107
|
+
* @param args - Gate inputs shared by both export commands
|
|
108
|
+
* @param args.patchesDir - Absolute path of the patches directory
|
|
109
|
+
* @param args.filesAffected - Engine-relative files the export claims
|
|
110
|
+
* @param args.supersede - The command's `--supersede` flag
|
|
111
|
+
* @param args.allowOverlap - The command's `--allow-overlap` flag
|
|
112
|
+
* @param args.isInteractive - Whether prompting the operator is possible
|
|
113
|
+
* @param args.s - Active spinner, stopped before any prompt
|
|
114
|
+
* @returns `true` when both gates passed; `false` when the operator
|
|
115
|
+
* declined (the caller returns without committing)
|
|
116
|
+
*/
|
|
117
|
+
export declare function runSupersedeAndOverlapGates(args: {
|
|
118
|
+
patchesDir: string;
|
|
119
|
+
filesAffected: string[];
|
|
120
|
+
supersede: boolean | undefined;
|
|
121
|
+
allowOverlap: boolean;
|
|
122
|
+
isInteractive: boolean;
|
|
123
|
+
s: SpinnerHandle;
|
|
124
|
+
}): Promise<boolean>;
|
|
@@ -4,6 +4,7 @@ import { confirm, select, text } from '@clack/prompts';
|
|
|
4
4
|
import { addLicenseHeaderToFile, getLicenseHeader } from '../core/license-headers.js';
|
|
5
5
|
import { findAllPatchesForFiles } from '../core/patch-export.js';
|
|
6
6
|
import { commentStyleForFile, detectNewFilesInDiff, lintExportedPatch, resolvePatchSizeTier, } from '../core/patch-lint.js';
|
|
7
|
+
import { resolvePatchOwnedSysMjs } from '../core/patch-lint-ownership.js';
|
|
7
8
|
import { loadPatchesManifest } from '../core/patch-manifest.js';
|
|
8
9
|
import { getPatchPolicyCategories, isCategoryAllowedByConfig } from '../core/patch-policy.js';
|
|
9
10
|
import { GeneralError, InvalidArgumentError } from '../errors/base.js';
|
|
@@ -45,7 +46,15 @@ export async function runPatchLint(engineDir, filesAffected, diffContent, config
|
|
|
45
46
|
? 'Lint: branding threshold tier applied via patches.json `tier: "branding"` opt-in.'
|
|
46
47
|
: 'Lint: branding threshold tier applied (patch is all under browser/branding/ plus registration siblings).');
|
|
47
48
|
}
|
|
48
|
-
|
|
49
|
+
// When a whole-queue context is supplied, checkJs resolves cross-patch
|
|
50
|
+
// `resource:///`/`chrome://` imports against every patch-owned module, but
|
|
51
|
+
// only this patch's own new modules should report diagnostics. Scope the
|
|
52
|
+
// report to the files this diff creates so re-exporting one patch does not
|
|
53
|
+
// surface another patch's findings (and no ambient stub shim is needed).
|
|
54
|
+
const checkJsReportScope = patchQueueCtx
|
|
55
|
+
? resolvePatchOwnedSysMjs(detectNewFilesInDiff(diffContent))
|
|
56
|
+
: undefined;
|
|
57
|
+
const issues = await lintExportedPatch(engineDir, filesAffected, diffContent, config, patchQueueCtx, ignoreChecks, patchTier, checkJsReportScope ? { checkJsReportScope } : undefined);
|
|
49
58
|
if (issues.length === 0)
|
|
50
59
|
return;
|
|
51
60
|
const errors = issues.filter((i) => i.severity === 'error');
|
|
@@ -315,4 +324,40 @@ export async function guardOwnershipOverlap(args) {
|
|
|
315
324
|
}
|
|
316
325
|
return true;
|
|
317
326
|
}
|
|
327
|
+
/**
|
|
328
|
+
* Runs the two pre-commit gates shared by `export` and `export-all` in
|
|
329
|
+
* order: the supersede confirmation, then the cross-patch ownership
|
|
330
|
+
* overlap guard. The overlap guard receives the filenames of the patches
|
|
331
|
+
* the export would fully supersede so it does not flag a file claimed by
|
|
332
|
+
* a patch that is about to be removed (pre-0.16.0 `export` only caught
|
|
333
|
+
* FULL-coverage supersedes, so a second export targeting a shared file
|
|
334
|
+
* like `browser/themes/shared/jar.inc.mn` created a queue where two
|
|
335
|
+
* patches both claimed the file and `verify` failed immediately).
|
|
336
|
+
*
|
|
337
|
+
* @param args - Gate inputs shared by both export commands
|
|
338
|
+
* @param args.patchesDir - Absolute path of the patches directory
|
|
339
|
+
* @param args.filesAffected - Engine-relative files the export claims
|
|
340
|
+
* @param args.supersede - The command's `--supersede` flag
|
|
341
|
+
* @param args.allowOverlap - The command's `--allow-overlap` flag
|
|
342
|
+
* @param args.isInteractive - Whether prompting the operator is possible
|
|
343
|
+
* @param args.s - Active spinner, stopped before any prompt
|
|
344
|
+
* @returns `true` when both gates passed; `false` when the operator
|
|
345
|
+
* declined (the caller returns without committing)
|
|
346
|
+
*/
|
|
347
|
+
export async function runSupersedeAndOverlapGates(args) {
|
|
348
|
+
const { patchesDir, filesAffected, supersede, allowOverlap, isInteractive, s } = args;
|
|
349
|
+
const shouldProceed = await confirmSupersedePatches(patchesDir, filesAffected, supersede, isInteractive, s);
|
|
350
|
+
if (!shouldProceed)
|
|
351
|
+
return false;
|
|
352
|
+
const willSupersede = await findAllPatchesForFiles(patchesDir, filesAffected);
|
|
353
|
+
const supersedingFilenames = new Set(willSupersede.map((p) => p.filename));
|
|
354
|
+
return guardOwnershipOverlap({
|
|
355
|
+
patchesDir,
|
|
356
|
+
filesAffected,
|
|
357
|
+
supersedingFilenames,
|
|
358
|
+
allowOverlap,
|
|
359
|
+
isInteractive,
|
|
360
|
+
s,
|
|
361
|
+
});
|
|
362
|
+
}
|
|
318
363
|
//# sourceMappingURL=export-shared.js.map
|