@hominis/fireforge 0.35.0 → 0.36.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 +29 -0
- package/README.md +34 -9
- package/dist/bin/fireforge.js +21 -6
- package/dist/src/cli.js +22 -5
- package/dist/src/commands/bootstrap-checks.js +0 -1
- package/dist/src/commands/bootstrap.d.ts +2 -1
- package/dist/src/commands/bootstrap.js +16 -7
- package/dist/src/commands/build.js +4 -3
- package/dist/src/commands/config.js +30 -10
- package/dist/src/commands/discard.js +18 -3
- package/dist/src/commands/doctor/post-rebase-audit.js +0 -1
- package/dist/src/commands/doctor-check-core.js +0 -1
- package/dist/src/commands/doctor-external-toolchains.d.ts +3 -0
- package/dist/src/commands/doctor-external-toolchains.js +54 -0
- package/dist/src/commands/doctor-furnace-jar.js +0 -1
- package/dist/src/commands/doctor-furnace-manifest-sync.js +0 -1
- package/dist/src/commands/doctor-furnace.js +2 -2
- package/dist/src/commands/doctor-working-tree.js +0 -1
- package/dist/src/commands/doctor.js +4 -1
- package/dist/src/commands/download.js +28 -13
- package/dist/src/commands/export-all.js +5 -3
- package/dist/src/commands/export-flow.js +12 -7
- package/dist/src/commands/export-placement-gate.js +0 -1
- package/dist/src/commands/export-placement-policy.js +0 -1
- package/dist/src/commands/export-shared.d.ts +5 -1
- package/dist/src/commands/export-shared.js +18 -6
- package/dist/src/commands/export.js +15 -10
- package/dist/src/commands/furnace/apply.js +22 -6
- package/dist/src/commands/furnace/chrome-doc-remove.js +0 -1
- package/dist/src/commands/furnace/chrome-doc-templates.js +0 -1
- package/dist/src/commands/furnace/chrome-doc-tests.js +0 -1
- package/dist/src/commands/furnace/chrome-doc.js +0 -1
- package/dist/src/commands/furnace/create-browser-test.js +0 -1
- package/dist/src/commands/furnace/create-dry-run.js +0 -1
- package/dist/src/commands/furnace/create-features.js +0 -1
- package/dist/src/commands/furnace/create-mochikit.js +0 -1
- package/dist/src/commands/furnace/create-readback.js +0 -1
- package/dist/src/commands/furnace/create-templates.js +0 -1
- package/dist/src/commands/furnace/create-validation.js +0 -1
- package/dist/src/commands/furnace/create-xpcshell.js +0 -1
- package/dist/src/commands/furnace/create.js +0 -1
- package/dist/src/commands/furnace/deploy.js +7 -83
- package/dist/src/commands/furnace/diff.js +0 -1
- package/dist/src/commands/furnace/index.js +59 -25
- package/dist/src/commands/furnace/init.js +0 -1
- package/dist/src/commands/furnace/list.js +0 -1
- package/dist/src/commands/furnace/override.js +0 -1
- package/dist/src/commands/furnace/preview.js +3 -3
- package/dist/src/commands/furnace/refresh.js +0 -1
- package/dist/src/commands/furnace/remove-state.d.ts +15 -0
- package/dist/src/commands/furnace/remove-state.js +47 -1
- package/dist/src/commands/furnace/remove.js +3 -9
- package/dist/src/commands/furnace/rename-browser-test.js +1 -5
- package/dist/src/commands/furnace/rename-helpers.d.ts +6 -0
- package/dist/src/commands/furnace/rename-helpers.js +30 -1
- package/dist/src/commands/furnace/rename-xpcshell.js +7 -6
- package/dist/src/commands/furnace/rename.js +15 -36
- package/dist/src/commands/furnace/scan.js +0 -1
- package/dist/src/commands/furnace/status.js +0 -1
- package/dist/src/commands/furnace/sync.js +76 -4
- package/dist/src/commands/furnace/validate.js +0 -1
- package/dist/src/commands/furnace/validation-output.js +0 -1
- package/dist/src/commands/import.js +35 -9
- package/dist/src/commands/lint-per-patch.js +19 -7
- package/dist/src/commands/lint.js +13 -3
- package/dist/src/commands/manifest.js +1 -2
- package/dist/src/commands/package.js +2 -1
- package/dist/src/commands/patch/compact.js +1 -1
- package/dist/src/commands/patch/delete.js +1 -1
- package/dist/src/commands/patch/index.js +1 -1
- package/dist/src/commands/patch/lint-ignore.js +21 -6
- package/dist/src/commands/patch/move-files.js +1 -1
- package/dist/src/commands/patch/patch-context.js +0 -1
- package/dist/src/commands/patch/rename.js +4 -4
- package/dist/src/commands/patch/reorder.js +3 -4
- package/dist/src/commands/patch/split-plan.js +2 -1
- package/dist/src/commands/patch/split.js +4 -4
- package/dist/src/commands/patch/staged-dependency.js +1 -1
- package/dist/src/commands/patch/tier.js +18 -5
- package/dist/src/commands/re-export-bulk-scan.js +0 -1
- package/dist/src/commands/re-export-files.js +1 -1
- package/dist/src/commands/re-export-options.js +0 -1
- package/dist/src/commands/re-export-scan.js +0 -1
- package/dist/src/commands/re-export.js +6 -4
- package/dist/src/commands/rebase/abort.js +2 -9
- package/dist/src/commands/rebase/confirm.js +0 -1
- package/dist/src/commands/rebase/conflict-summary.js +0 -1
- package/dist/src/commands/rebase/continue.js +0 -1
- package/dist/src/commands/rebase/index.d.ts +1 -1
- package/dist/src/commands/rebase/index.js +10 -15
- package/dist/src/commands/rebase/patch-loop.js +38 -7
- package/dist/src/commands/rebase/summary.js +2 -3
- package/dist/src/commands/rebase.js +0 -1
- package/dist/src/commands/register.js +1 -1
- package/dist/src/commands/reset.js +16 -9
- package/dist/src/commands/resolve.js +2 -2
- package/dist/src/commands/run.js +4 -4
- package/dist/src/commands/setup-support.js +0 -1
- package/dist/src/commands/setup.js +1 -2
- package/dist/src/commands/source.js +1 -2
- package/dist/src/commands/status-output.js +1 -12
- package/dist/src/commands/status.js +147 -133
- package/dist/src/commands/test-appdir.js +0 -1
- package/dist/src/commands/test-diagnose.js +18 -2
- package/dist/src/commands/test-modes.d.ts +15 -0
- package/dist/src/commands/test-modes.js +61 -0
- package/dist/src/commands/test-register.d.ts +4 -0
- package/dist/src/commands/test-register.js +64 -0
- package/dist/src/commands/test-run.d.ts +23 -7
- package/dist/src/commands/test-run.js +15 -14
- package/dist/src/commands/test.d.ts +0 -4
- package/dist/src/commands/test.js +85 -86
- package/dist/src/commands/token-coverage.js +0 -1
- package/dist/src/commands/token.js +1 -2
- package/dist/src/commands/typecheck.js +1 -1
- package/dist/src/commands/verify.js +3 -9
- package/dist/src/commands/watch.js +5 -1
- package/dist/src/commands/wire.js +1 -1
- package/dist/src/core/ast-utils.js +0 -1
- package/dist/src/core/brand-validation.js +0 -1
- package/dist/src/core/branding.js +0 -1
- package/dist/src/core/browser-wire.js +0 -1
- package/dist/src/core/build-audit-platform.js +0 -1
- package/dist/src/core/build-audit-registration.js +0 -1
- package/dist/src/core/build-audit-resolve.js +0 -1
- package/dist/src/core/build-audit-transforms.js +0 -1
- package/dist/src/core/build-audit.js +0 -1
- package/dist/src/core/build-baseline-types.js +0 -1
- package/dist/src/core/build-baseline.js +0 -1
- package/dist/src/core/build-prepare.js +2 -2
- package/dist/src/core/config-mutate.js +0 -1
- package/dist/src/core/config-paths.d.ts +2 -2
- package/dist/src/core/config-paths.js +6 -1
- package/dist/src/core/config-state.js +0 -1
- package/dist/src/core/config-validate-patch-policy.js +0 -1
- package/dist/src/core/config-validate-test-toolchains.d.ts +8 -0
- package/dist/src/core/config-validate-test-toolchains.js +105 -0
- package/dist/src/core/config-validate.js +3 -1
- package/dist/src/core/config.js +0 -1
- package/dist/src/core/destructive.js +0 -1
- package/dist/src/core/diff-hunks.js +13 -7
- package/dist/src/core/engine-changes.js +0 -1
- package/dist/src/core/engine-session-lock.d.ts +12 -0
- package/dist/src/core/engine-session-lock.js +45 -0
- package/dist/src/core/file-lock.d.ts +8 -0
- package/dist/src/core/file-lock.js +157 -48
- package/dist/src/core/firefox-archive.d.ts +4 -0
- package/dist/src/core/firefox-archive.js +6 -1
- package/dist/src/core/firefox-cache.js +60 -4
- package/dist/src/core/firefox-download.js +0 -1
- package/dist/src/core/firefox-extract.js +75 -16
- package/dist/src/core/firefox-ignorefile.js +0 -1
- package/dist/src/core/firefox.d.ts +12 -0
- package/dist/src/core/firefox.js +64 -2
- package/dist/src/core/furnace-apply-dry-run.js +0 -1
- package/dist/src/core/furnace-apply-ftl.d.ts +7 -3
- package/dist/src/core/furnace-apply-ftl.js +10 -5
- package/dist/src/core/furnace-apply-helpers.js +0 -1
- package/dist/src/core/furnace-apply-output.js +2 -2
- package/dist/src/core/furnace-apply.d.ts +7 -1
- package/dist/src/core/furnace-apply.js +47 -13
- package/dist/src/core/furnace-checksum-utils.js +0 -1
- package/dist/src/core/furnace-config-array-utils.js +0 -1
- package/dist/src/core/furnace-config-custom.js +0 -1
- package/dist/src/core/furnace-config-order.js +0 -1
- package/dist/src/core/furnace-config-tokens.js +0 -1
- package/dist/src/core/furnace-config-validate.js +0 -1
- package/dist/src/core/furnace-config.d.ts +11 -0
- package/dist/src/core/furnace-config.js +37 -1
- package/dist/src/core/furnace-constants.js +0 -1
- package/dist/src/core/furnace-css-fragments.js +0 -1
- package/dist/src/core/furnace-graph-utils.js +0 -1
- package/dist/src/core/furnace-jsconfig.d.ts +2 -3
- package/dist/src/core/furnace-jsconfig.js +25 -18
- package/dist/src/core/furnace-marker.js +0 -1
- package/dist/src/core/furnace-operation.js +0 -1
- package/dist/src/core/furnace-refresh.js +0 -1
- package/dist/src/core/furnace-registration-ast.d.ts +14 -0
- package/dist/src/core/furnace-registration-ast.js +1 -2
- package/dist/src/core/furnace-registration-remove.js +0 -1
- package/dist/src/core/furnace-registration-validate.js +1 -5
- package/dist/src/core/furnace-registration.js +0 -1
- package/dist/src/core/furnace-rollback.js +0 -1
- package/dist/src/core/furnace-scanner.js +0 -1
- package/dist/src/core/furnace-staleness.js +0 -1
- package/dist/src/core/furnace-state-persist.d.ts +72 -0
- package/dist/src/core/furnace-state-persist.js +118 -0
- package/dist/src/core/furnace-step-errors.d.ts +23 -0
- package/dist/src/core/furnace-step-errors.js +25 -0
- package/dist/src/core/furnace-stories.js +0 -1
- package/dist/src/core/furnace-validate-accessibility.js +0 -1
- package/dist/src/core/furnace-validate-checks.js +0 -1
- package/dist/src/core/furnace-validate-compatibility.js +0 -1
- package/dist/src/core/furnace-validate-helpers.js +0 -1
- package/dist/src/core/furnace-validate-registration.js +7 -7
- package/dist/src/core/furnace-validate-structure.d.ts +8 -0
- package/dist/src/core/furnace-validate-structure.js +11 -2
- package/dist/src/core/furnace-validate.js +0 -1
- package/dist/src/core/furnace-version-drift.d.ts +6 -3
- package/dist/src/core/furnace-version-drift.js +6 -4
- package/dist/src/core/git-base.js +0 -1
- package/dist/src/core/git-diff.js +0 -1
- package/dist/src/core/git-file-ops.js +0 -1
- package/dist/src/core/git-status.d.ts +13 -0
- package/dist/src/core/git-status.js +32 -2
- package/dist/src/core/git.d.ts +10 -1
- package/dist/src/core/git.js +63 -6
- package/dist/src/core/license-headers.js +0 -1
- package/dist/src/core/lint-cache.js +0 -1
- package/dist/src/core/lint-projection.js +0 -1
- package/dist/src/core/mach-build-artifacts.js +0 -1
- package/dist/src/core/mach-error-hints.js +0 -1
- package/dist/src/core/mach-mozconfig.js +0 -1
- package/dist/src/core/mach-python.js +0 -1
- package/dist/src/core/mach-resource-shim.js +0 -1
- package/dist/src/core/mach.js +0 -1
- package/dist/src/core/manifest-helpers.js +0 -1
- package/dist/src/core/manifest-register.js +0 -1
- package/dist/src/core/manifest-rules.js +0 -1
- package/dist/src/core/manifest-tokenizers.js +0 -1
- package/dist/src/core/marionette-port.d.ts +8 -0
- package/dist/src/core/marionette-port.js +34 -3
- package/dist/src/core/marionette-preflight.js +0 -1
- package/dist/src/core/markdown-table.js +0 -1
- package/dist/src/core/ownership-table.js +2 -9
- package/dist/src/core/parser-fallback.js +0 -1
- package/dist/src/core/patch-apply-fuzz.d.ts +27 -9
- package/dist/src/core/patch-apply-fuzz.js +71 -34
- package/dist/src/core/patch-apply.d.ts +30 -0
- package/dist/src/core/patch-apply.js +101 -18
- package/dist/src/core/patch-artifact-normalize.d.ts +18 -5
- package/dist/src/core/patch-artifact-normalize.js +19 -15
- package/dist/src/core/patch-export-coverage.js +0 -1
- package/dist/src/core/patch-export-metadata.js +0 -1
- package/dist/src/core/patch-export-update.js +2 -3
- package/dist/src/core/patch-export.d.ts +2 -6
- package/dist/src/core/patch-export.js +24 -48
- package/dist/src/core/patch-files.js +0 -1
- package/dist/src/core/patch-identifier-suggest.js +0 -1
- package/dist/src/core/patch-lint-checkjs.js +0 -1
- package/dist/src/core/patch-lint-chrome-jsdoc.js +0 -1
- package/dist/src/core/patch-lint-cross.js +0 -1
- package/dist/src/core/patch-lint-css.js +0 -1
- package/dist/src/core/patch-lint-diff-tag.js +0 -1
- package/dist/src/core/patch-lint-diff.d.ts +2 -1
- package/dist/src/core/patch-lint-diff.js +17 -32
- package/dist/src/core/patch-lint-imports.js +0 -1
- package/dist/src/core/patch-lint-jsdoc.js +0 -1
- package/dist/src/core/patch-lint-observer.js +0 -1
- package/dist/src/core/patch-lint-ownership.js +0 -1
- package/dist/src/core/patch-lint-reexports.js +0 -1
- package/dist/src/core/patch-lint.d.ts +37 -2
- package/dist/src/core/patch-lint.js +47 -34
- package/dist/src/core/patch-lock.js +0 -1
- package/dist/src/core/patch-manifest-consistency.js +9 -1
- package/dist/src/core/patch-manifest-io.d.ts +16 -0
- package/dist/src/core/patch-manifest-io.js +30 -5
- package/dist/src/core/patch-manifest-query.js +18 -10
- package/dist/src/core/patch-manifest-resolve.js +0 -1
- package/dist/src/core/patch-manifest-validate.js +0 -1
- package/dist/src/core/patch-manifest.d.ts +1 -1
- package/dist/src/core/patch-manifest.js +1 -2
- package/dist/src/core/patch-parse.d.ts +59 -0
- package/dist/src/core/patch-parse.js +209 -92
- package/dist/src/core/patch-policy.js +0 -1
- package/dist/src/core/patch-registration-refs.js +16 -26
- package/dist/src/core/patch-source-metadata.js +0 -1
- package/dist/src/core/patch-transform.d.ts +4 -0
- package/dist/src/core/patch-transform.js +30 -38
- package/dist/src/core/rebase-session.js +0 -1
- package/dist/src/core/register-browser-content.js +0 -1
- package/dist/src/core/register-module.js +0 -1
- package/dist/src/core/register-result.js +0 -1
- package/dist/src/core/register-scaffold.js +0 -1
- package/dist/src/core/register-shared-css.js +0 -1
- package/dist/src/core/register-test-manifest.js +0 -1
- package/dist/src/core/register-xpcshell-test.js +0 -1
- package/dist/src/core/shared-ftl.js +0 -1
- package/dist/src/core/signal-critical.js +0 -1
- package/dist/src/core/smoke-patterns.js +0 -1
- package/dist/src/core/state-file.js +0 -1
- package/dist/src/core/status-classify.d.ts +16 -0
- package/dist/src/core/status-classify.js +24 -16
- package/dist/src/core/test-harness-crash.d.ts +64 -7
- package/dist/src/core/test-harness-crash.js +196 -15
- package/dist/src/core/test-harness-output.js +0 -1
- package/dist/src/core/test-path-scope.d.ts +21 -13
- package/dist/src/core/test-path-scope.js +43 -28
- package/dist/src/core/test-stale-check.js +0 -1
- package/dist/src/core/test-stale-symlink.js +0 -1
- package/dist/src/core/test-xpcshell-retry.js +0 -1
- package/dist/src/core/token-coverage.js +0 -1
- package/dist/src/core/token-dark-mode.js +0 -1
- package/dist/src/core/token-docs.js +0 -1
- package/dist/src/core/token-manager.js +0 -1
- package/dist/src/core/token-scaffold.js +0 -1
- package/dist/src/core/token-variant.js +0 -1
- package/dist/src/core/toolchain-preflight.d.ts +31 -10
- package/dist/src/core/toolchain-preflight.js +97 -36
- package/dist/src/core/typecheck-shim.js +13 -1
- package/dist/src/core/typecheck.js +0 -1
- package/dist/src/core/wire-destroy.js +0 -1
- package/dist/src/core/wire-dom-fragment.js +0 -1
- package/dist/src/core/wire-init.js +0 -1
- package/dist/src/core/wire-subscript.js +0 -1
- package/dist/src/core/wire-targets.js +0 -1
- package/dist/src/core/wire-utils.js +0 -1
- package/dist/src/core/xpcshell-appdir.js +1 -4
- package/dist/src/errors/base.d.ts +29 -0
- package/dist/src/errors/base.js +35 -1
- package/dist/src/errors/build.js +0 -1
- package/dist/src/errors/codes.d.ts +6 -0
- package/dist/src/errors/codes.js +6 -1
- package/dist/src/errors/config.js +0 -1
- package/dist/src/errors/download.js +0 -1
- package/dist/src/errors/furnace.d.ts +11 -1
- package/dist/src/errors/furnace.js +6 -5
- package/dist/src/errors/git.js +0 -1
- package/dist/src/errors/patch.d.ts +17 -0
- package/dist/src/errors/patch.js +29 -1
- package/dist/src/errors/rebase.js +0 -1
- package/dist/src/errors/run.js +1 -1
- package/dist/src/index.js +0 -1
- package/dist/src/types/cli.js +0 -1
- package/dist/src/types/commands/index.js +0 -1
- package/dist/src/types/commands/options.d.ts +13 -5
- package/dist/src/types/commands/options.js +0 -1
- package/dist/src/types/commands/patches.d.ts +8 -0
- package/dist/src/types/commands/patches.js +0 -1
- package/dist/src/types/commands/project.d.ts +11 -2
- package/dist/src/types/commands/project.js +0 -1
- package/dist/src/types/config.d.ts +32 -0
- package/dist/src/types/config.js +0 -1
- package/dist/src/types/furnace.d.ts +10 -0
- package/dist/src/types/furnace.js +0 -1
- package/dist/src/types/index.js +0 -1
- package/dist/src/types/typecheck.js +0 -1
- package/dist/src/utils/elapsed.js +0 -1
- package/dist/src/utils/errors.js +0 -1
- package/dist/src/utils/fs.js +37 -5
- package/dist/src/utils/logger.d.ts +7 -0
- package/dist/src/utils/logger.js +85 -3
- package/dist/src/utils/options.d.ts +15 -0
- package/dist/src/utils/options.js +25 -1
- package/dist/src/utils/package-root.js +11 -4
- package/dist/src/utils/parse.js +0 -1
- package/dist/src/utils/paths.js +0 -1
- package/dist/src/utils/platform.js +0 -1
- package/dist/src/utils/process.d.ts +33 -4
- package/dist/src/utils/process.js +183 -28
- package/dist/src/utils/regex.d.ts +13 -3
- package/dist/src/utils/regex.js +53 -4
- package/dist/src/utils/validation.d.ts +0 -12
- package/dist/src/utils/validation.js +13 -26
- package/package.json +8 -7
|
@@ -6,7 +6,7 @@ import { join } from 'node:path';
|
|
|
6
6
|
import { ExtractionError } from '../errors/download.js';
|
|
7
7
|
import { elapsedSince } from '../utils/elapsed.js';
|
|
8
8
|
import { ensureDir, pathExists } from '../utils/fs.js';
|
|
9
|
-
import { exec, executableExists } from '../utils/process.js';
|
|
9
|
+
import { exec, execStream, executableExists } from '../utils/process.js';
|
|
10
10
|
/**
|
|
11
11
|
* Returns true when an archive member path could land outside the
|
|
12
12
|
* extraction root: absolute (POSIX or Windows drive/backslash rooted) or
|
|
@@ -88,26 +88,86 @@ export function findUnsafeArchiveLink(verboseLines) {
|
|
|
88
88
|
* (~38%) to this one-time extraction step, versus ~44 s if run
|
|
89
89
|
* sequentially.
|
|
90
90
|
*/
|
|
91
|
-
|
|
91
|
+
/**
|
|
92
|
+
* Incremental line splitter for streamed listings. Keeps only the current
|
|
93
|
+
* partial line in memory, so a 350k-entry Firefox listing costs O(longest
|
|
94
|
+
* line), not O(listing).
|
|
95
|
+
*/
|
|
96
|
+
function createLineScanner(onLine) {
|
|
97
|
+
let buffer = '';
|
|
98
|
+
return {
|
|
99
|
+
push(chunk) {
|
|
100
|
+
buffer += chunk;
|
|
101
|
+
let idx;
|
|
102
|
+
while ((idx = buffer.indexOf('\n')) !== -1) {
|
|
103
|
+
onLine(buffer.slice(0, idx));
|
|
104
|
+
buffer = buffer.slice(idx + 1);
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
flush() {
|
|
108
|
+
if (buffer.length > 0) {
|
|
109
|
+
onLine(buffer);
|
|
110
|
+
buffer = '';
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Runs one tar listing pass, streaming lines through `checkLine` and
|
|
117
|
+
* returning the first unsafe finding (or null) plus the exit code.
|
|
118
|
+
*
|
|
119
|
+
* Streaming matters for safety, not just memory: the buffered `exec`
|
|
120
|
+
* collector silently truncates at 50 MB, and a full Firefox `-tvf` listing
|
|
121
|
+
* already sits in the 40–50 MB range — so the buffered implementation could
|
|
122
|
+
* scan only the head of the listing while reporting the archive safe. A
|
|
123
|
+
* crafted archive could exploit exactly that by padding benign entries
|
|
124
|
+
* first and hiding a traversal name or absolute link target past the cap.
|
|
125
|
+
* With per-line streaming EVERY entry is scanned, with bounded memory.
|
|
126
|
+
*/
|
|
127
|
+
async function runListingScan(archivePath, tarFlag, checkLine) {
|
|
92
128
|
// LC_ALL=C keeps the -tvf column format stable for the link parse.
|
|
93
129
|
const listEnv = { LC_ALL: 'C', LANG: 'C' };
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
130
|
+
let unsafe = null;
|
|
131
|
+
let stderrTail = '';
|
|
132
|
+
const scanner = createLineScanner((line) => {
|
|
133
|
+
if (unsafe === null) {
|
|
134
|
+
unsafe = checkLine(line);
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
const exitCode = await execStream('tar', [tarFlag, archivePath], {
|
|
138
|
+
env: listEnv,
|
|
139
|
+
onStdout: (chunk) => {
|
|
140
|
+
scanner.push(chunk);
|
|
141
|
+
},
|
|
142
|
+
onStderr: (chunk) => {
|
|
143
|
+
// Keep a small diagnostic tail; the listing itself is the big stream.
|
|
144
|
+
stderrTail = (stderrTail + chunk).slice(-4096);
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
scanner.flush();
|
|
148
|
+
return { unsafe, exitCode, stderrTail };
|
|
149
|
+
}
|
|
150
|
+
async function preflightArchiveEntries(archivePath) {
|
|
151
|
+
const [nameScan, linkScan] = await Promise.all([
|
|
152
|
+
runListingScan(archivePath, '-tf', (line) => {
|
|
153
|
+
const name = line.trim();
|
|
154
|
+
if (name.length === 0)
|
|
155
|
+
return null;
|
|
156
|
+
return isUnsafeArchivePath(name) ? name : null;
|
|
157
|
+
}),
|
|
158
|
+
runListingScan(archivePath, '-tvf', (line) => findUnsafeArchiveLink([line])),
|
|
97
159
|
]);
|
|
98
|
-
if (
|
|
99
|
-
throw new ExtractionError(archivePath, new Error(`tar -tf preflight exited with code ${
|
|
160
|
+
if (nameScan.exitCode !== 0) {
|
|
161
|
+
throw new ExtractionError(archivePath, new Error(`tar -tf preflight exited with code ${nameScan.exitCode}:\n${nameScan.stderrTail}`));
|
|
100
162
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
throw new ExtractionError(archivePath, new Error(`Archive rejected: member name could escape the extraction root: ${unsafeName}`));
|
|
163
|
+
if (nameScan.unsafe !== null) {
|
|
164
|
+
throw new ExtractionError(archivePath, new Error(`Archive rejected: member name could escape the extraction root: ${nameScan.unsafe}`));
|
|
104
165
|
}
|
|
105
|
-
if (
|
|
106
|
-
throw new ExtractionError(archivePath, new Error(`tar -tvf preflight exited with code ${
|
|
166
|
+
if (linkScan.exitCode !== 0) {
|
|
167
|
+
throw new ExtractionError(archivePath, new Error(`tar -tvf preflight exited with code ${linkScan.exitCode}:\n${linkScan.stderrTail}`));
|
|
107
168
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
throw new ExtractionError(archivePath, new Error(`Archive rejected: link could escape the extraction root (${unsafeLink})`));
|
|
169
|
+
if (linkScan.unsafe !== null) {
|
|
170
|
+
throw new ExtractionError(archivePath, new Error(`Archive rejected: link could escape the extraction root (${linkScan.unsafe})`));
|
|
111
171
|
}
|
|
112
172
|
}
|
|
113
173
|
/**
|
|
@@ -172,4 +232,3 @@ export function formatBytes(bytes) {
|
|
|
172
232
|
}
|
|
173
233
|
return `${size.toFixed(1)} ${units[unitIndex]}`;
|
|
174
234
|
}
|
|
175
|
-
//# sourceMappingURL=firefox-extract.js.map
|
|
@@ -35,6 +35,18 @@ export type FirefoxSourcePhase = 'download' | 'extract';
|
|
|
35
35
|
/** Callback fired at phase transitions during {@link downloadFirefoxSource}. */
|
|
36
36
|
export type FirefoxSourcePhaseCallback = (phase: FirefoxSourcePhase) => void;
|
|
37
37
|
export type FirefoxSourceProgressCallback = (message: string) => void;
|
|
38
|
+
/**
|
|
39
|
+
* Sweeps orphaned working directories left behind by interrupted downloads
|
|
40
|
+
* (`<engine>.tmp-*` from extraction, `<engine>.replacement-*` from --force
|
|
41
|
+
* replacement). Both hold multi-GB partial trees, are worthless after the
|
|
42
|
+
* owning process died, and nothing else ever reclaimed them. Deliberately
|
|
43
|
+
* does NOT touch `<engine>.backup-*`: a backup may hold the operator's
|
|
44
|
+
* previous engine after a failed forced replacement and is their recovery
|
|
45
|
+
* copy — callers should surface its existence instead.
|
|
46
|
+
*
|
|
47
|
+
* @returns names of the directories that were removed
|
|
48
|
+
*/
|
|
49
|
+
export declare function sweepOrphanedEngineWorkDirs(destDir: string): Promise<string[]>;
|
|
38
50
|
/**
|
|
39
51
|
* Downloads and extracts Firefox source.
|
|
40
52
|
* @param version - Firefox version to download
|
package/dist/src/core/firefox.js
CHANGED
|
@@ -33,6 +33,63 @@ export function getDownloadUrl(version, product = 'firefox') {
|
|
|
33
33
|
export function getTarballFilename(version, product = 'firefox') {
|
|
34
34
|
return resolveArchive(version, product).filename;
|
|
35
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* Classifies an extraction failure as archive corruption (invalidate the
|
|
38
|
+
* cache and re-download) versus an environmental problem (keep the cached
|
|
39
|
+
* archive; the retry can reuse it). Environmental signatures are the
|
|
40
|
+
* conservative list — anything unrecognized is treated as corruption so a
|
|
41
|
+
* genuinely bad archive can never survive in the cache.
|
|
42
|
+
*/
|
|
43
|
+
function isLikelyArchiveCorruptionError(error) {
|
|
44
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
45
|
+
const code = error instanceof Error ? error.code : undefined;
|
|
46
|
+
if (code === 'ENOSPC' || code === 'EACCES' || code === 'EPERM' || code === 'EDQUOT') {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
if (/No space left on device|Permission denied|Disk quota exceeded/i.test(message)) {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
// Missing tar binary / preflight refusing an unsafe archive both carry
|
|
53
|
+
// ExtractionError text that names the condition; a missing tar is
|
|
54
|
+
// environmental, an unsafe-entry rejection indicts the archive.
|
|
55
|
+
if (/"tar" command was not found/i.test(message)) {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Sweeps orphaned working directories left behind by interrupted downloads
|
|
62
|
+
* (`<engine>.tmp-*` from extraction, `<engine>.replacement-*` from --force
|
|
63
|
+
* replacement). Both hold multi-GB partial trees, are worthless after the
|
|
64
|
+
* owning process died, and nothing else ever reclaimed them. Deliberately
|
|
65
|
+
* does NOT touch `<engine>.backup-*`: a backup may hold the operator's
|
|
66
|
+
* previous engine after a failed forced replacement and is their recovery
|
|
67
|
+
* copy — callers should surface its existence instead.
|
|
68
|
+
*
|
|
69
|
+
* @returns names of the directories that were removed
|
|
70
|
+
*/
|
|
71
|
+
export async function sweepOrphanedEngineWorkDirs(destDir) {
|
|
72
|
+
const { readdir } = await import('node:fs/promises');
|
|
73
|
+
const { basename, dirname } = await import('node:path');
|
|
74
|
+
const parent = dirname(destDir);
|
|
75
|
+
const enginePrefixTmp = `${basename(destDir)}.tmp-`;
|
|
76
|
+
const enginePrefixReplacement = `${basename(destDir)}.replacement-`;
|
|
77
|
+
let entries;
|
|
78
|
+
try {
|
|
79
|
+
entries = await readdir(parent);
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return [];
|
|
83
|
+
}
|
|
84
|
+
const removed = [];
|
|
85
|
+
for (const name of entries) {
|
|
86
|
+
if (name.startsWith(enginePrefixTmp) || name.startsWith(enginePrefixReplacement)) {
|
|
87
|
+
await removeDir(join(parent, name));
|
|
88
|
+
removed.push(name);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return removed;
|
|
92
|
+
}
|
|
36
93
|
/**
|
|
37
94
|
* Downloads and extracts Firefox source.
|
|
38
95
|
* @param version - Firefox version to download
|
|
@@ -60,7 +117,13 @@ export async function downloadFirefoxSource(version, product, destDir, cacheDir,
|
|
|
60
117
|
}
|
|
61
118
|
catch (error) {
|
|
62
119
|
await removeDir(tempDir);
|
|
63
|
-
|
|
120
|
+
// Only throw away the ~600 MB cached archive when the failure indicts
|
|
121
|
+
// the ARCHIVE, not the environment. ENOSPC, a permission problem in
|
|
122
|
+
// destDir, or tar disappearing mid-run used to invalidate a perfectly
|
|
123
|
+
// good download and force a full re-fetch on retry.
|
|
124
|
+
if (isLikelyArchiveCorruptionError(error)) {
|
|
125
|
+
await invalidateArchiveCache(archive, cacheDir);
|
|
126
|
+
}
|
|
64
127
|
throw error;
|
|
65
128
|
}
|
|
66
129
|
// Firefox source extracts to a subdirectory (e.g., firefox-140.0/)
|
|
@@ -80,4 +143,3 @@ export async function downloadFirefoxSource(version, product, destDir, cacheDir,
|
|
|
80
143
|
await rename(tempDir, destDir);
|
|
81
144
|
}
|
|
82
145
|
}
|
|
83
|
-
//# sourceMappingURL=firefox.js.map
|
|
@@ -4,9 +4,13 @@
|
|
|
4
4
|
* per-file LOC budget.
|
|
5
5
|
*
|
|
6
6
|
* Every helper here degrades gracefully: if the locale jar.mn is missing or
|
|
7
|
-
* the FTL tree is non-standard, apply logs
|
|
8
|
-
* aborting the whole command.
|
|
9
|
-
*
|
|
7
|
+
* the FTL tree is non-standard, apply logs an ADVISORY `stepError`
|
|
8
|
+
* (`advisory: true`) rather than aborting the whole command. Advisory step
|
|
9
|
+
* errors are reported as warnings and never trigger rollback — missing
|
|
10
|
+
* jar.mn on a fork without a locale package should not block a working
|
|
11
|
+
* `.mjs`/`.css` from shipping (the blocking/advisory split exists because
|
|
12
|
+
* this contract used to be contradicted: any FTL step error rolled back
|
|
13
|
+
* the whole apply).
|
|
10
14
|
*/
|
|
11
15
|
import type { CustomComponentConfig, DryRunAction, StepError } from '../types/furnace.js';
|
|
12
16
|
import { type RollbackJournal } from './furnace-rollback.js';
|
|
@@ -5,9 +5,13 @@
|
|
|
5
5
|
* per-file LOC budget.
|
|
6
6
|
*
|
|
7
7
|
* Every helper here degrades gracefully: if the locale jar.mn is missing or
|
|
8
|
-
* the FTL tree is non-standard, apply logs
|
|
9
|
-
* aborting the whole command.
|
|
10
|
-
*
|
|
8
|
+
* the FTL tree is non-standard, apply logs an ADVISORY `stepError`
|
|
9
|
+
* (`advisory: true`) rather than aborting the whole command. Advisory step
|
|
10
|
+
* errors are reported as warnings and never trigger rollback — missing
|
|
11
|
+
* jar.mn on a fork without a locale package should not block a working
|
|
12
|
+
* `.mjs`/`.css` from shipping (the blocking/advisory split exists because
|
|
13
|
+
* this contract used to be contradicted: any FTL step error rolled back
|
|
14
|
+
* the whole apply).
|
|
11
15
|
*/
|
|
12
16
|
import { join, relative } from 'node:path';
|
|
13
17
|
import { toError } from '../utils/errors.js';
|
|
@@ -86,7 +90,7 @@ export async function applySharedFtlPrune(engineDir, name, ftlDir, config, affec
|
|
|
86
90
|
affectedPaths.push(prunedPath);
|
|
87
91
|
}
|
|
88
92
|
catch (error) {
|
|
89
|
-
stepErrors.push({ step: 'locale jar.mn prune', error: toError(error).message });
|
|
93
|
+
stepErrors.push({ step: 'locale jar.mn prune', error: toError(error).message, advisory: true });
|
|
90
94
|
}
|
|
91
95
|
}
|
|
92
96
|
/**
|
|
@@ -139,6 +143,7 @@ export async function applyCustomFtlFile(engineDir, name, componentDir, ftlDir,
|
|
|
139
143
|
stepErrors.push({
|
|
140
144
|
step: 'locale jar.mn registration',
|
|
141
145
|
error: `Locale jar.mn not found at ${localeJarRel}; component "${name}" ships without a chrome URI for ${ftlFile}. Add the file manually or set furnace.json "ftlBasePath" to a tree that owns a jar.mn.`,
|
|
146
|
+
advisory: true,
|
|
142
147
|
});
|
|
143
148
|
return;
|
|
144
149
|
}
|
|
@@ -155,6 +160,7 @@ export async function applyCustomFtlFile(engineDir, name, componentDir, ftlDir,
|
|
|
155
160
|
stepErrors.push({
|
|
156
161
|
step: 'locale jar.mn registration',
|
|
157
162
|
error: toError(error).message,
|
|
163
|
+
advisory: true,
|
|
158
164
|
});
|
|
159
165
|
}
|
|
160
166
|
}
|
|
@@ -199,4 +205,3 @@ export async function removeCustomFtlJarMnEntry(engineDir, fileName, ftlDir, con
|
|
|
199
205
|
}
|
|
200
206
|
await removeLocaleFtlJarMnEntry(engineDir, localeJarRel, tagName, chromeSubPath);
|
|
201
207
|
}
|
|
202
|
-
//# sourceMappingURL=furnace-apply-ftl.js.map
|
|
@@ -421,4 +421,3 @@ export async function applyOverrideComponent(engineDir, name, componentDir, conf
|
|
|
421
421
|
return { affectedPaths };
|
|
422
422
|
}
|
|
423
423
|
export { diffDeletedFiles, extractComponentChecksums, prefixChecksums, } from './furnace-checksum-utils.js';
|
|
424
|
-
//# sourceMappingURL=furnace-apply-helpers.js.map
|
|
@@ -45,7 +45,8 @@ export function logApplyResult(result, isDryRun) {
|
|
|
45
45
|
for (const applied of result.applied) {
|
|
46
46
|
if (applied.stepErrors && applied.stepErrors.length > 0) {
|
|
47
47
|
for (const stepErr of applied.stepErrors) {
|
|
48
|
-
|
|
48
|
+
const advisoryPrefix = stepErr.advisory === true ? '(advisory) ' : '';
|
|
49
|
+
warn(`${applied.name}: ${advisoryPrefix}[${stepErr.step}] ${stepErr.error}`);
|
|
49
50
|
}
|
|
50
51
|
}
|
|
51
52
|
}
|
|
@@ -54,4 +55,3 @@ export function logApplyResult(result, isDryRun) {
|
|
|
54
55
|
error(`${err.name} — ${err.error}`);
|
|
55
56
|
}
|
|
56
57
|
}
|
|
57
|
-
//# sourceMappingURL=furnace-apply-output.js.map
|
|
@@ -31,4 +31,10 @@ export declare function applyAllComponents(root: string, dryRun?: boolean, optio
|
|
|
31
31
|
}): Promise<ApplyResult & {
|
|
32
32
|
actions?: DryRunAction[];
|
|
33
33
|
rollbackJournal?: RollbackJournal;
|
|
34
|
-
}>;
|
|
34
|
+
}>; /**
|
|
35
|
+
* Shared context threaded through the batch apply loops. Bundles the state
|
|
36
|
+
* both loops mutate (result/actions/checksums) with the immutable run
|
|
37
|
+
* parameters — the previous 12–13 positional parameters let call sites
|
|
38
|
+
* drift (the named-apply state-wipe bug) and made every new parameter a
|
|
39
|
+
* two-signature change.
|
|
40
|
+
*/
|
|
@@ -13,6 +13,7 @@ import { resolveFurnaceMarkerComment } from './furnace-marker.js';
|
|
|
13
13
|
import { recordFurnaceRollbackFailure } from './furnace-operation.js';
|
|
14
14
|
import { addJarMnEntries, removeCustomElementRegistration, removeJarMnEntries, } from './furnace-registration.js';
|
|
15
15
|
import { createRollbackJournal, restoreRollbackJournalOrThrow, snapshotFile, } from './furnace-rollback.js';
|
|
16
|
+
import { blockingStepErrors, hasBlockingStepErrors } from './furnace-step-errors.js';
|
|
16
17
|
import { runPostApplyConsistencyChecks } from './furnace-validate-registration.js';
|
|
17
18
|
export { computeComponentChecksums, extractComponentChecksums, hasComponentChanged, hasCustomEngineDrift, hasOverrideEngineDrift, prefixChecksums, } from './furnace-apply-helpers.js';
|
|
18
19
|
function addMissingComponentError(result, name, directoryPath) {
|
|
@@ -29,7 +30,9 @@ function buildOverrideUndeployActions(name, config, engineDir, deletedFiles, ftl
|
|
|
29
30
|
description: `Restore engine/${fileName.endsWith('.ftl') ? `${ftlDir}/${fileName}` : `${config.basePath}/${fileName}`} to Firefox baseline`,
|
|
30
31
|
}));
|
|
31
32
|
}
|
|
32
|
-
async function applyOverrideBatch(
|
|
33
|
+
async function applyOverrideBatch(ctx) {
|
|
34
|
+
const { config, furnacePaths, state, engineDir, ftlDir, dryRun } = ctx;
|
|
35
|
+
const { result, allActions, newChecksums, rollbackJournal, componentName } = ctx;
|
|
33
36
|
const overrideEntries = Object.entries(config.overrides).filter(([name]) => !componentName || name === componentName);
|
|
34
37
|
const totalOverrides = overrideEntries.length;
|
|
35
38
|
let overrideIndex = 0;
|
|
@@ -162,7 +165,9 @@ async function reconcileCustomRegistrationAfterUndeploy(engineDir, name, config,
|
|
|
162
165
|
filesAffected.push(CUSTOM_ELEMENTS_JS);
|
|
163
166
|
}
|
|
164
167
|
}
|
|
165
|
-
async function applyCustomBatch(
|
|
168
|
+
async function applyCustomBatch(ctx, root, markerComment) {
|
|
169
|
+
const { config, furnacePaths, state, engineDir, ftlDir, dryRun } = ctx;
|
|
170
|
+
const { result, allActions, newChecksums, rollbackJournal, componentName } = ctx;
|
|
166
171
|
const allKnown = new Set([
|
|
167
172
|
...config.stock,
|
|
168
173
|
...Object.keys(config.overrides),
|
|
@@ -251,7 +256,7 @@ async function applyCustomBatch(root, config, furnacePaths, state, engineDir, ft
|
|
|
251
256
|
if (dryRun && actions) {
|
|
252
257
|
allActions.push(...actions);
|
|
253
258
|
}
|
|
254
|
-
if (!dryRun && deletedFiles.length > 0 && stepErrors.length === 0) {
|
|
259
|
+
if (!dryRun && deletedFiles.length > 0 && blockingStepErrors(stepErrors).length === 0) {
|
|
255
260
|
await reconcileCustomRegistrationAfterUndeploy(engineDir, name, customConfig, deletedFiles, currentChecksums, rollbackJournal, filesAffectedTotal);
|
|
256
261
|
}
|
|
257
262
|
filesAffectedTotal.push(...filesAffected);
|
|
@@ -261,9 +266,11 @@ async function applyCustomBatch(root, config, furnacePaths, state, engineDir, ft
|
|
|
261
266
|
filesAffected: filesAffectedTotal,
|
|
262
267
|
...(stepErrors.length > 0 ? { stepErrors } : {}),
|
|
263
268
|
});
|
|
264
|
-
// Only store checksums when the component applied without
|
|
265
|
-
// so that partially failed components are re-applied on
|
|
266
|
-
|
|
269
|
+
// Only store checksums when the component applied without BLOCKING
|
|
270
|
+
// step errors, so that partially failed components are re-applied on
|
|
271
|
+
// the next run. Advisory errors (FTL degradation) still persist —
|
|
272
|
+
// re-applying cannot fix a fork that has no locale jar.mn.
|
|
273
|
+
if (!dryRun && blockingStepErrors(stepErrors).length === 0) {
|
|
267
274
|
Object.assign(newChecksums, prefixChecksums(currentChecksums, 'custom', name));
|
|
268
275
|
}
|
|
269
276
|
}
|
|
@@ -338,16 +345,38 @@ export async function applyAllComponents(root, dryRun = false, options) {
|
|
|
338
345
|
throw new FurnaceError(`Component "${componentName}" not found in furnace.json. Run "fireforge furnace list" to see registered components.`, componentName);
|
|
339
346
|
}
|
|
340
347
|
}
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
348
|
+
const batchContext = {
|
|
349
|
+
config,
|
|
350
|
+
furnacePaths,
|
|
351
|
+
state,
|
|
352
|
+
engineDir,
|
|
353
|
+
ftlDir,
|
|
354
|
+
dryRun,
|
|
355
|
+
result,
|
|
356
|
+
allActions,
|
|
357
|
+
newChecksums,
|
|
358
|
+
rollbackJournal,
|
|
359
|
+
componentName,
|
|
360
|
+
};
|
|
361
|
+
await applyOverrideBatch(batchContext);
|
|
362
|
+
await applyCustomBatch(batchContext, root, markerComment);
|
|
363
|
+
// Check for any partial failures (blocking step errors on applied
|
|
364
|
+
// components). Advisory step errors (e.g. FTL degradation) are warnings
|
|
365
|
+
// and never gate rollback.
|
|
366
|
+
const hasApplyStepErrors = result.applied.some((entry) => hasBlockingStepErrors(entry));
|
|
345
367
|
// Orphaned components are implicitly cleaned up: newChecksums only
|
|
346
368
|
// contains entries for components that still exist in furnace.json,
|
|
347
369
|
// and it fully replaces state.appliedChecksums below.
|
|
348
|
-
if (!dryRun && !
|
|
370
|
+
if (!dryRun && !hasApplyStepErrors && result.errors.length === 0) {
|
|
349
371
|
await runPostApplyConsistencyChecks(root, config, result, ftlDir);
|
|
350
372
|
}
|
|
373
|
+
// Recompute AFTER the consistency checks: they MUTATE entry.stepErrors
|
|
374
|
+
// when the applied output is inconsistent (e.g. a jar.mn entry that
|
|
375
|
+
// silently mis-landed). Reading the pre-check snapshot meant those
|
|
376
|
+
// failures skipped rollback and persisted checksums for a component
|
|
377
|
+
// known to be inconsistent — while the CLI simultaneously reported
|
|
378
|
+
// "failed to apply cleanly".
|
|
379
|
+
const hasStepErrors = result.applied.some((entry) => hasBlockingStepErrors(entry));
|
|
351
380
|
// --- Rollback on failure, persist on success (skip for dry-run) ---
|
|
352
381
|
if (!dryRun) {
|
|
353
382
|
if (result.errors.length > 0 || hasStepErrors) {
|
|
@@ -386,5 +415,10 @@ export async function applyAllComponents(root, dryRun = false, options) {
|
|
|
386
415
|
result.actions = allActions;
|
|
387
416
|
}
|
|
388
417
|
return result;
|
|
389
|
-
}
|
|
390
|
-
|
|
418
|
+
} /**
|
|
419
|
+
* Shared context threaded through the batch apply loops. Bundles the state
|
|
420
|
+
* both loops mutate (result/actions/checksums) with the immutable run
|
|
421
|
+
* parameters — the previous 12–13 positional parameters let call sites
|
|
422
|
+
* drift (the named-apply state-wipe bug) and made every new parameter a
|
|
423
|
+
* two-signature change.
|
|
424
|
+
*/
|
|
@@ -46,6 +46,17 @@ export declare function validateFurnaceConfig(data: unknown): FurnaceConfig;
|
|
|
46
46
|
* @throws Error if config doesn't exist or is invalid
|
|
47
47
|
*/
|
|
48
48
|
export declare function loadFurnaceConfig(root: string): Promise<FurnaceConfig>;
|
|
49
|
+
/**
|
|
50
|
+
* Clears applied furnace state while preserving `pendingRepair`.
|
|
51
|
+
*
|
|
52
|
+
* Used whenever the engine baseline is replaced or reset (download --force,
|
|
53
|
+
* reset, rebase, rebase --abort): every applied checksum describes content
|
|
54
|
+
* that no longer exists, but pendingRepair tracks authoring-side rollback
|
|
55
|
+
* issues in the component WORKSPACE and must survive an engine refresh.
|
|
56
|
+
* No-ops when the state file does not exist. Centralized because the same
|
|
57
|
+
* reset logic had drifted into four separate command files.
|
|
58
|
+
*/
|
|
59
|
+
export declare function clearAppliedFurnaceState(root: string): Promise<void>;
|
|
49
60
|
/**
|
|
50
61
|
* Writes a furnace configuration to furnace.json.
|
|
51
62
|
* @param root - Root directory of the project
|
|
@@ -122,6 +122,25 @@ export function validateFurnaceConfig(data) {
|
|
|
122
122
|
detectComposesCycles(custom);
|
|
123
123
|
// Validate that every composes reference points to a known component.
|
|
124
124
|
validateComposesReferences(stock, overrides, custom);
|
|
125
|
+
// Warn when two custom components share a targetPath. Nothing technically
|
|
126
|
+
// prevents co-location, but per-component removal and orphan detection
|
|
127
|
+
// both reason about "files this component deployed into its directory" —
|
|
128
|
+
// shared directories make those judgements ambiguous, and historically a
|
|
129
|
+
// shared targetPath meant `furnace remove` of one component deleted the
|
|
130
|
+
// other's deployed files.
|
|
131
|
+
const targetPathOwners = new Map();
|
|
132
|
+
for (const [name, custom_] of Object.entries(custom)) {
|
|
133
|
+
const owners = targetPathOwners.get(custom_.targetPath) ?? [];
|
|
134
|
+
owners.push(name);
|
|
135
|
+
targetPathOwners.set(custom_.targetPath, owners);
|
|
136
|
+
}
|
|
137
|
+
for (const [targetPath, owners] of targetPathOwners) {
|
|
138
|
+
if (owners.length > 1) {
|
|
139
|
+
warn(`furnace.json: custom components ${owners.join(', ')} share targetPath "${targetPath}". ` +
|
|
140
|
+
'Give each component its own directory — shared directories make per-component ' +
|
|
141
|
+
'removal and drift detection ambiguous.');
|
|
142
|
+
}
|
|
143
|
+
}
|
|
125
144
|
const config = {
|
|
126
145
|
version: CURRENT_CONFIG_VERSION,
|
|
127
146
|
componentPrefix: migrated['componentPrefix'],
|
|
@@ -308,6 +327,24 @@ export async function loadFurnaceConfig(root) {
|
|
|
308
327
|
throw new FurnaceError(`Invalid furnace.json at ${paths.furnaceConfig}: ${toError(error).message}`);
|
|
309
328
|
}
|
|
310
329
|
}
|
|
330
|
+
/**
|
|
331
|
+
* Clears applied furnace state while preserving `pendingRepair`.
|
|
332
|
+
*
|
|
333
|
+
* Used whenever the engine baseline is replaced or reset (download --force,
|
|
334
|
+
* reset, rebase, rebase --abort): every applied checksum describes content
|
|
335
|
+
* that no longer exists, but pendingRepair tracks authoring-side rollback
|
|
336
|
+
* issues in the component WORKSPACE and must survive an engine refresh.
|
|
337
|
+
* No-ops when the state file does not exist. Centralized because the same
|
|
338
|
+
* reset logic had drifted into four separate command files.
|
|
339
|
+
*/
|
|
340
|
+
export async function clearAppliedFurnaceState(root) {
|
|
341
|
+
const paths = getFurnacePaths(root);
|
|
342
|
+
if (!(await pathExists(paths.furnaceState)))
|
|
343
|
+
return;
|
|
344
|
+
await updateFurnaceState(root, (current) => ({
|
|
345
|
+
...(current.pendingRepair ? { pendingRepair: current.pendingRepair } : {}),
|
|
346
|
+
}));
|
|
347
|
+
}
|
|
311
348
|
/**
|
|
312
349
|
* Writes a furnace configuration to furnace.json.
|
|
313
350
|
* @param root - Root directory of the project
|
|
@@ -500,4 +537,3 @@ export async function collectFurnaceManagedPrefixes(root) {
|
|
|
500
537
|
prefixes.add(FURNACE_STORYBOOK_STORIES_PREFIX);
|
|
501
538
|
return prefixes;
|
|
502
539
|
}
|
|
503
|
-
//# sourceMappingURL=furnace-config.js.map
|
|
@@ -38,9 +38,8 @@ export interface JsconfigSyncResult {
|
|
|
38
38
|
* writing.
|
|
39
39
|
*
|
|
40
40
|
* The consumer owns the jsconfig file: a missing file is an error with
|
|
41
|
-
* guidance rather than a silent scaffold
|
|
42
|
-
*
|
|
43
|
-
* JSON parser, so the error message says so explicitly.
|
|
41
|
+
* guidance rather than a silent scaffold. JSONC comments and trailing commas
|
|
42
|
+
* are preserved; Furnace edits only `compilerOptions.paths`.
|
|
44
43
|
*
|
|
45
44
|
* @param root - Project root directory
|
|
46
45
|
* @param config - Loaded Furnace configuration (must carry `typecheckJsconfig`)
|