@hominis/fireforge 0.30.1 → 0.31.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 +25 -0
- package/README.md +22 -0
- package/dist/src/commands/export-all.js +5 -15
- 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 +36 -0
- package/dist/src/commands/export.js +47 -112
- 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 +1 -1
- package/dist/src/commands/lint-per-patch.js +110 -81
- package/dist/src/commands/lint.d.ts +1 -58
- package/dist/src/commands/lint.js +96 -84
- 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-scan.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 +58 -0
- package/dist/src/commands/test-run.js +88 -0
- package/dist/src/commands/test.js +169 -257
- 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 +48 -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 +171 -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-file-ops.d.ts +0 -12
- package/dist/src/core/git-file-ops.js +2 -2
- 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 +5 -1
- package/dist/src/core/mach.js +6 -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.js +53 -7
- 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.js +132 -125
- 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 +1 -1
- package/dist/src/core/test-xpcshell-retry.js +4 -2
- 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 +0 -21
- package/dist/src/core/typecheck-shim.js +26 -4
- 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 +105 -0
- 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,30 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.31.0
|
|
4
|
+
|
|
5
|
+
- 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.
|
|
6
|
+
- 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.
|
|
7
|
+
- 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.
|
|
8
|
+
- 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.
|
|
9
|
+
- 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.
|
|
10
|
+
- 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`.
|
|
11
|
+
- 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.
|
|
12
|
+
- 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.
|
|
13
|
+
- 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.
|
|
14
|
+
- 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.
|
|
15
|
+
- 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.
|
|
16
|
+
- 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.
|
|
17
|
+
- 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`.
|
|
18
|
+
- 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`.
|
|
19
|
+
- 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.
|
|
20
|
+
- 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.
|
|
21
|
+
- 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.
|
|
22
|
+
- 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.
|
|
23
|
+
- 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.
|
|
24
|
+
- 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`.
|
|
25
|
+
- Restricted `process.exit()` to `bin/fireforge.ts` via lint (`no-restricted-properties`), turning the previously comment-enforced invariant into a build failure.
|
|
26
|
+
- 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.
|
|
27
|
+
|
|
3
28
|
## 0.30.0
|
|
4
29
|
|
|
5
30
|
- 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
|
|
@@ -272,25 +272,15 @@ export async function exportAllCommand(projectRoot, options = {}) {
|
|
|
272
272
|
outro('Dry run complete — no changes made');
|
|
273
273
|
return;
|
|
274
274
|
}
|
|
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({
|
|
275
|
+
const shouldProceedPastGates = await runSupersedeAndOverlapGates({
|
|
286
276
|
patchesDir: paths.patches,
|
|
287
277
|
filesAffected,
|
|
288
|
-
|
|
278
|
+
supersede: options.supersede,
|
|
289
279
|
allowOverlap: options.allowOverlap === true,
|
|
290
280
|
isInteractive,
|
|
291
281
|
s,
|
|
292
282
|
});
|
|
293
|
-
if (!
|
|
283
|
+
if (!shouldProceedPastGates)
|
|
294
284
|
return;
|
|
295
285
|
// Get Firefox version for metadata
|
|
296
286
|
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>;
|
|
@@ -315,4 +315,40 @@ export async function guardOwnershipOverlap(args) {
|
|
|
315
315
|
}
|
|
316
316
|
return true;
|
|
317
317
|
}
|
|
318
|
+
/**
|
|
319
|
+
* Runs the two pre-commit gates shared by `export` and `export-all` in
|
|
320
|
+
* order: the supersede confirmation, then the cross-patch ownership
|
|
321
|
+
* overlap guard. The overlap guard receives the filenames of the patches
|
|
322
|
+
* the export would fully supersede so it does not flag a file claimed by
|
|
323
|
+
* a patch that is about to be removed (pre-0.16.0 `export` only caught
|
|
324
|
+
* FULL-coverage supersedes, so a second export targeting a shared file
|
|
325
|
+
* like `browser/themes/shared/jar.inc.mn` created a queue where two
|
|
326
|
+
* patches both claimed the file and `verify` failed immediately).
|
|
327
|
+
*
|
|
328
|
+
* @param args - Gate inputs shared by both export commands
|
|
329
|
+
* @param args.patchesDir - Absolute path of the patches directory
|
|
330
|
+
* @param args.filesAffected - Engine-relative files the export claims
|
|
331
|
+
* @param args.supersede - The command's `--supersede` flag
|
|
332
|
+
* @param args.allowOverlap - The command's `--allow-overlap` flag
|
|
333
|
+
* @param args.isInteractive - Whether prompting the operator is possible
|
|
334
|
+
* @param args.s - Active spinner, stopped before any prompt
|
|
335
|
+
* @returns `true` when both gates passed; `false` when the operator
|
|
336
|
+
* declined (the caller returns without committing)
|
|
337
|
+
*/
|
|
338
|
+
export async function runSupersedeAndOverlapGates(args) {
|
|
339
|
+
const { patchesDir, filesAffected, supersede, allowOverlap, isInteractive, s } = args;
|
|
340
|
+
const shouldProceed = await confirmSupersedePatches(patchesDir, filesAffected, supersede, isInteractive, s);
|
|
341
|
+
if (!shouldProceed)
|
|
342
|
+
return false;
|
|
343
|
+
const willSupersede = await findAllPatchesForFiles(patchesDir, filesAffected);
|
|
344
|
+
const supersedingFilenames = new Set(willSupersede.map((p) => p.filename));
|
|
345
|
+
return guardOwnershipOverlap({
|
|
346
|
+
patchesDir,
|
|
347
|
+
filesAffected,
|
|
348
|
+
supersedingFilenames,
|
|
349
|
+
allowOverlap,
|
|
350
|
+
isInteractive,
|
|
351
|
+
s,
|
|
352
|
+
});
|
|
353
|
+
}
|
|
318
354
|
//# sourceMappingURL=export-shared.js.map
|
|
@@ -3,16 +3,14 @@ import { stat } from 'node:fs/promises';
|
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
import { Option } from 'commander';
|
|
5
5
|
import { getProjectPaths, loadConfig } from '../core/config.js';
|
|
6
|
-
import { appendHistory
|
|
6
|
+
import { appendHistory } from '../core/destructive.js';
|
|
7
7
|
import { collectFurnaceManagedPrefixes } from '../core/furnace-config.js';
|
|
8
8
|
import { getStatusWithCodes, isGitRepository } from '../core/git.js';
|
|
9
9
|
import { generateBinaryFilePatch, generateFullFilePatch } from '../core/git-diff.js';
|
|
10
10
|
import { isBinaryFile } from '../core/git-file-ops.js';
|
|
11
11
|
import { getModifiedFilesInDir, getUntrackedFiles, getUntrackedFilesInDir, } from '../core/git-status.js';
|
|
12
12
|
import { extractAffectedFiles } from '../core/patch-apply.js';
|
|
13
|
-
import { commitExportedPatch
|
|
14
|
-
import { loadPatchesManifest } from '../core/patch-manifest.js';
|
|
15
|
-
import { applyRenameMapToManifest, buildProjectedManifest, enforcePatchPolicy, } from '../core/patch-policy.js';
|
|
13
|
+
import { commitExportedPatch } from '../core/patch-export.js';
|
|
16
14
|
import { buildPatchSourceMetadata } from '../core/patch-source-metadata.js';
|
|
17
15
|
import { GeneralError, InvalidArgumentError } from '../errors/base.js';
|
|
18
16
|
import { toError } from '../utils/errors.js';
|
|
@@ -21,9 +19,9 @@ import { info, intro, outro, spinner, verbose, warn } from '../utils/logger.js';
|
|
|
21
19
|
import { pickDefined } from '../utils/options.js';
|
|
22
20
|
import { stripEnginePrefix } from '../utils/paths.js';
|
|
23
21
|
import { parsePositiveIntegerFlag } from '../utils/validation.js';
|
|
24
|
-
import { commitPlacementExport,
|
|
25
|
-
import {
|
|
26
|
-
import { autoFixLicenseHeaders,
|
|
22
|
+
import { commitPlacementExport, renderDryRunPreview } from './export-flow.js';
|
|
23
|
+
import { gatePlacementPlan, patchMetadataExtras } from './export-placement-gate.js';
|
|
24
|
+
import { autoFixLicenseHeaders, promptExportPatchMetadata, runPatchLint, runSupersedeAndOverlapGates, } from './export-shared.js';
|
|
27
25
|
async function collectExportFiles(paths, files) {
|
|
28
26
|
const collectedFiles = new Set();
|
|
29
27
|
let fileStatuses;
|
|
@@ -100,20 +98,14 @@ async function generatePatchDiff(engineDir, allFiles) {
|
|
|
100
98
|
return diffs.join('\n');
|
|
101
99
|
}
|
|
102
100
|
/**
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
101
|
+
* Validation + diff phase of `exportCommand`: checks flag combinations and
|
|
102
|
+
* the engine checkout, collects the export file set (honouring
|
|
103
|
+
* `--exclude-furnace`), generates the diff, auto-fixes license headers,
|
|
104
|
+
* and prompts for patch metadata. Returns `null` when the operator
|
|
105
|
+
* cancelled the metadata prompt (the command ends silently, matching the
|
|
106
|
+
* prompt's own cancel handling).
|
|
108
107
|
*/
|
|
109
|
-
|
|
110
|
-
// gate → dry-run/placement/default write. Splitting it further would
|
|
111
|
-
// spread the error-handling (spinner.error, try/catch) across multiple
|
|
112
|
-
// helpers and hurt readability more than it would help.
|
|
113
|
-
// eslint-disable-next-line max-lines-per-function
|
|
114
|
-
export async function exportCommand(projectRoot, files, options) {
|
|
115
|
-
const isDryRun = options.dryRun === true;
|
|
116
|
-
intro(isDryRun ? 'FireForge Export (dry run)' : 'FireForge Export');
|
|
108
|
+
async function prepareExport(projectRoot, files, options) {
|
|
117
109
|
// Placement flags are mutually exclusive with each other.
|
|
118
110
|
const placementFlagCount = [
|
|
119
111
|
options.order !== undefined,
|
|
@@ -170,7 +162,23 @@ export async function exportCommand(projectRoot, files, options) {
|
|
|
170
162
|
}
|
|
171
163
|
const metadata = await promptExportPatchMetadata(options, isInteractive, 'export', config);
|
|
172
164
|
if (!metadata)
|
|
165
|
+
return null;
|
|
166
|
+
return { paths, placementFlagCount, diff, config, isInteractive, metadata };
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Runs the export command to export file changes as a patch.
|
|
170
|
+
* Accepts one or more file/directory paths and bundles them into a single patch.
|
|
171
|
+
* @param projectRoot - Root directory of the project
|
|
172
|
+
* @param files - File or directory paths to export (relative to engine/)
|
|
173
|
+
* @param options - Export options
|
|
174
|
+
*/
|
|
175
|
+
export async function exportCommand(projectRoot, files, options) {
|
|
176
|
+
const isDryRun = options.dryRun === true;
|
|
177
|
+
intro(isDryRun ? 'FireForge Export (dry run)' : 'FireForge Export');
|
|
178
|
+
const prepared = await prepareExport(projectRoot, files, options);
|
|
179
|
+
if (!prepared)
|
|
173
180
|
return;
|
|
181
|
+
const { paths, placementFlagCount, diff, config, isInteractive, metadata } = prepared;
|
|
174
182
|
const { patchName, selectedCategory, description } = metadata;
|
|
175
183
|
const s = spinner(isDryRun ? 'Planning export...' : 'Exporting patch...');
|
|
176
184
|
try {
|
|
@@ -189,71 +197,21 @@ export async function exportCommand(projectRoot, files, options) {
|
|
|
189
197
|
// exclusive with supersede — the semantics overlap confusingly.
|
|
190
198
|
let placementPlan = null;
|
|
191
199
|
if (placementFlagCount > 0) {
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
const conflicts = await projectPlacementForLint(paths.patches, placementPlan, diff);
|
|
201
|
-
const renamed = currentManifest !== null
|
|
202
|
-
? applyRenameMapToManifest(currentManifest, placementPlan.renameMap)
|
|
203
|
-
: buildProjectedManifest(null, []);
|
|
204
|
-
enforcePatchPolicy({
|
|
200
|
+
const gated = await gatePlacementPlan({
|
|
201
|
+
patchesDir: paths.patches,
|
|
202
|
+
options,
|
|
203
|
+
selectedCategory,
|
|
204
|
+
patchName,
|
|
205
|
+
description,
|
|
206
|
+
filesAffected,
|
|
207
|
+
diff,
|
|
205
208
|
config,
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
{
|
|
209
|
-
filename: placementPlan.newFilename,
|
|
210
|
-
order: placementPlan.insertionOrder,
|
|
211
|
-
category: selectedCategory,
|
|
212
|
-
name: patchName,
|
|
213
|
-
description,
|
|
214
|
-
createdAt: new Date().toISOString(),
|
|
215
|
-
...buildPatchSourceMetadata(config.firefox),
|
|
216
|
-
filesAffected,
|
|
217
|
-
...(options.tier !== undefined ? { tier: options.tier } : {}),
|
|
218
|
-
...(options.lintIgnore !== undefined && options.lintIgnore.length > 0
|
|
219
|
-
? { lintIgnore: options.lintIgnore }
|
|
220
|
-
: {}),
|
|
221
|
-
},
|
|
222
|
-
]),
|
|
223
|
-
command: 'export',
|
|
224
|
-
forceUnsafe: options.forceUnsafe === true,
|
|
209
|
+
isDryRun,
|
|
210
|
+
s,
|
|
225
211
|
});
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
// enough to warrant a prompt (more than one rename) OR when the user
|
|
230
|
-
// asked for a dry-run. The dry-run branch must always print the
|
|
231
|
-
// placement summary — previously, single-rename/no-rename dry-runs
|
|
232
|
-
// exited silently with no filename or projected layout.
|
|
233
|
-
if (renameCount > 1 || isDryRun) {
|
|
234
|
-
s.stop();
|
|
235
|
-
const decision = await confirmDestructive({
|
|
236
|
-
operation: 'export-order',
|
|
237
|
-
title: `Export with placement at order ${placementPlan.insertionOrder}`,
|
|
238
|
-
summary,
|
|
239
|
-
yes: options.yes === true,
|
|
240
|
-
dryRun: isDryRun,
|
|
241
|
-
unsafeOverride: options.forceUnsafe === true,
|
|
242
|
-
conflicts,
|
|
243
|
-
});
|
|
244
|
-
if (decision === 'dry-run') {
|
|
245
|
-
outro('Dry run complete — no changes made');
|
|
246
|
-
return;
|
|
247
|
-
}
|
|
248
|
-
if (decision === 'cancelled') {
|
|
249
|
-
outro('Export cancelled');
|
|
250
|
-
return;
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
else if (conflicts && options.forceUnsafe !== true) {
|
|
254
|
-
s.stop();
|
|
255
|
-
throw new InvalidArgumentError(`Refusing to run export: ${conflicts.reason}. Pass --force-unsafe to override.`, '--force-unsafe');
|
|
256
|
-
}
|
|
212
|
+
if (gated === 'stop')
|
|
213
|
+
return;
|
|
214
|
+
placementPlan = gated;
|
|
257
215
|
}
|
|
258
216
|
// Dry-run path: compute the plan and print it, never write.
|
|
259
217
|
if (isDryRun && !placementPlan) {
|
|
@@ -267,10 +225,7 @@ export async function exportCommand(projectRoot, files, options) {
|
|
|
267
225
|
...buildPatchSourceMetadata(config.firefox),
|
|
268
226
|
explicitSupersede: options.supersede === true,
|
|
269
227
|
allowOverlap: options.allowOverlap === true,
|
|
270
|
-
...(options
|
|
271
|
-
...(options.lintIgnore !== undefined && options.lintIgnore.length > 0
|
|
272
|
-
? { lintIgnore: options.lintIgnore }
|
|
273
|
-
: {}),
|
|
228
|
+
...patchMetadataExtras(options),
|
|
274
229
|
config,
|
|
275
230
|
forceUnsafe: options.forceUnsafe === true,
|
|
276
231
|
});
|
|
@@ -291,10 +246,7 @@ export async function exportCommand(projectRoot, files, options) {
|
|
|
291
246
|
createdAt: new Date().toISOString(),
|
|
292
247
|
...buildPatchSourceMetadata(config.firefox),
|
|
293
248
|
filesAffected,
|
|
294
|
-
...(options
|
|
295
|
-
...(options.lintIgnore !== undefined && options.lintIgnore.length > 0
|
|
296
|
-
? { lintIgnore: options.lintIgnore }
|
|
297
|
-
: {}),
|
|
249
|
+
...patchMetadataExtras(options),
|
|
298
250
|
};
|
|
299
251
|
const committedPlan = await commitPlacementExport({
|
|
300
252
|
patchesDir: paths.patches,
|
|
@@ -336,29 +288,15 @@ export async function exportCommand(projectRoot, files, options) {
|
|
|
336
288
|
return;
|
|
337
289
|
}
|
|
338
290
|
// Default (no dry-run, no placement) path: the pre-existing behavior.
|
|
339
|
-
|
|
340
|
-
const shouldProceed = await confirmSupersedePatches(paths.patches, filesAffected, options.supersede, isInteractive, s);
|
|
341
|
-
if (!shouldProceed)
|
|
342
|
-
return;
|
|
343
|
-
// Overlap gate: pre-0.16.0 `export` only caught FULL-coverage
|
|
344
|
-
// supersedes, so a second export targeting a shared file like
|
|
345
|
-
// `browser/themes/shared/jar.inc.mn` happily created a queue where
|
|
346
|
-
// two patches both listed the same file in `filesAffected`. `verify`
|
|
347
|
-
// then failed immediately on "cross-patch filesAffected conflicts".
|
|
348
|
-
// `confirmSupersedePatches` might already have confirmed full
|
|
349
|
-
// supersedes above; pass their filenames through so we do not flag
|
|
350
|
-
// a file claimed by a patch that is about to be removed.
|
|
351
|
-
const willSupersede = await findAllPatchesForFiles(paths.patches, filesAffected);
|
|
352
|
-
const supersedingFilenames = new Set(willSupersede.map((p) => p.filename));
|
|
353
|
-
const shouldProceedPastOverlap = await guardOwnershipOverlap({
|
|
291
|
+
const shouldProceedPastGates = await runSupersedeAndOverlapGates({
|
|
354
292
|
patchesDir: paths.patches,
|
|
355
293
|
filesAffected,
|
|
356
|
-
|
|
294
|
+
supersede: options.supersede,
|
|
357
295
|
allowOverlap: options.allowOverlap === true,
|
|
358
296
|
isInteractive,
|
|
359
297
|
s,
|
|
360
298
|
});
|
|
361
|
-
if (!
|
|
299
|
+
if (!shouldProceedPastGates)
|
|
362
300
|
return;
|
|
363
301
|
const { patchFilename, superseded } = await commitExportedPatch({
|
|
364
302
|
patchesDir: paths.patches,
|
|
@@ -368,10 +306,7 @@ export async function exportCommand(projectRoot, files, options) {
|
|
|
368
306
|
diff,
|
|
369
307
|
filesAffected,
|
|
370
308
|
...buildPatchSourceMetadata(config.firefox),
|
|
371
|
-
...(options
|
|
372
|
-
...(options.lintIgnore !== undefined && options.lintIgnore.length > 0
|
|
373
|
-
? { lintIgnore: options.lintIgnore }
|
|
374
|
-
: {}),
|
|
309
|
+
...patchMetadataExtras(options),
|
|
375
310
|
config,
|
|
376
311
|
policyCommand: 'export',
|
|
377
312
|
forceUnsafe: options.forceUnsafe === true,
|
|
@@ -7,19 +7,6 @@
|
|
|
7
7
|
* upstream Firefox (browser.xhtml, privatebrowsing/aboutPrivateBrowsing.html,
|
|
8
8
|
* etc.) minus the fork-specific wiring. A fork author fills in the body.
|
|
9
9
|
*/
|
|
10
|
-
/**
|
|
11
|
-
* Sentinel attribute emitted on every `furnace chrome-doc create`-scaffolded
|
|
12
|
-
* root element. Platform modules (`DevToolsStartup`, `PageActions`,
|
|
13
|
-
* `SessionStore`, `DownloadsButton`, …) that observe
|
|
14
|
-
* `browser-delayed-startup-finished` and walk INTO the window assume the
|
|
15
|
-
* `browser.xhtml` DOM and throw on anything else. A fork-authored patch
|
|
16
|
-
* to such a module can use `hasAttribute(...)` against this sentinel as
|
|
17
|
-
* a cheap, fork-neutral guard to skip the walk on a custom chrome doc.
|
|
18
|
-
*
|
|
19
|
-
* Exposed as a named constant so test code and external checks can
|
|
20
|
-
* reference the exact attribute name without hardcoding the string.
|
|
21
|
-
*/
|
|
22
|
-
export declare const FURNACE_CHROME_DOC_SENTINEL = "data-furnace-chrome-doc";
|
|
23
10
|
/**
|
|
24
11
|
* XHTML shell for a top-level chrome document.
|
|
25
12
|
*
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
* Exposed as a named constant so test code and external checks can
|
|
21
21
|
* reference the exact attribute name without hardcoding the string.
|
|
22
22
|
*/
|
|
23
|
-
|
|
23
|
+
const FURNACE_CHROME_DOC_SENTINEL = 'data-furnace-chrome-doc';
|
|
24
24
|
/**
|
|
25
25
|
* XHTML shell for a top-level chrome document.
|
|
26
26
|
*
|