@hominis/fireforge 0.40.0 → 0.42.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 +85 -0
- package/README.md +16 -10
- package/dist/bin/fireforge.js +25 -4
- package/dist/build-info.json +9 -0
- package/dist/src/cli.js +20 -3
- package/dist/src/commands/bootstrap.js +5 -1
- package/dist/src/commands/discard.js +2 -22
- package/dist/src/commands/doctor/post-rebase-audit.js +27 -4
- package/dist/src/commands/doctor-check-core.d.ts +20 -0
- package/dist/src/commands/doctor-check-core.js +28 -0
- package/dist/src/commands/doctor-external-toolchains.js +2 -0
- package/dist/src/commands/doctor-furnace-manifest-sync.js +144 -46
- package/dist/src/commands/doctor-furnace.js +13 -11
- package/dist/src/commands/doctor-orphaned-harness.js +2 -1
- package/dist/src/commands/doctor.js +2 -2
- package/dist/src/commands/export-all.js +9 -1
- package/dist/src/commands/export-flow.js +13 -3
- package/dist/src/commands/export-placement-conflicts.d.ts +17 -0
- package/dist/src/commands/export-placement-conflicts.js +74 -0
- package/dist/src/commands/export-placement-gate.js +1 -1
- package/dist/src/commands/export-shared.d.ts +8 -1
- package/dist/src/commands/export-shared.js +16 -7
- package/dist/src/commands/export.js +2 -2
- package/dist/src/commands/furnace/apply.js +4 -3
- package/dist/src/commands/furnace/chrome-doc-remove.js +13 -2
- package/dist/src/commands/furnace/chrome-doc.js +13 -2
- package/dist/src/commands/furnace/create-browser-test.d.ts +5 -0
- package/dist/src/commands/furnace/create-browser-test.js +5 -0
- package/dist/src/commands/furnace/create.js +2 -2
- package/dist/src/commands/furnace/diff.js +2 -1
- package/dist/src/commands/furnace/init.js +4 -6
- package/dist/src/commands/furnace/override.js +2 -2
- package/dist/src/commands/furnace/remove-mutations.d.ts +40 -0
- package/dist/src/commands/furnace/remove-mutations.js +171 -0
- package/dist/src/commands/furnace/remove.js +21 -128
- package/dist/src/commands/furnace/rename-browser-test.js +54 -5
- package/dist/src/commands/furnace/scan.js +27 -36
- package/dist/src/commands/furnace/status.js +30 -28
- package/dist/src/commands/furnace/validate.js +25 -18
- package/dist/src/commands/import.js +6 -20
- package/dist/src/commands/lint-per-patch.js +12 -59
- package/dist/src/commands/lint-per-run-checkjs.d.ts +64 -0
- package/dist/src/commands/lint-per-run-checkjs.js +102 -0
- package/dist/src/commands/lint.js +4 -4
- package/dist/src/commands/manifest.js +1 -1
- package/dist/src/commands/package.js +9 -16
- package/dist/src/commands/patch/compact.js +5 -5
- package/dist/src/commands/patch/delete.js +5 -5
- package/dist/src/commands/patch/lint-ignore.js +3 -2
- package/dist/src/commands/patch/move-files-create.js +22 -4
- package/dist/src/commands/patch/move-files-into.js +14 -10
- package/dist/src/commands/patch/move-files.js +6 -5
- package/dist/src/commands/patch/rename.d.ts +2 -2
- package/dist/src/commands/patch/rename.js +151 -50
- package/dist/src/commands/patch/reorder.d.ts +9 -0
- package/dist/src/commands/patch/reorder.js +14 -10
- package/dist/src/commands/patch/split-plan.d.ts +13 -6
- package/dist/src/commands/patch/split-plan.js +22 -12
- package/dist/src/commands/patch/split.d.ts +1 -1
- package/dist/src/commands/patch/split.js +23 -9
- package/dist/src/commands/patch/staged-dependency-validate.d.ts +15 -0
- package/dist/src/commands/patch/staged-dependency-validate.js +49 -0
- package/dist/src/commands/patch/staged-dependency.js +10 -0
- package/dist/src/commands/re-export-adjacent.js +5 -6
- package/dist/src/commands/re-export-bulk-scan.d.ts +13 -0
- package/dist/src/commands/re-export-bulk-scan.js +142 -2
- package/dist/src/commands/re-export-drift.d.ts +74 -0
- package/dist/src/commands/re-export-drift.js +234 -0
- package/dist/src/commands/re-export-files.js +5 -3
- package/dist/src/commands/re-export-lint.d.ts +97 -0
- package/dist/src/commands/re-export-lint.js +210 -0
- package/dist/src/commands/re-export-options.js +8 -0
- package/dist/src/commands/re-export-register.d.ts +18 -0
- package/dist/src/commands/re-export-register.js +61 -0
- package/dist/src/commands/re-export-scan.js +13 -7
- package/dist/src/commands/re-export-single.d.ts +20 -0
- package/dist/src/commands/re-export-single.js +261 -0
- package/dist/src/commands/re-export.d.ts +0 -4
- package/dist/src/commands/re-export.js +113 -268
- package/dist/src/commands/rebase/abort.js +13 -4
- package/dist/src/commands/rebase/continue.js +11 -4
- package/dist/src/commands/rebase/index.js +19 -29
- package/dist/src/commands/rebase/patch-loop.js +12 -0
- package/dist/src/commands/reset.js +4 -23
- package/dist/src/commands/resolve.js +2 -9
- package/dist/src/commands/run.js +11 -21
- package/dist/src/commands/source.js +3 -9
- package/dist/src/commands/status-check.d.ts +13 -0
- package/dist/src/commands/status-check.js +31 -13
- package/dist/src/commands/status-json.d.ts +31 -0
- package/dist/src/commands/status-json.js +76 -0
- package/dist/src/commands/status-output.d.ts +1 -0
- package/dist/src/commands/status-output.js +10 -1
- package/dist/src/commands/status-ownership.d.ts +52 -0
- package/dist/src/commands/status-ownership.js +57 -0
- package/dist/src/commands/status.js +184 -175
- package/dist/src/commands/test-build-phase.d.ts +19 -0
- package/dist/src/commands/test-build-phase.js +168 -0
- package/dist/src/commands/test-diagnose.js +30 -2
- package/dist/src/commands/test-modes.d.ts +9 -1
- package/dist/src/commands/test-modes.js +46 -13
- package/dist/src/commands/test-register.js +59 -27
- package/dist/src/commands/test-run.d.ts +29 -3
- package/dist/src/commands/test-run.js +41 -4
- package/dist/src/commands/test-verdict.d.ts +32 -0
- package/dist/src/commands/test-verdict.js +48 -0
- package/dist/src/commands/test.d.ts +9 -0
- package/dist/src/commands/test.js +100 -123
- package/dist/src/commands/token-coverage.js +4 -9
- package/dist/src/commands/tree.d.ts +3 -0
- package/dist/src/commands/tree.js +171 -25
- package/dist/src/commands/verify.js +2 -2
- package/dist/src/commands/watch.js +22 -21
- package/dist/src/commands/wire.js +1 -1
- package/dist/src/core/ast-utils.d.ts +11 -0
- package/dist/src/core/ast-utils.js +11 -0
- package/dist/src/core/branding.d.ts +16 -0
- package/dist/src/core/branding.js +18 -2
- package/dist/src/core/build-audit-platform.js +6 -1
- package/dist/src/core/build-audit-registration.js +2 -0
- package/dist/src/core/build-audit-resolve.d.ts +13 -0
- package/dist/src/core/build-audit-resolve.js +34 -13
- package/dist/src/core/build-audit.js +4 -20
- package/dist/src/core/build-baseline-types.d.ts +29 -8
- package/dist/src/core/build-baseline-types.js +2 -1
- package/dist/src/core/build-baseline.d.ts +10 -2
- package/dist/src/core/build-baseline.js +39 -8
- package/dist/src/core/build-prepare.d.ts +4 -0
- package/dist/src/core/build-prepare.js +7 -1
- package/dist/src/core/config-paths.d.ts +1 -1
- package/dist/src/core/config-paths.js +2 -0
- package/dist/src/core/config-validate-test-toolchains.js +3 -2
- package/dist/src/core/config-validate.js +34 -36
- package/dist/src/core/config.js +16 -3
- package/dist/src/core/coverage-extend.d.ts +89 -0
- package/dist/src/core/coverage-extend.js +188 -0
- package/dist/src/core/destructive.js +3 -1
- package/dist/src/core/discard-baseline.js +18 -4
- package/dist/src/core/engine-precondition.d.ts +35 -0
- package/dist/src/core/engine-precondition.js +50 -0
- package/dist/src/core/engine-session-lock.d.ts +38 -1
- package/dist/src/core/engine-session-lock.js +78 -4
- package/dist/src/core/file-lock.d.ts +7 -0
- package/dist/src/core/file-lock.js +20 -30
- package/dist/src/core/firefox.js +5 -2
- package/dist/src/core/furnace-apply-helpers.d.ts +12 -1
- package/dist/src/core/furnace-apply-helpers.js +12 -3
- package/dist/src/core/furnace-apply-output.js +9 -0
- package/dist/src/core/furnace-apply-overwrite-warn.d.ts +63 -0
- package/dist/src/core/furnace-apply-overwrite-warn.js +123 -0
- package/dist/src/core/furnace-apply-undeploy-actions.d.ts +11 -0
- package/dist/src/core/furnace-apply-undeploy-actions.js +49 -0
- package/dist/src/core/furnace-apply.d.ts +16 -4
- package/dist/src/core/furnace-apply.js +39 -43
- package/dist/src/core/furnace-config.js +2 -0
- package/dist/src/core/furnace-css-fragments.js +2 -0
- package/dist/src/core/furnace-jsconfig.js +14 -7
- package/dist/src/core/furnace-registration-ast.js +8 -8
- package/dist/src/core/furnace-registration-remove.js +6 -3
- package/dist/src/core/furnace-registration.js +9 -12
- package/dist/src/core/furnace-rollback.js +1 -1
- package/dist/src/core/furnace-scanner.js +2 -1
- package/dist/src/core/furnace-stale-export.js +2 -1
- package/dist/src/core/furnace-state-persist.d.ts +4 -6
- package/dist/src/core/furnace-validate-compatibility.js +4 -7
- package/dist/src/core/furnace-validate-helpers.js +33 -1
- package/dist/src/core/furnace-validate-registration.js +16 -0
- package/dist/src/core/furnace-validate.js +4 -2
- package/dist/src/core/git-diff.js +37 -2
- package/dist/src/core/lint-cache.d.ts +1 -1
- package/dist/src/core/lint-cache.js +33 -10
- package/dist/src/core/mach-build-artifacts.d.ts +63 -1
- package/dist/src/core/mach-build-artifacts.js +108 -2
- package/dist/src/core/mach-mozconfig.js +9 -1
- package/dist/src/core/mach-resource-shim.js +1 -0
- package/dist/src/core/marionette-port.d.ts +16 -0
- package/dist/src/core/marionette-port.js +93 -8
- package/dist/src/core/marionette-preflight.js +6 -5
- package/dist/src/core/patch-apply.d.ts +20 -13
- package/dist/src/core/patch-apply.js +32 -33
- package/dist/src/core/patch-export.js +3 -5
- package/dist/src/core/patch-lint-checkjs.d.ts +25 -3
- package/dist/src/core/patch-lint-checkjs.js +78 -7
- package/dist/src/core/patch-lint-chrome-jsdoc.js +9 -0
- package/dist/src/core/patch-lint-creators.d.ts +30 -0
- package/dist/src/core/patch-lint-creators.js +45 -0
- package/dist/src/core/patch-lint-cross.d.ts +1 -17
- package/dist/src/core/patch-lint-cross.js +16 -32
- package/dist/src/core/patch-lint-jsdoc.d.ts +3 -1
- package/dist/src/core/patch-lint-jsdoc.js +15 -5
- package/dist/src/core/patch-lint-module-registration.d.ts +15 -0
- package/dist/src/core/patch-lint-module-registration.js +88 -0
- package/dist/src/core/patch-lint-mozbuild.js +99 -11
- package/dist/src/core/patch-lint-reexports.d.ts +1 -0
- package/dist/src/core/patch-lint-reexports.js +1 -0
- package/dist/src/core/patch-lint.d.ts +8 -0
- package/dist/src/core/patch-lint.js +84 -6
- package/dist/src/core/patch-lock.d.ts +12 -1
- package/dist/src/core/patch-lock.js +40 -4
- package/dist/src/core/patch-manifest-consistency.js +15 -3
- package/dist/src/core/patch-parse.d.ts +8 -0
- package/dist/src/core/patch-parse.js +10 -0
- package/dist/src/core/patch-policy.d.ts +7 -0
- package/dist/src/core/patch-policy.js +4 -1
- package/dist/src/core/patch-transform.d.ts +10 -0
- package/dist/src/core/patch-transform.js +13 -2
- package/dist/src/core/rebase-session.d.ts +44 -2
- package/dist/src/core/rebase-session.js +114 -17
- package/dist/src/core/register-browser-content.js +2 -1
- package/dist/src/core/register-shared-css.js +2 -1
- package/dist/src/core/smoke-patterns.d.ts +0 -8
- package/dist/src/core/smoke-patterns.js +3 -2
- package/dist/src/core/status-binary.d.ts +55 -0
- package/dist/src/core/status-binary.js +124 -0
- package/dist/src/core/status-classify.d.ts +6 -1
- package/dist/src/core/status-classify.js +101 -73
- package/dist/src/core/test-harness-crash.d.ts +32 -1
- package/dist/src/core/test-harness-crash.js +59 -4
- package/dist/src/core/test-path-scope.js +2 -0
- package/dist/src/core/test-stale-check.js +1 -20
- package/dist/src/core/toolchain-preflight.js +24 -26
- package/dist/src/core/tree-guard.d.ts +1 -1
- package/dist/src/core/tree-guard.js +67 -12
- package/dist/src/core/tree-store.d.ts +86 -10
- package/dist/src/core/tree-store.js +310 -72
- package/dist/src/core/typecheck.js +2 -1
- package/dist/src/core/wire-destroy.js +14 -3
- package/dist/src/core/wire-init.js +37 -40
- package/dist/src/core/wire-subscript.js +11 -3
- package/dist/src/core/wire-utils.js +4 -4
- package/dist/src/core/xpcshell-appdir.js +3 -0
- package/dist/src/errors/base.d.ts +13 -0
- package/dist/src/errors/base.js +13 -0
- package/dist/src/errors/rebase.d.ts +13 -0
- package/dist/src/errors/rebase.js +17 -0
- package/dist/src/types/commands/options.d.ts +86 -1
- package/dist/src/types/commands/patches.d.ts +8 -0
- package/dist/src/types/furnace.d.ts +7 -0
- package/dist/src/utils/build-info.d.ts +31 -0
- package/dist/src/utils/build-info.js +98 -0
- package/dist/src/utils/concurrency.d.ts +8 -0
- package/dist/src/utils/concurrency.js +21 -0
- package/dist/src/utils/errors.d.ts +39 -0
- package/dist/src/utils/errors.js +55 -0
- package/dist/src/utils/fs.js +5 -12
- package/dist/src/utils/logger.d.ts +14 -2
- package/dist/src/utils/logger.js +43 -13
- package/dist/src/utils/options.d.ts +21 -4
- package/dist/src/utils/options.js +26 -6
- package/dist/src/utils/package-root.js +7 -4
- package/dist/src/utils/process-group.js +13 -2
- package/dist/src/utils/stdio-drain.d.ts +28 -0
- package/dist/src/utils/stdio-drain.js +54 -0
- package/dist/src/utils/validation.d.ts +23 -2
- package/dist/src/utils/validation.js +29 -3
- package/package.json +9 -9
|
@@ -10,6 +10,7 @@ import { getPersistableAppliedEntry, persistSingleComponentState, shouldPersistS
|
|
|
10
10
|
import { countEntriesWithBlockingStepErrors } from '../../core/furnace-step-errors.js';
|
|
11
11
|
import { findOverrideBaseVersionDrift, formatOverrideBaseVersionDriftError, formatOverrideBaseVersionDriftWarning, } from '../../core/furnace-version-drift.js';
|
|
12
12
|
import { FurnaceError } from '../../errors/furnace.js';
|
|
13
|
+
import { getNodeErrorCode, toError } from '../../utils/errors.js';
|
|
13
14
|
import { pathExists } from '../../utils/fs.js';
|
|
14
15
|
import { info, intro, outro, spinner, warn } from '../../utils/logger.js';
|
|
15
16
|
/** Interval (ms) for the periodic checksum poll that catches events missed by fs.watch. */
|
|
@@ -48,11 +49,11 @@ function checksumMapsEqual(a, b) {
|
|
|
48
49
|
* collapsed all causes into one string and made diagnosis difficult.
|
|
49
50
|
*/
|
|
50
51
|
function classifyWatchApplyError(err) {
|
|
51
|
-
const message = err
|
|
52
|
+
const message = toError(err).message;
|
|
52
53
|
if (err instanceof FurnaceError) {
|
|
53
54
|
return `Apply failed: ${message}`;
|
|
54
55
|
}
|
|
55
|
-
const code = err
|
|
56
|
+
const code = getNodeErrorCode(err);
|
|
56
57
|
switch (code) {
|
|
57
58
|
case 'EACCES':
|
|
58
59
|
case 'EPERM':
|
|
@@ -165,7 +166,7 @@ async function runWatchLoop(projectRoot) {
|
|
|
165
166
|
// Directory vanished between the pathExists check and fs.watch, or
|
|
166
167
|
// fs.watch otherwise refused. refreshWatchers will retry on the next
|
|
167
168
|
// poll tick.
|
|
168
|
-
warn(`Could not start watcher on ${dir}: ${err
|
|
169
|
+
warn(`Could not start watcher on ${dir}: ${toError(err).message}`);
|
|
169
170
|
return false;
|
|
170
171
|
}
|
|
171
172
|
};
|
|
@@ -7,9 +7,10 @@ import { readdir } from 'node:fs/promises';
|
|
|
7
7
|
import { join } from 'node:path';
|
|
8
8
|
import { confirm } from '@clack/prompts';
|
|
9
9
|
import { loadConfig } from '../../core/config.js';
|
|
10
|
-
import { runFurnaceMutation } from '../../core/furnace-operation.js';
|
|
10
|
+
import { recordFurnaceRollbackFailure, runFurnaceMutation, } from '../../core/furnace-operation.js';
|
|
11
11
|
import { createRollbackJournal, restoreRollbackJournalOrThrow, snapshotDir, snapshotFile, } from '../../core/furnace-rollback.js';
|
|
12
12
|
import { FurnaceError } from '../../errors/furnace.js';
|
|
13
|
+
import { toError } from '../../utils/errors.js';
|
|
13
14
|
import { pathExists, readText, removeDir, removeFile, writeText } from '../../utils/fs.js';
|
|
14
15
|
import { cancel, info, intro, isCancel, note, outro } from '../../utils/logger.js';
|
|
15
16
|
import { buildChromeDocPlan, validateChromeDocName } from './chrome-doc.js';
|
|
@@ -86,7 +87,16 @@ async function performChromeDocRemoveMutations(args) {
|
|
|
86
87
|
await removeEmptyDirIfPresent(join(args.engineDir, 'browser/base/content/test', `${args.binaryName}-xpcshell`), journal);
|
|
87
88
|
}
|
|
88
89
|
catch (error) {
|
|
89
|
-
|
|
90
|
+
try {
|
|
91
|
+
await restoreRollbackJournalOrThrow(journal, `Failed to remove chrome-doc "${args.name}"`);
|
|
92
|
+
}
|
|
93
|
+
catch (rollbackError) {
|
|
94
|
+
// Matches the seven sibling mutation sites: a failed rollback leaves the
|
|
95
|
+
// engine in an unknown state, so record the repair breadcrumb and let
|
|
96
|
+
// the rollback error win rather than discarding it.
|
|
97
|
+
await recordFurnaceRollbackFailure(args.projectRoot, 'chrome-doc-rollback', `chrome-doc "${args.name}": ${toError(rollbackError).message}`);
|
|
98
|
+
throw rollbackError;
|
|
99
|
+
}
|
|
90
100
|
throw error;
|
|
91
101
|
}
|
|
92
102
|
return { removedFiles, removedJarEntries, removedTestDir };
|
|
@@ -126,6 +136,7 @@ export async function furnaceChromeDocRemoveCommand(projectRoot, name, options =
|
|
|
126
136
|
}
|
|
127
137
|
}
|
|
128
138
|
const result = await runFurnaceMutation(projectRoot, 'chrome-doc-rollback', (ctx) => performChromeDocRemoveMutations({
|
|
139
|
+
projectRoot,
|
|
129
140
|
name,
|
|
130
141
|
engineDir,
|
|
131
142
|
plan,
|
|
@@ -19,11 +19,12 @@
|
|
|
19
19
|
import { join } from 'node:path';
|
|
20
20
|
import { loadConfig } from '../../core/config.js';
|
|
21
21
|
import { furnaceConfigExists, loadFurnaceConfig } from '../../core/furnace-config.js';
|
|
22
|
-
import { runFurnaceMutation } from '../../core/furnace-operation.js';
|
|
22
|
+
import { recordFurnaceRollbackFailure, runFurnaceMutation, } from '../../core/furnace-operation.js';
|
|
23
23
|
import { createRollbackJournal, recordCreatedDir, restoreRollbackJournalOrThrow, snapshotFile, } from '../../core/furnace-rollback.js';
|
|
24
24
|
import { DEFAULT_LICENSE, getLicenseHeader } from '../../core/license-headers.js';
|
|
25
25
|
import { InvalidArgumentError } from '../../errors/base.js';
|
|
26
26
|
import { FurnaceError } from '../../errors/furnace.js';
|
|
27
|
+
import { toError } from '../../utils/errors.js';
|
|
27
28
|
import { pathExists, readText, writeText } from '../../utils/fs.js';
|
|
28
29
|
import { intro, note, outro, warn } from '../../utils/logger.js';
|
|
29
30
|
import { generateBrowserWindowXhtml, generateChromeDocCss, generateChromeDocFtl, generateChromeDocJs, generateChromeDocXhtml, jarIncMnEntryForChromeDoc, jarMnEntriesForChromeDoc, localeJarMnEntryForChromeDoc, localesFtlWildcardCapturesScaffoldedName, } from './chrome-doc-templates.js';
|
|
@@ -273,7 +274,16 @@ async function performChromeDocMutations(args) {
|
|
|
273
274
|
}
|
|
274
275
|
}
|
|
275
276
|
catch (error) {
|
|
276
|
-
|
|
277
|
+
try {
|
|
278
|
+
await restoreRollbackJournalOrThrow(journal, `Failed to scaffold chrome-doc "${args.name}"`);
|
|
279
|
+
}
|
|
280
|
+
catch (rollbackError) {
|
|
281
|
+
// Matches the seven sibling mutation sites: when the rollback itself
|
|
282
|
+
// fails the engine is in an unknown state, so leave a breadcrumb for
|
|
283
|
+
// `doctor --repair-furnace` and let the rollback error win.
|
|
284
|
+
await recordFurnaceRollbackFailure(args.projectRoot, 'chrome-doc-rollback', `chrome-doc "${args.name}": ${toError(rollbackError).message}`);
|
|
285
|
+
throw rollbackError;
|
|
286
|
+
}
|
|
277
287
|
throw error;
|
|
278
288
|
}
|
|
279
289
|
return written;
|
|
@@ -331,6 +341,7 @@ export async function furnaceChromeDocCreateCommand(projectRoot, name, options =
|
|
|
331
341
|
return;
|
|
332
342
|
}
|
|
333
343
|
const written = await runFurnaceMutation(projectRoot, 'chrome-doc-rollback', (ctx) => performChromeDocMutations({
|
|
344
|
+
projectRoot,
|
|
334
345
|
name,
|
|
335
346
|
license,
|
|
336
347
|
engineDir,
|
|
@@ -21,7 +21,12 @@ export declare function resolveValidatedTestDir(rawTestDir: string | undefined,
|
|
|
21
21
|
*/
|
|
22
22
|
export declare function resolveTestDirOverride(raw: string): string;
|
|
23
23
|
/**
|
|
24
|
+
* Writes the browser-mochitest scaffold for a component: the test file, its
|
|
25
|
+
* `browser.toml` manifest entry, and the parent `moz.build` registration.
|
|
24
26
|
*
|
|
27
|
+
* Every write is recorded in `journal` when one is supplied, so a failure
|
|
28
|
+
* partway through is rolled back rather than leaving a half-registered test
|
|
29
|
+
* the harness will try to run.
|
|
25
30
|
*/
|
|
26
31
|
export declare function scaffoldTestFiles(componentName: string, license: ProjectLicense, forgeConfig: {
|
|
27
32
|
binaryName: string;
|
|
@@ -57,7 +57,12 @@ export function resolveTestDirOverride(raw) {
|
|
|
57
57
|
// Exported for direct unit testing of the --test-dir / collision-safety
|
|
58
58
|
// behaviour (the full create command needs a furnace project fixture).
|
|
59
59
|
/**
|
|
60
|
+
* Writes the browser-mochitest scaffold for a component: the test file, its
|
|
61
|
+
* `browser.toml` manifest entry, and the parent `moz.build` registration.
|
|
60
62
|
*
|
|
63
|
+
* Every write is recorded in `journal` when one is supplied, so a failure
|
|
64
|
+
* partway through is rolled back rather than leaving a half-registered test
|
|
65
|
+
* the harness will try to run.
|
|
61
66
|
*/
|
|
62
67
|
export async function scaffoldTestFiles(componentName, license, forgeConfig, paths, journal, testDirOverride) {
|
|
63
68
|
const strippedName = componentName.startsWith('moz-') ? componentName.slice(4) : componentName;
|
|
@@ -222,7 +222,7 @@ async function resolveDescription(isInteractive, options) {
|
|
|
222
222
|
placeholder: 'A brief description of the component',
|
|
223
223
|
});
|
|
224
224
|
if (!isCancel(descResult)) {
|
|
225
|
-
description =
|
|
225
|
+
description = descResult;
|
|
226
226
|
}
|
|
227
227
|
}
|
|
228
228
|
return description;
|
|
@@ -267,7 +267,7 @@ export async function furnaceCreateCommand(projectRoot, name, options = {}) {
|
|
|
267
267
|
cancel('Create cancelled');
|
|
268
268
|
return;
|
|
269
269
|
}
|
|
270
|
-
componentName =
|
|
270
|
+
componentName = nameResult;
|
|
271
271
|
}
|
|
272
272
|
// Load the current furnace config only after the interactive name prompt
|
|
273
273
|
// succeeds so a cancelled create in a fresh project does not strand a new
|
|
@@ -8,6 +8,7 @@ import { getFurnacePaths, loadFurnaceConfig } from '../../core/furnace-config.js
|
|
|
8
8
|
import { isComponentSourceFile, resolveFtlDir } from '../../core/furnace-constants.js';
|
|
9
9
|
import { getFileContentAtRef } from '../../core/git-file-ops.js';
|
|
10
10
|
import { FurnaceError } from '../../errors/furnace.js';
|
|
11
|
+
import { toError } from '../../utils/errors.js';
|
|
11
12
|
import { pathExists, readText } from '../../utils/fs.js';
|
|
12
13
|
import { formatErrorText, formatSuccessText, info, intro, outro } from '../../utils/logger.js';
|
|
13
14
|
/**
|
|
@@ -72,7 +73,7 @@ async function diffOverride(name, projectRoot, config) {
|
|
|
72
73
|
}
|
|
73
74
|
catch (error) {
|
|
74
75
|
throw new FurnaceError(`Cannot read baseline for "${entry.name}" at commit ${baseCommit.slice(0, 8)}: ` +
|
|
75
|
-
`${error
|
|
76
|
+
`${toError(error).message}. ` +
|
|
76
77
|
`The commit may no longer exist in the engine history (e.g. after a re-download). ` +
|
|
77
78
|
`Run "fireforge furnace refresh --reset-base ${name}" to establish a new baseline.`, name);
|
|
78
79
|
}
|
|
@@ -9,7 +9,7 @@ import { registerSharedCSS } from '../../core/register-shared-css.js';
|
|
|
9
9
|
import { getTokensCssPath } from '../../core/token-manager.js';
|
|
10
10
|
import { generateDefaultTokensCss } from '../../core/token-scaffold.js';
|
|
11
11
|
import { FurnaceError } from '../../errors/furnace.js';
|
|
12
|
-
import { toError } from '../../utils/errors.js';
|
|
12
|
+
import { getNodeErrorCode, toError } from '../../utils/errors.js';
|
|
13
13
|
import { ensureDir, pathExists, writeText } from '../../utils/fs.js';
|
|
14
14
|
import { cancel, info, intro, isCancel, note, outro, success, warn } from '../../utils/logger.js';
|
|
15
15
|
/**
|
|
@@ -77,8 +77,8 @@ async function validateFtlBasePath(value, engineDir) {
|
|
|
77
77
|
if (engineDir) {
|
|
78
78
|
const resolved = join(engineDir, value);
|
|
79
79
|
try {
|
|
80
|
-
const
|
|
81
|
-
if (!
|
|
80
|
+
const stats = await stat(resolved);
|
|
81
|
+
if (!stats.isDirectory()) {
|
|
82
82
|
throw new FurnaceError(`ftlBasePath "${value}" resolves to a non-directory at ${resolved}. FireForge expects a locale directory (for example toolkit/locales/en-US/toolkit/global or browser/locales/en-US/browser).`);
|
|
83
83
|
}
|
|
84
84
|
}
|
|
@@ -89,9 +89,7 @@ async function validateFtlBasePath(value, engineDir) {
|
|
|
89
89
|
throw error;
|
|
90
90
|
// ENOENT is expected on a fresh project before `fireforge
|
|
91
91
|
// download` has populated engine/; only warn.
|
|
92
|
-
const code =
|
|
93
|
-
? error.code
|
|
94
|
-
: undefined;
|
|
92
|
+
const code = getNodeErrorCode(error);
|
|
95
93
|
if (code === 'ENOENT') {
|
|
96
94
|
warn(`ftlBasePath "${value}" does not yet exist at ${resolved}. This is fine if you have not run "fireforge download" yet; rerun "fireforge furnace init --force" after the engine is extracted to re-validate.`);
|
|
97
95
|
}
|
|
@@ -282,7 +282,7 @@ export async function furnaceOverrideCommand(projectRoot, name, options = {}) {
|
|
|
282
282
|
placeholder: 'What are you changing about this component?',
|
|
283
283
|
});
|
|
284
284
|
if (!isCancel(descResult)) {
|
|
285
|
-
description =
|
|
285
|
+
description = descResult;
|
|
286
286
|
}
|
|
287
287
|
}
|
|
288
288
|
// --- Copy original files ---
|
|
@@ -417,7 +417,7 @@ export async function furnaceBatchOverrideCommand(projectRoot, names, options =
|
|
|
417
417
|
catch (error) {
|
|
418
418
|
failed.push({
|
|
419
419
|
name: componentName,
|
|
420
|
-
error: error
|
|
420
|
+
error: toError(error).message,
|
|
421
421
|
});
|
|
422
422
|
}
|
|
423
423
|
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { loadFurnaceConfig } from '../../core/furnace-config.js';
|
|
2
|
+
import { getFurnacePaths } from '../../core/furnace-config.js';
|
|
3
|
+
import type { RollbackJournal } from '../../core/furnace-rollback.js';
|
|
4
|
+
import type { FurnaceState } from '../../types/furnace.js';
|
|
5
|
+
/**
|
|
6
|
+
* Engine-side and workspace-side removal for an OVERRIDE component.
|
|
7
|
+
*
|
|
8
|
+
* Extracted in 0.41.0: this and its custom-component sibling were the only
|
|
9
|
+
* large inline blocks in the furnace subsystem never wrapped in a
|
|
10
|
+
* `perform*Mutations` helper, and together they held `remove.ts` at 496/500
|
|
11
|
+
* file lines with `furnaceRemoveCommand` at 149/150 — one line from a build
|
|
12
|
+
* failure. Both run inside the caller's rollback journal rather than opening
|
|
13
|
+
* their own, so a failure anywhere still restores the whole operation.
|
|
14
|
+
*/
|
|
15
|
+
export declare function performOverrideRemovalMutations(args: {
|
|
16
|
+
name: string;
|
|
17
|
+
paths: {
|
|
18
|
+
engine: string;
|
|
19
|
+
};
|
|
20
|
+
furnacePaths: ReturnType<typeof getFurnacePaths>;
|
|
21
|
+
freshConfig: Awaited<ReturnType<typeof loadFurnaceConfig>>;
|
|
22
|
+
freshState: FurnaceState;
|
|
23
|
+
ftlDir: string;
|
|
24
|
+
journal: RollbackJournal;
|
|
25
|
+
}): Promise<void>;
|
|
26
|
+
/**
|
|
27
|
+
* Engine-side and workspace-side removal for a CUSTOM component.
|
|
28
|
+
* Sibling of {@link performOverrideRemovalMutations}; see its note.
|
|
29
|
+
*/
|
|
30
|
+
export declare function performCustomRemovalMutations(args: {
|
|
31
|
+
projectRoot: string;
|
|
32
|
+
name: string;
|
|
33
|
+
paths: {
|
|
34
|
+
engine: string;
|
|
35
|
+
};
|
|
36
|
+
furnacePaths: ReturnType<typeof getFurnacePaths>;
|
|
37
|
+
freshConfig: Awaited<ReturnType<typeof loadFurnaceConfig>>;
|
|
38
|
+
ftlDir: string;
|
|
39
|
+
journal: RollbackJournal;
|
|
40
|
+
}): Promise<void>;
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* Engine-side and workspace-side mutation bodies for `furnace remove`.
|
|
4
|
+
*
|
|
5
|
+
* Split out of `remove.ts` in 0.41.0. The two `perform*RemovalMutations`
|
|
6
|
+
* functions were the only large inline blocks in the furnace subsystem never
|
|
7
|
+
* wrapped in a helper, and together they held `remove.ts` at 496/500 file
|
|
8
|
+
* lines with `furnaceRemoveCommand` at 149/150 — one line from a build
|
|
9
|
+
* failure. Extracting them inside the same file traded a function-length
|
|
10
|
+
* problem for a file-length one, so they live here instead.
|
|
11
|
+
*
|
|
12
|
+
* Both run inside the CALLER's rollback journal rather than opening their
|
|
13
|
+
* own, so a failure anywhere in the remove still restores the whole
|
|
14
|
+
* operation as a single unit.
|
|
15
|
+
*/
|
|
16
|
+
import { readdir } from 'node:fs/promises';
|
|
17
|
+
import { join } from 'node:path';
|
|
18
|
+
import { removeCustomFtlJarMnEntry } from '../../core/furnace-apply-ftl.js';
|
|
19
|
+
import { getOverrideEngineTargetPath, isOverrideCopyCandidate, restoreOverrideFileToBaseline, } from '../../core/furnace-apply-helpers.js';
|
|
20
|
+
import { extractComponentChecksums } from '../../core/furnace-checksum-utils.js';
|
|
21
|
+
import { getFurnacePaths } from '../../core/furnace-config.js';
|
|
22
|
+
import { removeCustomElementRegistration, removeJarMnEntries, } from '../../core/furnace-registration.js';
|
|
23
|
+
import { snapshotDir, snapshotFile } from '../../core/furnace-rollback.js';
|
|
24
|
+
import { isGitRepository } from '../../core/git.js';
|
|
25
|
+
import { FurnaceError } from '../../errors/furnace.js';
|
|
26
|
+
import { pathExists, removeDir, removeFile } from '../../utils/fs.js';
|
|
27
|
+
import { info } from '../../utils/logger.js';
|
|
28
|
+
import { removeDeployedCustomFiles } from './remove-state.js';
|
|
29
|
+
/**
|
|
30
|
+
* Restores every override-deployed file in `engine/` to its pristine HEAD
|
|
31
|
+
* state, inverting what `applyOverrideComponent` would have written. Files that
|
|
32
|
+
* existed in HEAD are restored via `git restore`; files the override
|
|
33
|
+
* introduced (not in HEAD) are deleted outright.
|
|
34
|
+
*
|
|
35
|
+
* The restore set is the **union** of (a) files currently in the override
|
|
36
|
+
* workspace directory and (b) filenames recorded in `previousChecksumKeys`
|
|
37
|
+
* — i.e. files we know we deployed last time, even if the developer has
|
|
38
|
+
* since deleted them from the workspace. Without (b), a workspace deletion
|
|
39
|
+
* leaves an orphaned engine copy that `furnace remove` would never see.
|
|
40
|
+
*
|
|
41
|
+
* Every touched engine file is snapshotted into the rollback journal before
|
|
42
|
+
* mutation so a mid-remove failure still rolls the engine back to its
|
|
43
|
+
* pre-command state.
|
|
44
|
+
*/
|
|
45
|
+
async function restoreOverrideEngineFiles(engineDir, overrideDir, overrideConfig, previousChecksumKeys, ftlDir, journal) {
|
|
46
|
+
// Engine-as-git is a hard precondition for restoration: git HEAD is the only
|
|
47
|
+
// honest oracle for "what was there before the override". If the engine is
|
|
48
|
+
// not a git repo we refuse rather than silently leaving files behind — the
|
|
49
|
+
// previous warn-and-continue behaviour is exactly what this fix removes.
|
|
50
|
+
if (!(await isGitRepository(engineDir))) {
|
|
51
|
+
throw new FurnaceError('Cannot restore override files: engine is not a git repository. Run "fireforge download" to initialise it.');
|
|
52
|
+
}
|
|
53
|
+
// Build the union of "files we still see on disk" and "files state.json
|
|
54
|
+
// claims we deployed". The state set is the only authority for files that
|
|
55
|
+
// were deployed and later deleted from the workspace; the workspace set is
|
|
56
|
+
// the only authority for files added since last apply that have not yet
|
|
57
|
+
// been recorded in state. We need both.
|
|
58
|
+
const fileSet = new Set();
|
|
59
|
+
if (await pathExists(overrideDir)) {
|
|
60
|
+
const entries = await readdir(overrideDir, { withFileTypes: true });
|
|
61
|
+
for (const entry of entries) {
|
|
62
|
+
if (!entry.isFile())
|
|
63
|
+
continue;
|
|
64
|
+
if (!isOverrideCopyCandidate(entry.name, overrideConfig.type))
|
|
65
|
+
continue;
|
|
66
|
+
fileSet.add(entry.name);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
for (const key of previousChecksumKeys) {
|
|
70
|
+
fileSet.add(key);
|
|
71
|
+
}
|
|
72
|
+
let restored = 0;
|
|
73
|
+
let removed = 0;
|
|
74
|
+
for (const fileName of fileSet) {
|
|
75
|
+
const enginePath = getOverrideEngineTargetPath(engineDir, overrideConfig, fileName, ftlDir);
|
|
76
|
+
const action = await restoreOverrideFileToBaseline(engineDir, enginePath, journal);
|
|
77
|
+
if (action === 'restored')
|
|
78
|
+
restored += 1;
|
|
79
|
+
else if (action === 'removed')
|
|
80
|
+
removed += 1;
|
|
81
|
+
}
|
|
82
|
+
return { restored, removed };
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Engine-side and workspace-side removal for an OVERRIDE component.
|
|
86
|
+
*
|
|
87
|
+
* Extracted in 0.41.0: this and its custom-component sibling were the only
|
|
88
|
+
* large inline blocks in the furnace subsystem never wrapped in a
|
|
89
|
+
* `perform*Mutations` helper, and together they held `remove.ts` at 496/500
|
|
90
|
+
* file lines with `furnaceRemoveCommand` at 149/150 — one line from a build
|
|
91
|
+
* failure. Both run inside the caller's rollback journal rather than opening
|
|
92
|
+
* their own, so a failure anywhere still restores the whole operation.
|
|
93
|
+
*/
|
|
94
|
+
export async function performOverrideRemovalMutations(args) {
|
|
95
|
+
const { name, paths, furnacePaths, freshConfig, freshState, ftlDir, journal } = args;
|
|
96
|
+
const overrideConfig = freshConfig.overrides[name];
|
|
97
|
+
const dir = join(furnacePaths.overridesDir, name);
|
|
98
|
+
// Restore deployed engine files BEFORE removing the workspace
|
|
99
|
+
// directory. The restore set is the union of (a) files currently in
|
|
100
|
+
// the workspace and (b) files state.json says we deployed last time
|
|
101
|
+
// — without (b), source-side deletions would orphan engine copies
|
|
102
|
+
// that this command can never see again.
|
|
103
|
+
if (overrideConfig?.basePath) {
|
|
104
|
+
const previousKeys = Object.keys(extractComponentChecksums(freshState.appliedChecksums, 'override', name));
|
|
105
|
+
const { restored, removed } = await restoreOverrideEngineFiles(paths.engine, dir, overrideConfig, previousKeys, ftlDir, journal);
|
|
106
|
+
if (restored > 0) {
|
|
107
|
+
info(`Restored ${restored} file${restored === 1 ? '' : 's'} in engine/${overrideConfig.basePath} to Firefox baseline`);
|
|
108
|
+
}
|
|
109
|
+
if (removed > 0) {
|
|
110
|
+
info(`Removed ${removed} override-introduced file${removed === 1 ? '' : 's'} from engine/${overrideConfig.basePath}`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
if (await pathExists(dir)) {
|
|
114
|
+
await snapshotDir(journal, dir);
|
|
115
|
+
await removeDir(dir);
|
|
116
|
+
info(`Deleted components/overrides/${name}/`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Engine-side and workspace-side removal for a CUSTOM component.
|
|
121
|
+
* Sibling of {@link performOverrideRemovalMutations}; see its note.
|
|
122
|
+
*/
|
|
123
|
+
export async function performCustomRemovalMutations(args) {
|
|
124
|
+
const { projectRoot, name, paths, furnacePaths, freshConfig, ftlDir, journal } = args;
|
|
125
|
+
const customConfig = freshConfig.custom[name];
|
|
126
|
+
// Custom-component removal mutates engine files (jar.mn,
|
|
127
|
+
// customElements.js, deployed widgets, optional .ftl) and the
|
|
128
|
+
// rollback journal is the only safety net for those edits while
|
|
129
|
+
// the command runs. The git-as-engine precondition is enforced
|
|
130
|
+
// before the lock is acquired (see furnaceRemoveCommand above)
|
|
131
|
+
// so if we reach this point, the engine is a git repository.
|
|
132
|
+
if (customConfig?.register) {
|
|
133
|
+
// customElements.js is the only file removeCustomElementRegistration touches.
|
|
134
|
+
await snapshotFile(journal, join(paths.engine, 'toolkit/content/customElements.js'));
|
|
135
|
+
await removeCustomElementRegistration(paths.engine, name);
|
|
136
|
+
info(`Deregistered ${name} from customElements.js`);
|
|
137
|
+
}
|
|
138
|
+
// jar.mn is the only file removeJarMnEntries touches.
|
|
139
|
+
await snapshotFile(journal, join(paths.engine, 'toolkit/content/jar.mn'));
|
|
140
|
+
await removeJarMnEntries(paths.engine, name);
|
|
141
|
+
info(`Removed ${name} entries from toolkit/content/jar.mn`);
|
|
142
|
+
const dir = join(furnacePaths.customDir, name);
|
|
143
|
+
if (await pathExists(dir)) {
|
|
144
|
+
await snapshotDir(journal, dir);
|
|
145
|
+
await removeDir(dir);
|
|
146
|
+
info(`Deleted components/custom/${name}/`);
|
|
147
|
+
}
|
|
148
|
+
// Clean up deployed files in engine (per-file — see helper doc).
|
|
149
|
+
if (customConfig?.targetPath) {
|
|
150
|
+
await removeDeployedCustomFiles(projectRoot, paths.engine, name, customConfig.targetPath, journal);
|
|
151
|
+
}
|
|
152
|
+
// Localized components deploy a .ftl outside targetPath into the
|
|
153
|
+
// shared Fluent tree; apply writes it, so remove must delete it too
|
|
154
|
+
// or the locale payload is orphaned.
|
|
155
|
+
if (customConfig?.localized) {
|
|
156
|
+
const ftlRel = join(ftlDir, `${name}.ftl`);
|
|
157
|
+
const ftlPath = join(paths.engine, ftlRel);
|
|
158
|
+
if (await pathExists(ftlPath)) {
|
|
159
|
+
await snapshotFile(journal, ftlPath);
|
|
160
|
+
await removeFile(ftlPath);
|
|
161
|
+
info(`Deleted localized file engine/${ftlRel}`);
|
|
162
|
+
}
|
|
163
|
+
// Drop the locale jar.mn chrome registration that `applyCustomFtlFile`
|
|
164
|
+
// wrote during deploy — otherwise the engine is left with a
|
|
165
|
+
// `locale/.../${name}.ftl` entry pointing at a file we just
|
|
166
|
+
// deleted. 2026-04-21 eval (Finding #1): `furnace remove` left
|
|
167
|
+
// `browser/locales/jar.mn` referencing the missing FTL, which
|
|
168
|
+
// would break the next package-manifest validation.
|
|
169
|
+
await removeCustomFtlJarMnEntry(paths.engine, `${name}.ftl`, ftlDir, customConfig, journal);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
@@ -3,20 +3,18 @@ import { readdir, unlink } from 'node:fs/promises';
|
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
import { confirm } from '@clack/prompts';
|
|
5
5
|
import { getProjectPaths, loadConfig } from '../../core/config.js';
|
|
6
|
-
import { removeCustomFtlJarMnEntry } from '../../core/furnace-apply-ftl.js';
|
|
7
|
-
import { extractComponentChecksums, getOverrideEngineTargetPath, isOverrideCopyCandidate, restoreOverrideFileToBaseline, } from '../../core/furnace-apply-helpers.js';
|
|
8
6
|
import { getFurnacePaths, loadFurnaceConfig, loadFurnaceState, updateFurnaceState, writeFurnaceConfig, } from '../../core/furnace-config.js';
|
|
9
7
|
import { resolveFtlDir, xpcshellTestParentDir } from '../../core/furnace-constants.js';
|
|
10
8
|
import { recordFurnaceRollbackFailure, runFurnaceMutation } from '../../core/furnace-operation.js';
|
|
11
|
-
import { removeCustomElementRegistration, removeJarMnEntries, } from '../../core/furnace-registration.js';
|
|
12
9
|
import { createRollbackJournal, restoreRollbackJournalOrThrow, snapshotDir, snapshotFile, } from '../../core/furnace-rollback.js';
|
|
13
10
|
import { isGitRepository } from '../../core/git.js';
|
|
14
11
|
import { deregisterTestManifest } from '../../core/manifest-register.js';
|
|
15
12
|
import { FurnaceError } from '../../errors/furnace.js';
|
|
16
13
|
import { toError } from '../../utils/errors.js';
|
|
17
|
-
import { pathExists, readText, removeDir,
|
|
14
|
+
import { pathExists, readText, removeDir, writeText } from '../../utils/fs.js';
|
|
18
15
|
import { cancel, info, intro, isCancel, outro, warn } from '../../utils/logger.js';
|
|
19
|
-
import {
|
|
16
|
+
import { performCustomRemovalMutations, performOverrideRemovalMutations, } from './remove-mutations.js';
|
|
17
|
+
import { dropChecksumsByPrefix } from './remove-state.js';
|
|
20
18
|
/**
|
|
21
19
|
* Removes an entire TOML section (header + body lines) for a given test file.
|
|
22
20
|
* Matches from `["filename"]` up to the next section header `[` or end-of-file,
|
|
@@ -69,61 +67,6 @@ function findComponentType(config, name) {
|
|
|
69
67
|
return 'custom';
|
|
70
68
|
return undefined;
|
|
71
69
|
}
|
|
72
|
-
/**
|
|
73
|
-
* Restores every override-deployed file in `engine/` to its pristine HEAD
|
|
74
|
-
* state, inverting what `applyOverrideComponent` would have written. Files that
|
|
75
|
-
* existed in HEAD are restored via `git restore`; files the override
|
|
76
|
-
* introduced (not in HEAD) are deleted outright.
|
|
77
|
-
*
|
|
78
|
-
* The restore set is the **union** of (a) files currently in the override
|
|
79
|
-
* workspace directory and (b) filenames recorded in `previousChecksumKeys`
|
|
80
|
-
* — i.e. files we know we deployed last time, even if the developer has
|
|
81
|
-
* since deleted them from the workspace. Without (b), a workspace deletion
|
|
82
|
-
* leaves an orphaned engine copy that `furnace remove` would never see.
|
|
83
|
-
*
|
|
84
|
-
* Every touched engine file is snapshotted into the rollback journal before
|
|
85
|
-
* mutation so a mid-remove failure still rolls the engine back to its
|
|
86
|
-
* pre-command state.
|
|
87
|
-
*/
|
|
88
|
-
async function restoreOverrideEngineFiles(engineDir, overrideDir, overrideConfig, previousChecksumKeys, ftlDir, journal) {
|
|
89
|
-
// Engine-as-git is a hard precondition for restoration: git HEAD is the only
|
|
90
|
-
// honest oracle for "what was there before the override". If the engine is
|
|
91
|
-
// not a git repo we refuse rather than silently leaving files behind — the
|
|
92
|
-
// previous warn-and-continue behaviour is exactly what this fix removes.
|
|
93
|
-
if (!(await isGitRepository(engineDir))) {
|
|
94
|
-
throw new FurnaceError('Cannot restore override files: engine is not a git repository. Run "fireforge download" to initialise it.');
|
|
95
|
-
}
|
|
96
|
-
// Build the union of "files we still see on disk" and "files state.json
|
|
97
|
-
// claims we deployed". The state set is the only authority for files that
|
|
98
|
-
// were deployed and later deleted from the workspace; the workspace set is
|
|
99
|
-
// the only authority for files added since last apply that have not yet
|
|
100
|
-
// been recorded in state. We need both.
|
|
101
|
-
const fileSet = new Set();
|
|
102
|
-
if (await pathExists(overrideDir)) {
|
|
103
|
-
const entries = await readdir(overrideDir, { withFileTypes: true });
|
|
104
|
-
for (const entry of entries) {
|
|
105
|
-
if (!entry.isFile())
|
|
106
|
-
continue;
|
|
107
|
-
if (!isOverrideCopyCandidate(entry.name, overrideConfig.type))
|
|
108
|
-
continue;
|
|
109
|
-
fileSet.add(entry.name);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
for (const key of previousChecksumKeys) {
|
|
113
|
-
fileSet.add(key);
|
|
114
|
-
}
|
|
115
|
-
let restored = 0;
|
|
116
|
-
let removed = 0;
|
|
117
|
-
for (const fileName of fileSet) {
|
|
118
|
-
const enginePath = getOverrideEngineTargetPath(engineDir, overrideConfig, fileName, ftlDir);
|
|
119
|
-
const action = await restoreOverrideFileToBaseline(engineDir, enginePath, journal);
|
|
120
|
-
if (action === 'restored')
|
|
121
|
-
restored += 1;
|
|
122
|
-
else if (action === 'removed')
|
|
123
|
-
removed += 1;
|
|
124
|
-
}
|
|
125
|
-
return { restored, removed };
|
|
126
|
-
}
|
|
127
70
|
/**
|
|
128
71
|
* Removes generated browser mochitest files associated with a custom component.
|
|
129
72
|
* @param name - Custom component tag name
|
|
@@ -410,76 +353,26 @@ export async function furnaceRemoveCommand(projectRoot, name, options = {}) {
|
|
|
410
353
|
try {
|
|
411
354
|
const { config: freshConfig, ftlDir, state: freshState, type: freshType, } = await loadFreshRemoveTarget(projectRoot, name, paths.engine);
|
|
412
355
|
if (freshType === 'override') {
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
const { restored, removed } = await restoreOverrideEngineFiles(paths.engine, dir, overrideConfig, previousKeys, ftlDir, journal);
|
|
423
|
-
if (restored > 0) {
|
|
424
|
-
info(`Restored ${restored} file${restored === 1 ? '' : 's'} in engine/${overrideConfig.basePath} to Firefox baseline`);
|
|
425
|
-
}
|
|
426
|
-
if (removed > 0) {
|
|
427
|
-
info(`Removed ${removed} override-introduced file${removed === 1 ? '' : 's'} from engine/${overrideConfig.basePath}`);
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
if (await pathExists(dir)) {
|
|
431
|
-
await snapshotDir(journal, dir);
|
|
432
|
-
await removeDir(dir);
|
|
433
|
-
info(`Deleted components/overrides/${name}/`);
|
|
434
|
-
}
|
|
356
|
+
await performOverrideRemovalMutations({
|
|
357
|
+
name,
|
|
358
|
+
paths,
|
|
359
|
+
furnacePaths,
|
|
360
|
+
freshConfig,
|
|
361
|
+
freshState,
|
|
362
|
+
ftlDir,
|
|
363
|
+
journal,
|
|
364
|
+
});
|
|
435
365
|
}
|
|
436
366
|
else if (freshType === 'custom') {
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
await snapshotFile(journal, join(paths.engine, 'toolkit/content/customElements.js'));
|
|
447
|
-
await removeCustomElementRegistration(paths.engine, name);
|
|
448
|
-
info(`Deregistered ${name} from customElements.js`);
|
|
449
|
-
}
|
|
450
|
-
// jar.mn is the only file removeJarMnEntries touches.
|
|
451
|
-
await snapshotFile(journal, join(paths.engine, 'toolkit/content/jar.mn'));
|
|
452
|
-
await removeJarMnEntries(paths.engine, name);
|
|
453
|
-
info(`Removed ${name} entries from toolkit/content/jar.mn`);
|
|
454
|
-
const dir = join(furnacePaths.customDir, name);
|
|
455
|
-
if (await pathExists(dir)) {
|
|
456
|
-
await snapshotDir(journal, dir);
|
|
457
|
-
await removeDir(dir);
|
|
458
|
-
info(`Deleted components/custom/${name}/`);
|
|
459
|
-
}
|
|
460
|
-
// Clean up deployed files in engine (per-file — see helper doc).
|
|
461
|
-
if (customConfig?.targetPath) {
|
|
462
|
-
await removeDeployedCustomFiles(projectRoot, paths.engine, name, customConfig.targetPath, journal);
|
|
463
|
-
}
|
|
464
|
-
// Localized components deploy a .ftl outside targetPath into the
|
|
465
|
-
// shared Fluent tree; apply writes it, so remove must delete it too
|
|
466
|
-
// or the locale payload is orphaned.
|
|
467
|
-
if (customConfig?.localized) {
|
|
468
|
-
const ftlRel = join(ftlDir, `${name}.ftl`);
|
|
469
|
-
const ftlPath = join(paths.engine, ftlRel);
|
|
470
|
-
if (await pathExists(ftlPath)) {
|
|
471
|
-
await snapshotFile(journal, ftlPath);
|
|
472
|
-
await removeFile(ftlPath);
|
|
473
|
-
info(`Deleted localized file engine/${ftlRel}`);
|
|
474
|
-
}
|
|
475
|
-
// Drop the locale jar.mn chrome registration that `applyCustomFtlFile`
|
|
476
|
-
// wrote during deploy — otherwise the engine is left with a
|
|
477
|
-
// `locale/.../${name}.ftl` entry pointing at a file we just
|
|
478
|
-
// deleted. 2026-04-21 eval (Finding #1): `furnace remove` left
|
|
479
|
-
// `browser/locales/jar.mn` referencing the missing FTL, which
|
|
480
|
-
// would break the next package-manifest validation.
|
|
481
|
-
await removeCustomFtlJarMnEntry(paths.engine, `${name}.ftl`, ftlDir, customConfig, journal);
|
|
482
|
-
}
|
|
367
|
+
await performCustomRemovalMutations({
|
|
368
|
+
projectRoot,
|
|
369
|
+
name,
|
|
370
|
+
paths,
|
|
371
|
+
furnacePaths,
|
|
372
|
+
freshConfig,
|
|
373
|
+
ftlDir,
|
|
374
|
+
journal,
|
|
375
|
+
});
|
|
483
376
|
}
|
|
484
377
|
const testCleanupFailures = freshType === 'custom' ? await cleanupAllCustomTestFiles(name, projectRoot, journal) : [];
|
|
485
378
|
// Remove entry from furnace.json
|