@hominis/fireforge 0.45.0 → 0.46.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 +80 -0
- package/dist/bin/fireforge.js +6 -0
- package/dist/build-info.json +4 -4
- package/dist/src/cli-usage-error.d.ts +46 -0
- package/dist/src/cli-usage-error.js +81 -0
- package/dist/src/cli.d.ts +5 -1
- package/dist/src/cli.js +20 -21
- package/dist/src/commands/bootstrap.js +1 -2
- package/dist/src/commands/build.js +15 -6
- package/dist/src/commands/destructive-decision.d.ts +19 -0
- package/dist/src/commands/destructive-decision.js +31 -0
- package/dist/src/commands/doctor-check-core.d.ts +0 -6
- package/dist/src/commands/doctor-check-core.js +0 -8
- package/dist/src/commands/doctor-orphaned-harness.d.ts +0 -6
- package/dist/src/commands/doctor-orphaned-harness.js +4 -12
- package/dist/src/commands/doctor-post-rebase-audit.js +1 -1
- package/dist/src/commands/doctor.d.ts +2 -0
- package/dist/src/commands/doctor.js +7 -8
- package/dist/src/commands/download.js +35 -24
- package/dist/src/commands/export-all.js +21 -4
- package/dist/src/commands/export-flow.js +15 -45
- package/dist/src/commands/export-placement-conflicts.js +3 -3
- package/dist/src/commands/export-placement-gate.js +2 -8
- package/dist/src/commands/export-placement-policy.js +3 -5
- package/dist/src/commands/export-shared.d.ts +42 -23
- package/dist/src/commands/export-shared.js +4 -19
- package/dist/src/commands/export.js +10 -1
- package/dist/src/commands/furnace/authoring-config.d.ts +13 -0
- package/dist/src/commands/furnace/authoring-config.js +19 -0
- package/dist/src/commands/furnace/chrome-doc-remove.js +1 -1
- package/dist/src/commands/furnace/create-browser-test.js +3 -8
- package/dist/src/commands/furnace/create-dry-run.js +2 -5
- package/dist/src/commands/furnace/create-templates.d.ts +17 -1
- package/dist/src/commands/furnace/create-templates.js +2 -1
- package/dist/src/commands/furnace/create.js +25 -19
- package/dist/src/commands/furnace/deploy.js +11 -2
- package/dist/src/commands/furnace/init.js +2 -1
- package/dist/src/commands/furnace/override.js +28 -19
- package/dist/src/commands/furnace/refresh.js +9 -1
- package/dist/src/commands/furnace/remove-mutations.d.ts +6 -7
- package/dist/src/commands/furnace/remove-mutations.js +0 -1
- package/dist/src/commands/furnace/remove.js +2 -6
- package/dist/src/commands/furnace/rename-browser-test.js +1 -7
- package/dist/src/commands/furnace/rename.js +29 -29
- package/dist/src/commands/furnace/status.js +12 -9
- package/dist/src/commands/furnace/test-file-name.d.ts +28 -0
- package/dist/src/commands/furnace/test-file-name.js +39 -0
- package/dist/src/commands/furnace/validation-output.d.ts +21 -9
- package/dist/src/commands/furnace/validation-output.js +3 -8
- package/dist/src/commands/import.js +2 -9
- package/dist/src/commands/lint-per-patch.d.ts +2 -2
- package/dist/src/commands/lint-per-patch.js +19 -4
- package/dist/src/commands/lint-per-run-checkjs.d.ts +2 -2
- package/dist/src/commands/lint.d.ts +2 -1
- package/dist/src/commands/lint.js +1 -1
- package/dist/src/commands/manifest.js +1 -1
- package/dist/src/commands/package.js +2 -4
- package/dist/src/commands/patch/compact.js +14 -15
- package/dist/src/commands/patch/delete.js +15 -25
- package/dist/src/commands/patch/entry-projection.d.ts +13 -0
- package/dist/src/commands/patch/entry-projection.js +24 -0
- package/dist/src/commands/patch/lint-ignore.d.ts +0 -8
- package/dist/src/commands/patch/lint-ignore.js +19 -29
- package/dist/src/commands/patch/move-files-create.js +24 -10
- package/dist/src/commands/patch/move-files-into.js +38 -34
- package/dist/src/commands/patch/move-files.js +4 -13
- package/dist/src/commands/patch/rename.js +27 -41
- package/dist/src/commands/patch/reorder.js +25 -38
- package/dist/src/commands/patch/split-plan.d.ts +4 -5
- package/dist/src/commands/patch/split-plan.js +4 -8
- package/dist/src/commands/patch/split.js +45 -43
- package/dist/src/commands/patch/staged-dependency.js +16 -22
- package/dist/src/commands/patch/tier.js +15 -25
- package/dist/src/commands/patch-rename-map.d.ts +19 -0
- package/dist/src/commands/patch-rename-map.js +36 -0
- package/dist/src/commands/pending-resolution.d.ts +15 -0
- package/dist/src/commands/pending-resolution.js +25 -0
- package/dist/src/commands/re-export-adjacent.d.ts +24 -2
- package/dist/src/commands/re-export-adjacent.js +32 -16
- package/dist/src/commands/re-export-bulk-scan.js +6 -10
- package/dist/src/commands/re-export-drift.js +4 -4
- package/dist/src/commands/re-export-files.d.ts +2 -3
- package/dist/src/commands/re-export-files.js +38 -29
- package/dist/src/commands/re-export-lint.d.ts +3 -4
- package/dist/src/commands/re-export-lint.js +9 -2
- package/dist/src/commands/re-export-scan.js +5 -2
- package/dist/src/commands/re-export-single.d.ts +22 -3
- package/dist/src/commands/re-export-single.js +19 -11
- package/dist/src/commands/re-export.js +28 -11
- package/dist/src/commands/rebase/abort.js +3 -8
- package/dist/src/commands/rebase/continue.js +12 -12
- package/dist/src/commands/rebase/index.js +2 -2
- package/dist/src/commands/rebase/patch-loop.d.ts +2 -2
- package/dist/src/commands/rebase/patch-loop.js +2 -7
- package/dist/src/commands/resolve.js +18 -10
- package/dist/src/commands/run.d.ts +0 -13
- package/dist/src/commands/run.js +12 -25
- package/dist/src/commands/setup-support.d.ts +16 -4
- package/dist/src/commands/setup-support.js +16 -16
- package/dist/src/commands/setup.js +3 -9
- package/dist/src/commands/source.js +8 -1
- package/dist/src/commands/status-check.js +2 -2
- package/dist/src/commands/status-output.js +2 -2
- package/dist/src/commands/status.js +3 -4
- package/dist/src/commands/test-build-phase.d.ts +2 -2
- package/dist/src/commands/test-build-phase.js +31 -7
- package/dist/src/commands/test-modes.d.ts +2 -2
- package/dist/src/commands/test-modes.js +0 -1
- package/dist/src/commands/test-register.js +5 -3
- package/dist/src/commands/test-run.js +25 -18
- package/dist/src/commands/test-stale-gate.js +4 -4
- package/dist/src/commands/test-verdict.d.ts +16 -2
- package/dist/src/commands/test-verdict.js +18 -3
- package/dist/src/commands/test.js +42 -5
- package/dist/src/commands/token-list.d.ts +26 -0
- package/dist/src/commands/token-list.js +153 -0
- package/dist/src/commands/token.js +17 -1
- package/dist/src/commands/tree.js +7 -2
- package/dist/src/commands/typecheck.js +9 -9
- package/dist/src/commands/wire.js +4 -3
- package/dist/src/core/branding.d.ts +0 -7
- package/dist/src/core/branding.js +3 -2
- package/dist/src/core/browser-wire.js +6 -3
- package/dist/src/core/build-audit-platform.d.ts +29 -0
- package/dist/src/core/build-audit-platform.js +140 -0
- package/dist/src/core/build-audit-registration.js +3 -2
- package/dist/src/core/build-audit-skip.d.ts +57 -0
- package/dist/src/core/build-audit-skip.js +112 -0
- package/dist/src/core/build-audit.d.ts +15 -2
- package/dist/src/core/build-audit.js +110 -10
- package/dist/src/core/build-baseline.d.ts +45 -28
- package/dist/src/core/build-baseline.js +6 -29
- package/dist/src/core/build-jar-escalation.js +3 -2
- package/dist/src/core/build-overwrite-guard.js +1 -1
- package/dist/src/core/build-prepare.d.ts +0 -8
- package/dist/src/core/build-prepare.js +7 -12
- package/dist/src/core/config-paths.d.ts +2 -6
- package/dist/src/core/config-paths.js +4 -2
- package/dist/src/core/config-state.d.ts +0 -6
- package/dist/src/core/config-state.js +1 -19
- package/dist/src/core/config-validate-build-audit.d.ts +8 -0
- package/dist/src/core/config-validate-build-audit.js +80 -0
- package/dist/src/core/config-validate-file-size.js +1 -1
- package/dist/src/core/config-validate-patch-policy.d.ts +2 -2
- package/dist/src/core/config-validate-patch-policy.js +3 -3
- package/dist/src/core/config-validate-test-toolchains.d.ts +2 -2
- package/dist/src/core/config-validate-test-toolchains.js +4 -5
- package/dist/src/core/config-validate.js +14 -3
- package/dist/src/core/config.d.ts +2 -2
- package/dist/src/core/config.js +11 -15
- package/dist/src/core/coverage-extend.js +4 -4
- package/dist/src/core/discard-baseline.js +7 -7
- package/dist/src/core/engine-precondition.d.ts +1 -9
- package/dist/src/core/engine-precondition.js +3 -8
- package/dist/src/core/engine-session-lock.js +13 -17
- package/dist/src/core/export-moved-code-shape.js +2 -2
- package/dist/src/core/file-lock-owner.d.ts +103 -0
- package/dist/src/core/file-lock-owner.js +259 -0
- package/dist/src/core/file-lock.d.ts +2 -7
- package/dist/src/core/file-lock.js +40 -91
- package/dist/src/core/firefox-cache.d.ts +13 -1
- package/dist/src/core/firefox-cache.js +59 -34
- package/dist/src/core/firefox-download.d.ts +15 -0
- package/dist/src/core/firefox-download.js +37 -21
- package/dist/src/core/firefox-extract.d.ts +6 -5
- package/dist/src/core/firefox-extract.js +36 -18
- package/dist/src/core/firefox.d.ts +30 -32
- package/dist/src/core/firefox.js +5 -36
- package/dist/src/core/furnace-apply-dry-run.d.ts +1 -6
- package/dist/src/core/furnace-apply-dry-run.js +1 -7
- package/dist/src/core/furnace-apply-helpers.d.ts +0 -11
- package/dist/src/core/furnace-apply-helpers.js +8 -19
- package/dist/src/core/furnace-apply-overwrite-warn.js +4 -2
- package/dist/src/core/furnace-apply.d.ts +1 -1
- package/dist/src/core/furnace-apply.js +30 -10
- package/dist/src/core/furnace-config.d.ts +1 -1
- package/dist/src/core/furnace-config.js +23 -36
- package/dist/src/core/furnace-constants.js +3 -2
- package/dist/src/core/furnace-dir-entry.d.ts +20 -0
- package/dist/src/core/furnace-dir-entry.js +21 -0
- package/dist/src/core/furnace-operation.d.ts +2 -0
- package/dist/src/core/furnace-operation.js +3 -1
- package/dist/src/core/furnace-refresh.d.ts +18 -6
- package/dist/src/core/furnace-refresh.js +3 -6
- package/dist/src/core/furnace-registration-ast.d.ts +0 -1
- package/dist/src/core/furnace-registration-ast.js +0 -3
- package/dist/src/core/furnace-registration.d.ts +2 -1
- package/dist/src/core/furnace-registration.js +22 -29
- package/dist/src/core/furnace-scanner.js +3 -6
- package/dist/src/core/furnace-state-persist.d.ts +2 -2
- package/dist/src/core/furnace-validate-registration.js +5 -7
- package/dist/src/core/git-base.d.ts +3 -1
- package/dist/src/core/git-base.js +25 -3
- package/dist/src/core/git-diff-new-file.d.ts +40 -0
- package/dist/src/core/git-diff-new-file.js +119 -0
- package/dist/src/core/git-diff.d.ts +9 -9
- package/dist/src/core/git-diff.js +88 -157
- package/dist/src/core/git-file-ops.d.ts +0 -7
- package/dist/src/core/git-file-ops.js +4 -28
- package/dist/src/core/git-readonly-index.d.ts +0 -2
- package/dist/src/core/git-readonly-index.js +0 -4
- package/dist/src/core/git-status.d.ts +7 -0
- package/dist/src/core/git-status.js +21 -9
- package/dist/src/core/git.d.ts +0 -6
- package/dist/src/core/git.js +14 -43
- package/dist/src/core/harness-orphans.js +8 -7
- package/dist/src/core/history-log.d.ts +19 -0
- package/dist/src/core/history-log.js +29 -0
- package/dist/src/core/mach-error-hints.d.ts +0 -5
- package/dist/src/core/mach-error-hints.js +1 -1
- package/dist/src/core/mach-known-noise-filter.d.ts +4 -3
- package/dist/src/core/mach-known-noise-filter.js +7 -9
- package/dist/src/core/mach-mozconfig.js +2 -1
- package/dist/src/core/mach-python.d.ts +2 -0
- package/dist/src/core/mach-python.js +12 -7
- package/dist/src/core/mach-resource-shim.d.ts +13 -1
- package/dist/src/core/mach-resource-shim.js +112 -9
- package/dist/src/core/mach.d.ts +39 -53
- package/dist/src/core/mach.js +13 -81
- package/dist/src/core/marionette-port.d.ts +0 -17
- package/dist/src/core/marionette-port.js +12 -58
- package/dist/src/core/marionette-preflight.js +9 -27
- package/dist/src/core/mochitest-server-port.js +3 -3
- package/dist/src/core/moz-manifest-rules.js +37 -33
- package/dist/src/core/patch-apply-fuzz.js +4 -4
- package/dist/src/core/patch-apply.d.ts +4 -5
- package/dist/src/core/patch-apply.js +6 -12
- package/dist/src/core/patch-export-coverage.d.ts +0 -9
- package/dist/src/core/patch-export-coverage.js +1 -30
- package/dist/src/core/patch-export-update.d.ts +18 -1
- package/dist/src/core/patch-export-update.js +4 -3
- package/dist/src/core/patch-export.d.ts +11 -21
- package/dist/src/core/patch-export.js +44 -66
- package/dist/src/core/patch-files.d.ts +0 -4
- package/dist/src/core/patch-files.js +0 -11
- package/dist/src/core/patch-lint-binary.d.ts +6 -12
- package/dist/src/core/patch-lint-cache.d.ts +17 -1
- package/dist/src/core/patch-lint-cache.js +3 -6
- package/dist/src/core/patch-lint-checkjs.d.ts +33 -16
- package/dist/src/core/patch-lint-checkjs.js +37 -24
- package/dist/src/core/patch-lint-creators.d.ts +2 -6
- package/dist/src/core/patch-lint-cross.d.ts +16 -8
- package/dist/src/core/patch-lint-cross.js +49 -92
- package/dist/src/core/patch-lint-css.js +10 -6
- package/dist/src/core/patch-lint-file-size.js +3 -3
- package/dist/src/core/patch-lint-forward-registration.d.ts +58 -0
- package/dist/src/core/patch-lint-forward-registration.js +210 -0
- package/dist/src/core/patch-lint-jsdoc.js +36 -36
- package/dist/src/core/patch-lint-module-registration.d.ts +2 -8
- package/dist/src/core/patch-lint-prettier.js +1 -1
- package/dist/src/core/patch-lint-queue-types.d.ts +60 -0
- package/dist/src/core/patch-lint-queue-types.js +12 -0
- package/dist/src/core/patch-lint-staged-registration.d.ts +61 -0
- package/dist/src/core/patch-lint-staged-registration.js +90 -0
- package/dist/src/core/patch-lint.d.ts +14 -9
- package/dist/src/core/patch-lint.js +4 -3
- package/dist/src/core/patch-lock.js +3 -3
- package/dist/src/core/patch-manifest-io.js +8 -7
- package/dist/src/core/patch-parse.d.ts +19 -8
- package/dist/src/core/patch-parse.js +25 -0
- package/dist/src/core/patch-policy.js +15 -27
- package/dist/src/core/patch-source-metadata.d.ts +1 -3
- package/dist/src/core/patch-source-metadata.js +0 -4
- package/dist/src/core/patch-transform.d.ts +0 -8
- package/dist/src/core/patch-transform.js +0 -10
- package/dist/src/core/process-cpu-time.js +1 -1
- package/dist/src/core/rebase-session.d.ts +2 -8
- package/dist/src/core/rebase-session.js +5 -17
- package/dist/src/core/register-browser-content.js +3 -2
- package/dist/src/core/register-browser-test.js +2 -1
- package/dist/src/core/register-module.js +2 -1
- package/dist/src/core/register-scaffold.js +5 -4
- package/dist/src/core/register-shared-css.js +2 -1
- package/dist/src/core/run-log-redact.d.ts +38 -0
- package/dist/src/core/run-log-redact.js +69 -0
- package/dist/src/core/run-log.d.ts +13 -2
- package/dist/src/core/run-log.js +67 -18
- package/dist/src/core/signal-critical.js +2 -4
- package/dist/src/core/smoke-patterns.d.ts +0 -1
- package/dist/src/core/smoke-patterns.js +6 -11
- package/dist/src/core/state-file.d.ts +12 -1
- package/dist/src/core/state-file.js +14 -1
- package/dist/src/core/status-classify.js +2 -1
- package/dist/src/core/test-changed-prefs.js +26 -11
- package/dist/src/core/test-harness-crash.js +2 -2
- package/dist/src/core/test-stale-check.d.ts +0 -2
- package/dist/src/core/test-stale-check.js +2 -2
- package/dist/src/core/test-xpcshell-retry.d.ts +26 -7
- package/dist/src/core/test-xpcshell-retry.js +14 -11
- package/dist/src/core/token-category.d.ts +33 -0
- package/dist/src/core/token-category.js +56 -48
- package/dist/src/core/token-dark-mode.js +2 -14
- package/dist/src/core/token-inventory.d.ts +56 -0
- package/dist/src/core/token-inventory.js +109 -0
- package/dist/src/core/token-manager.d.ts +1 -1
- package/dist/src/core/token-manager.js +1 -1
- package/dist/src/core/token-variant.js +21 -22
- package/dist/src/core/toolchain-preflight.js +1 -1
- package/dist/src/core/tree-cow.d.ts +0 -2
- package/dist/src/core/tree-cow.js +2 -2
- package/dist/src/core/tree-store.js +4 -5
- package/dist/src/core/typecheck.js +2 -2
- package/dist/src/core/wire-dom-fragment.js +6 -17
- package/dist/src/core/wire-utils.js +5 -17
- package/dist/src/core/xpcshell-appdir.js +2 -4
- package/dist/src/errors/base.d.ts +53 -1
- package/dist/src/errors/base.js +79 -11
- package/dist/src/errors/build.js +20 -28
- package/dist/src/errors/config.d.ts +0 -14
- package/dist/src/errors/config.js +13 -34
- package/dist/src/errors/download.d.ts +13 -0
- package/dist/src/errors/download.js +53 -24
- package/dist/src/errors/furnace.js +8 -9
- package/dist/src/errors/git.d.ts +8 -8
- package/dist/src/errors/git.js +20 -30
- package/dist/src/errors/patch.js +8 -10
- package/dist/src/errors/rebase.js +7 -7
- package/dist/src/types/commands/options.d.ts +77 -123
- package/dist/src/types/config.d.ts +34 -2
- package/dist/src/types/index.d.ts +0 -1
- package/dist/src/utils/assert.d.ts +3 -15
- package/dist/src/utils/assert.js +1 -18
- package/dist/src/utils/build-info.d.ts +1 -1
- package/dist/src/utils/child-env.d.ts +2 -1
- package/dist/src/utils/elapsed.js +10 -1
- package/dist/src/utils/fs.js +8 -13
- package/dist/src/utils/hash.d.ts +12 -0
- package/dist/src/utils/hash.js +16 -0
- package/dist/src/utils/line-dispatch.d.ts +15 -0
- package/dist/src/utils/line-dispatch.js +48 -0
- package/dist/src/utils/options.d.ts +1 -7
- package/dist/src/utils/options.js +6 -7
- package/dist/src/utils/package-root.js +3 -12
- package/dist/src/utils/platform.d.ts +0 -4
- package/dist/src/utils/process-group.d.ts +2 -1
- package/dist/src/utils/process-group.js +20 -19
- package/dist/src/utils/process.d.ts +40 -26
- package/dist/src/utils/process.js +115 -166
- package/dist/src/utils/ps-duration.js +3 -3
- package/dist/src/utils/sleep.d.ts +26 -0
- package/dist/src/utils/sleep.js +31 -0
- package/dist/src/utils/validation.d.ts +2 -38
- package/dist/src/utils/validation.js +7 -16
- package/docs/configuration.md +89 -1
- package/docs/exit-codes.md +31 -19
- package/docs/lifecycle-invariants.md +18 -12
- package/docs/machine-output.md +31 -8
- package/docs/patch-workflow.md +2 -1
- package/docs/run-logs.md +44 -4
- package/docs/testing.md +8 -1
- package/package.json +8 -7
- package/dist/src/commands/rebase.d.ts +0 -4
- package/dist/src/commands/rebase.js +0 -5
- package/dist/src/core/patch-artifact-normalize.d.ts +0 -21
- package/dist/src/core/patch-artifact-normalize.js +0 -24
- package/dist/src/core/patch-lint-reexports.d.ts +0 -7
- package/dist/src/core/patch-lint-reexports.js +0 -8
- package/dist/src/core/wire-targets.d.ts +0 -7
- package/dist/src/core/wire-targets.js +0 -8
|
@@ -8,12 +8,6 @@ import type { FireForgeState } from '../types/config.js';
|
|
|
8
8
|
* @returns FireForge state
|
|
9
9
|
*/
|
|
10
10
|
export declare function loadState(root: string): Promise<FireForgeState>;
|
|
11
|
-
/**
|
|
12
|
-
* Saves the fireforge state.
|
|
13
|
-
* @param root - Root directory of the project
|
|
14
|
-
* @param state - State to save
|
|
15
|
-
*/
|
|
16
|
-
export declare function saveState(root: string, state: FireForgeState): Promise<void>;
|
|
17
11
|
/**
|
|
18
12
|
* Updates specific fields in the fireforge state.
|
|
19
13
|
* @param root - Root directory of the project
|
|
@@ -24,13 +24,7 @@ function sanitizeProjectState(data) {
|
|
|
24
24
|
const state = {};
|
|
25
25
|
const issues = [];
|
|
26
26
|
const recoveredFields = [];
|
|
27
|
-
const stringFields = [
|
|
28
|
-
'brand',
|
|
29
|
-
'buildMode',
|
|
30
|
-
'lastBuild',
|
|
31
|
-
'downloadedVersion',
|
|
32
|
-
'baseCommit',
|
|
33
|
-
];
|
|
27
|
+
const stringFields = ['brand', 'buildMode', 'downloadedVersion', 'baseCommit'];
|
|
34
28
|
for (const key of stringFields) {
|
|
35
29
|
const value = data[key];
|
|
36
30
|
if (value === undefined) {
|
|
@@ -132,18 +126,6 @@ export async function loadState(root) {
|
|
|
132
126
|
const paths = getProjectPaths(root);
|
|
133
127
|
return loadStateFromPath(paths.state);
|
|
134
128
|
}
|
|
135
|
-
/**
|
|
136
|
-
* Saves the fireforge state.
|
|
137
|
-
* @param root - Root directory of the project
|
|
138
|
-
* @param state - State to save
|
|
139
|
-
*/
|
|
140
|
-
export async function saveState(root, state) {
|
|
141
|
-
const paths = getProjectPaths(root);
|
|
142
|
-
const validatedState = validateFireForgeState(state);
|
|
143
|
-
await withStateFileLock(paths.state, async () => {
|
|
144
|
-
await writeJson(paths.state, validatedState);
|
|
145
|
-
});
|
|
146
|
-
}
|
|
147
129
|
/**
|
|
148
130
|
* Updates specific fields in the fireforge state.
|
|
149
131
|
* @param root - Root directory of the project
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { BuildAuditConfig } from '../types/config.js';
|
|
2
|
+
/**
|
|
3
|
+
* Parses the `buildAudit` block.
|
|
4
|
+
*
|
|
5
|
+
* @param raw - The raw `buildAudit` value from fireforge.json
|
|
6
|
+
* @returns The validated block
|
|
7
|
+
*/
|
|
8
|
+
export declare function parseBuildAuditConfig(raw: unknown): BuildAuditConfig;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* Validation for the `buildAudit` block.
|
|
4
|
+
*
|
|
5
|
+
* Split out of `config-validate.ts`, which is at its per-file line budget,
|
|
6
|
+
* following the `config-validate-file-size.ts` precedent.
|
|
7
|
+
*
|
|
8
|
+
* The rules here are stricter than the schema strictly needs, and
|
|
9
|
+
* deliberately so: every entry is a standing suppression of a warning the
|
|
10
|
+
* audit would otherwise raise. A malformed one must fail loudly at config
|
|
11
|
+
* load rather than quietly admit nothing (leaving the operator believing a
|
|
12
|
+
* path is carved out) or quietly admit everything.
|
|
13
|
+
*/
|
|
14
|
+
import { ConfigError } from '../errors/config.js';
|
|
15
|
+
import { isContainedRelativePath } from '../utils/paths.js';
|
|
16
|
+
import { isObject } from '../utils/validation.js';
|
|
17
|
+
/** Parses one `buildAudit.unpackaged[]` entry. */
|
|
18
|
+
function parseUnpackagedEntry(raw, field) {
|
|
19
|
+
if (!isObject(raw)) {
|
|
20
|
+
throw new ConfigError(`Config field "${field}" must be a plain object with "path" and "reason"`);
|
|
21
|
+
}
|
|
22
|
+
for (const key of Object.keys(raw)) {
|
|
23
|
+
if (key !== 'path' && key !== 'reason') {
|
|
24
|
+
throw new ConfigError(`Config field "${field}" has unknown key "${key}"`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
const path = raw['path'];
|
|
28
|
+
if (typeof path !== 'string' || path.trim().length === 0) {
|
|
29
|
+
throw new ConfigError(`Config field "${field}.path" must be a non-empty string`);
|
|
30
|
+
}
|
|
31
|
+
if (!isContainedRelativePath(path)) {
|
|
32
|
+
throw new ConfigError(`Config field "${field}.path" must be an engine-relative path`);
|
|
33
|
+
}
|
|
34
|
+
if (path.includes('**')) {
|
|
35
|
+
// A `**` carve-out admits an entire subtree, which is how a reviewed
|
|
36
|
+
// exception quietly becomes a blanket one. `*` inside a single segment
|
|
37
|
+
// is enough for the shapes this exists for.
|
|
38
|
+
throw new ConfigError(`Config field "${field}.path" must not use "**" — a carve-out may glob within one path segment only`);
|
|
39
|
+
}
|
|
40
|
+
const reason = raw['reason'];
|
|
41
|
+
if (typeof reason !== 'string' || reason.trim().length === 0) {
|
|
42
|
+
// Required on purpose: this is the one audit class FireForge cannot
|
|
43
|
+
// derive from the tree, so the declaration IS the evidence.
|
|
44
|
+
throw new ConfigError(`Config field "${field}.reason" must be a non-empty string explaining why the file is never packaged`);
|
|
45
|
+
}
|
|
46
|
+
return { path, reason };
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Parses the `buildAudit` block.
|
|
50
|
+
*
|
|
51
|
+
* @param raw - The raw `buildAudit` value from fireforge.json
|
|
52
|
+
* @returns The validated block
|
|
53
|
+
*/
|
|
54
|
+
export function parseBuildAuditConfig(raw) {
|
|
55
|
+
if (!isObject(raw)) {
|
|
56
|
+
throw new ConfigError('Config field "buildAudit" must be a plain object');
|
|
57
|
+
}
|
|
58
|
+
for (const key of Object.keys(raw)) {
|
|
59
|
+
if (key !== 'unpackaged') {
|
|
60
|
+
throw new ConfigError(`Config field "buildAudit" has unknown key "${key}"`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
const unpackaged = raw['unpackaged'];
|
|
64
|
+
if (unpackaged === undefined)
|
|
65
|
+
return {};
|
|
66
|
+
if (!Array.isArray(unpackaged)) {
|
|
67
|
+
throw new ConfigError('Config field "buildAudit.unpackaged" must be an array');
|
|
68
|
+
}
|
|
69
|
+
const entries = unpackaged.map((entry, index) => parseUnpackagedEntry(entry, `buildAudit.unpackaged[${String(index)}]`));
|
|
70
|
+
const seen = new Set();
|
|
71
|
+
for (const entry of entries) {
|
|
72
|
+
if (seen.has(entry.path)) {
|
|
73
|
+
// Two rows for one path means one of the two reasons is not the
|
|
74
|
+
// operative one, and nothing says which.
|
|
75
|
+
throw new ConfigError(`Config field "buildAudit.unpackaged" declares "${entry.path}" more than once`);
|
|
76
|
+
}
|
|
77
|
+
seen.add(entry.path);
|
|
78
|
+
}
|
|
79
|
+
return { unpackaged: entries };
|
|
80
|
+
}
|
|
@@ -52,7 +52,7 @@ export function parsePatchLintFileSizeThresholds(raw) {
|
|
|
52
52
|
const merged = { ...DEFAULT_FILE_SIZE_THRESHOLDS[tier], ...parsed };
|
|
53
53
|
if (merged.notice > merged.warning || merged.warning > merged.error) {
|
|
54
54
|
throw new ConfigError(`Config field "patchLint.fileSizeThresholds.${tier}" must satisfy notice <= warning <= error ` +
|
|
55
|
-
`(resolved: ${
|
|
55
|
+
`(resolved: ${merged.notice}/${merged.warning}/${merged.error})`);
|
|
56
56
|
}
|
|
57
57
|
out[tier] = parsed;
|
|
58
58
|
}
|
|
@@ -2,6 +2,6 @@
|
|
|
2
2
|
* Validation helpers for `fireforge.json#patchPolicy`.
|
|
3
3
|
*/
|
|
4
4
|
import type { PatchPolicyConfig } from '../types/config.js';
|
|
5
|
-
import {
|
|
5
|
+
import { type ParsedRecord } from '../utils/parse.js';
|
|
6
6
|
/** Parses and validates the optional patch policy config block. */
|
|
7
|
-
export declare function parsePatchPolicyBlock(rec:
|
|
7
|
+
export declare function parsePatchPolicyBlock(rec: ParsedRecord): PatchPolicyConfig;
|
|
@@ -108,7 +108,7 @@ function parsePatchPolicyReservedRange(raw, label) {
|
|
|
108
108
|
return {
|
|
109
109
|
from,
|
|
110
110
|
to,
|
|
111
|
-
allowed: allowedRaw.map((entry, index) => parseReservedAllowedPatch(entry, `${label}.allowed[${
|
|
111
|
+
allowed: allowedRaw.map((entry, index) => parseReservedAllowedPatch(entry, `${label}.allowed[${index}]`)),
|
|
112
112
|
};
|
|
113
113
|
}
|
|
114
114
|
function assertPolicyRangesDoNotOverlap(ranges, label) {
|
|
@@ -160,14 +160,14 @@ export function parsePatchPolicyBlock(rec) {
|
|
|
160
160
|
if (!Array.isArray(rangesRaw) || rangesRaw.length === 0) {
|
|
161
161
|
throw new ConfigError('Config field "patchPolicy.ranges" must be a non-empty array');
|
|
162
162
|
}
|
|
163
|
-
out.ranges = rangesRaw.map((entry, index) => parsePatchPolicyRange(entry, `patchPolicy.ranges[${
|
|
163
|
+
out.ranges = rangesRaw.map((entry, index) => parsePatchPolicyRange(entry, `patchPolicy.ranges[${index}]`));
|
|
164
164
|
assertPolicyRangesDoNotOverlap(out.ranges, 'patchPolicy.ranges');
|
|
165
165
|
const reservedRangesRaw = rec.raw('reservedRanges');
|
|
166
166
|
if (reservedRangesRaw !== undefined) {
|
|
167
167
|
if (!Array.isArray(reservedRangesRaw)) {
|
|
168
168
|
throw new ConfigError('Config field "patchPolicy.reservedRanges" must be an array');
|
|
169
169
|
}
|
|
170
|
-
out.reservedRanges = reservedRangesRaw.map((entry, index) => parsePatchPolicyReservedRange(entry, `patchPolicy.reservedRanges[${
|
|
170
|
+
out.reservedRanges = reservedRangesRaw.map((entry, index) => parsePatchPolicyReservedRange(entry, `patchPolicy.reservedRanges[${index}]`));
|
|
171
171
|
assertPolicyRangesDoNotOverlap(out.reservedRanges, 'patchPolicy.reservedRanges');
|
|
172
172
|
}
|
|
173
173
|
return out;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { FireForgeConfig } from '../types/config.js';
|
|
2
|
-
import {
|
|
3
|
-
type ConfigRecord =
|
|
2
|
+
import type { ParsedRecord } from '../utils/parse.js';
|
|
3
|
+
type ConfigRecord = ParsedRecord;
|
|
4
4
|
/** Parses the optional `test` block. */
|
|
5
5
|
export declare function parseTestBlock(rec: ConfigRecord, config: FireForgeConfig): void;
|
|
6
6
|
/** Parses optional project-specific external toolchain doctor probes. */
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
// SPDX-License-Identifier: EUPL-1.2
|
|
2
2
|
import { ConfigError } from '../errors/config.js';
|
|
3
|
-
import { parseObject } from '../utils/parse.js';
|
|
4
3
|
import { isContainedRelativePath } from '../utils/paths.js';
|
|
5
4
|
import { isObject } from '../utils/validation.js';
|
|
6
5
|
function optionalConfigString(rec, key, label) {
|
|
@@ -58,16 +57,16 @@ export function parseExternalToolchainsBlock(rec, config) {
|
|
|
58
57
|
}
|
|
59
58
|
config.externalToolchains = raw.map((entry, index) => {
|
|
60
59
|
if (!isObject(entry)) {
|
|
61
|
-
throw new ConfigError(`Config field "externalToolchains[${
|
|
60
|
+
throw new ConfigError(`Config field "externalToolchains[${index}]" must be an object`);
|
|
62
61
|
}
|
|
63
62
|
const item = entry;
|
|
64
63
|
const name = item['name'];
|
|
65
64
|
if (typeof name !== 'string' || name.trim() === '') {
|
|
66
|
-
throw new ConfigError(`Config field "externalToolchains[${
|
|
65
|
+
throw new ConfigError(`Config field "externalToolchains[${index}].name" must be a non-empty string`);
|
|
67
66
|
}
|
|
68
67
|
const tools = item['tools'];
|
|
69
68
|
if (!Array.isArray(tools) || tools.length === 0) {
|
|
70
|
-
throw new ConfigError(`Config field "externalToolchains[${
|
|
69
|
+
throw new ConfigError(`Config field "externalToolchains[${index}].tools" must be a non-empty array`);
|
|
71
70
|
}
|
|
72
71
|
return {
|
|
73
72
|
name,
|
|
@@ -76,7 +75,7 @@ export function parseExternalToolchainsBlock(rec, config) {
|
|
|
76
75
|
});
|
|
77
76
|
}
|
|
78
77
|
function parseExternalToolRequirement(tool, index, toolIndex) {
|
|
79
|
-
const label = `externalToolchains[${
|
|
78
|
+
const label = `externalToolchains[${index}].tools[${toolIndex}]`;
|
|
80
79
|
if (!isObject(tool)) {
|
|
81
80
|
throw new ConfigError(`Config field "${label}" must be an object`);
|
|
82
81
|
}
|
|
@@ -8,6 +8,7 @@ import { parseObject } from '../utils/parse.js';
|
|
|
8
8
|
import { isContainedRelativePath, isExplicitAbsolutePath } from '../utils/paths.js';
|
|
9
9
|
import { describeProductVersionIncompatibility, FIREFOX_PRODUCTS, isObject, isValidAppId, isValidFirefoxCandidate, isValidFirefoxProduct, isValidFirefoxVersion, isValidProjectLicense, PROJECT_LICENSES, } from '../utils/validation.js';
|
|
10
10
|
import { SUPPORTED_CONFIG_ROOT_KEYS } from './config-paths.js';
|
|
11
|
+
import { parseBuildAuditConfig } from './config-validate-build-audit.js';
|
|
11
12
|
import { parsePatchLintFileSizeThresholds } from './config-validate-file-size.js';
|
|
12
13
|
import { parsePatchPolicyBlock } from './config-validate-patch-policy.js';
|
|
13
14
|
import { parseExternalToolchainsBlock, parseTestBlock } from './config-validate-test-toolchains.js';
|
|
@@ -54,7 +55,8 @@ function parseIdentityFields(rec) {
|
|
|
54
55
|
/**
|
|
55
56
|
* Parses and validates the required `firefox` block: version shape,
|
|
56
57
|
* product allowlist, product/version cross-compatibility, and the
|
|
57
|
-
* optional sha256 digest (normalized to lowercase)
|
|
58
|
+
* optional sha256 digest (normalized to lowercase), optional
|
|
59
|
+
* `allowUnverifiedDownload` opt-out, optional candidate directory.
|
|
58
60
|
*/
|
|
59
61
|
function parseFirefoxBlock(rec) {
|
|
60
62
|
let firefoxRec;
|
|
@@ -85,6 +87,10 @@ function parseFirefoxBlock(rec) {
|
|
|
85
87
|
if (firefoxSha256 !== undefined && !/^[a-f0-9]{64}$/i.test(firefoxSha256)) {
|
|
86
88
|
throw new ConfigError('Config field "firefox.sha256" must be a 64-character SHA-256 hex digest');
|
|
87
89
|
}
|
|
90
|
+
const allowUnverifiedDownload = firefoxRec.raw('allowUnverifiedDownload');
|
|
91
|
+
if (allowUnverifiedDownload !== undefined && typeof allowUnverifiedDownload !== 'boolean') {
|
|
92
|
+
throw new ConfigError('Config field "firefox.allowUnverifiedDownload" must be a boolean');
|
|
93
|
+
}
|
|
88
94
|
const firefoxCandidate = optionalConfigString(firefoxRec, 'candidate', 'firefox.candidate');
|
|
89
95
|
if (firefoxCandidate !== undefined && !isValidFirefoxCandidate(firefoxCandidate)) {
|
|
90
96
|
throw new ConfigError('Config field "firefox.candidate" must look like "buildN" (e.g. "build2")');
|
|
@@ -93,6 +99,7 @@ function parseFirefoxBlock(rec) {
|
|
|
93
99
|
version: firefoxVersion,
|
|
94
100
|
product: firefoxProduct,
|
|
95
101
|
...(firefoxSha256 !== undefined ? { sha256: firefoxSha256.toLowerCase() } : {}),
|
|
102
|
+
...(allowUnverifiedDownload !== undefined ? { allowUnverifiedDownload } : {}),
|
|
96
103
|
...(firefoxCandidate !== undefined ? { candidate: firefoxCandidate } : {}),
|
|
97
104
|
};
|
|
98
105
|
}
|
|
@@ -155,6 +162,10 @@ export function validateConfig(data) {
|
|
|
155
162
|
const firefox = parseFirefoxBlock(rec);
|
|
156
163
|
const config = { ...identity, firefox };
|
|
157
164
|
parseBuildBlock(rec, config);
|
|
165
|
+
const buildAuditRaw = rec.raw('buildAudit');
|
|
166
|
+
if (buildAuditRaw !== undefined) {
|
|
167
|
+
config.buildAudit = parseBuildAuditConfig(buildAuditRaw);
|
|
168
|
+
}
|
|
158
169
|
parseTestBlock(rec, config);
|
|
159
170
|
parseExternalToolchainsBlock(rec, config);
|
|
160
171
|
parseWireBlock(rec, config);
|
|
@@ -425,10 +436,10 @@ function parseTypecheckBlock(rec) {
|
|
|
425
436
|
for (let i = 0; i < projectsRaw.length; i++) {
|
|
426
437
|
const entry = projectsRaw[i];
|
|
427
438
|
if (typeof entry !== 'string' || entry.trim() === '') {
|
|
428
|
-
throw new ConfigError(`Config field "typecheck.projects[${
|
|
439
|
+
throw new ConfigError(`Config field "typecheck.projects[${i}]" must be a non-empty string`);
|
|
429
440
|
}
|
|
430
441
|
if (!isContainedRelativePath(entry)) {
|
|
431
|
-
throw new ConfigError(`Config field "typecheck.projects[${
|
|
442
|
+
throw new ConfigError(`Config field "typecheck.projects[${i}]" must be a project-relative path`);
|
|
432
443
|
}
|
|
433
444
|
projects.push(entry);
|
|
434
445
|
}
|
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
import type { FireForgeConfig } from '../types/config.js';
|
|
11
11
|
import type { JsonObject } from '../types/json.js';
|
|
12
12
|
export { mutateConfig } from './config-mutate.js';
|
|
13
|
-
export {
|
|
14
|
-
export { loadState,
|
|
13
|
+
export { FIREFORGE_DIR, getProjectPaths, SUPPORTED_CONFIG_PATHS, SUPPORTED_CONFIG_ROOT_KEYS, } from './config-paths.js';
|
|
14
|
+
export { loadState, updateState } from './config-state.js';
|
|
15
15
|
export { validateConfig } from './config-validate.js';
|
|
16
16
|
/**
|
|
17
17
|
* Checks if a fireforge.json exists in the given directory.
|
package/dist/src/core/config.js
CHANGED
|
@@ -18,20 +18,10 @@ import { validateConfig } from './config-validate.js';
|
|
|
18
18
|
import { createSiblingLockPath, withFileLock } from './file-lock.js';
|
|
19
19
|
// ---- re-exports ----
|
|
20
20
|
export { mutateConfig } from './config-mutate.js';
|
|
21
|
-
export {
|
|
22
|
-
export { loadState,
|
|
21
|
+
export { FIREFORGE_DIR, getProjectPaths, SUPPORTED_CONFIG_PATHS, SUPPORTED_CONFIG_ROOT_KEYS, } from './config-paths.js';
|
|
22
|
+
export { loadState, updateState } from './config-state.js';
|
|
23
23
|
export { validateConfig } from './config-validate.js';
|
|
24
24
|
// ---- config I/O (stays here because it bridges paths + validation) ----
|
|
25
|
-
/**
|
|
26
|
-
* Config-file existence probe.
|
|
27
|
-
*
|
|
28
|
-
* Uses {@link pathExistsStrict} deliberately: a permission error probing
|
|
29
|
-
* `fireforge.json` must propagate rather than read as "no config here",
|
|
30
|
-
* which is what plain `pathExists` would report.
|
|
31
|
-
*/
|
|
32
|
-
async function configPathExists(path) {
|
|
33
|
-
return fsUtils.pathExistsStrict(path);
|
|
34
|
-
}
|
|
35
25
|
/**
|
|
36
26
|
* Checks if a fireforge.json exists in the given directory.
|
|
37
27
|
* @param root - Root directory to check
|
|
@@ -39,7 +29,9 @@ async function configPathExists(path) {
|
|
|
39
29
|
*/
|
|
40
30
|
export async function configExists(root) {
|
|
41
31
|
const paths = getProjectPaths(root);
|
|
42
|
-
|
|
32
|
+
// Strict probe: a permission error on `fireforge.json` must propagate
|
|
33
|
+
// rather than read as "no config here", which plain `pathExists` reports.
|
|
34
|
+
return fsUtils.pathExistsStrict(paths.config);
|
|
43
35
|
}
|
|
44
36
|
/**
|
|
45
37
|
* Loads and validates the fireforge.json configuration.
|
|
@@ -49,7 +41,9 @@ export async function configExists(root) {
|
|
|
49
41
|
*/
|
|
50
42
|
export async function loadConfig(root) {
|
|
51
43
|
const paths = getProjectPaths(root);
|
|
52
|
-
|
|
44
|
+
// Strict probe: a permission error on `fireforge.json` must propagate
|
|
45
|
+
// rather than read as "no config here", which plain `pathExists` reports.
|
|
46
|
+
if (!(await fsUtils.pathExistsStrict(paths.config))) {
|
|
53
47
|
throw new ConfigNotFoundError(paths.config);
|
|
54
48
|
}
|
|
55
49
|
try {
|
|
@@ -81,7 +75,9 @@ export async function loadConfig(root) {
|
|
|
81
75
|
*/
|
|
82
76
|
export async function loadRawConfigDocument(root) {
|
|
83
77
|
const paths = getProjectPaths(root);
|
|
84
|
-
|
|
78
|
+
// Strict probe: a permission error on `fireforge.json` must propagate
|
|
79
|
+
// rather than read as "no config here", which plain `pathExists` reports.
|
|
80
|
+
if (!(await fsUtils.pathExistsStrict(paths.config))) {
|
|
85
81
|
throw new ConfigNotFoundError(paths.config);
|
|
86
82
|
}
|
|
87
83
|
try {
|
|
@@ -34,10 +34,10 @@
|
|
|
34
34
|
* staging stale while extend still vouches for it. Plain REPLACE has no such
|
|
35
35
|
* window, because the earlier scope simply stops being covered.
|
|
36
36
|
*/
|
|
37
|
-
import { createHash } from 'node:crypto';
|
|
38
37
|
import { readFile } from 'node:fs/promises';
|
|
39
38
|
import { join } from 'node:path';
|
|
40
39
|
import { getNodeErrorCode, toError } from '../utils/errors.js';
|
|
40
|
+
import { sha256Hex } from '../utils/hash.js';
|
|
41
41
|
import { verbose } from '../utils/logger.js';
|
|
42
42
|
import { DELETED_FILE_FINGERPRINT, } from './build-baseline-types.js';
|
|
43
43
|
import { getHead, isMissingHeadError } from './git.js';
|
|
@@ -144,7 +144,7 @@ export function formatExtendCoverageRefusal(result) {
|
|
|
144
144
|
`longer anchored. ${remedy}`);
|
|
145
145
|
case 'fingerprint-diverged':
|
|
146
146
|
return ('--extend-coverage refused: ' +
|
|
147
|
-
`${
|
|
147
|
+
`${result.detail.length} packageable file(s) the recorded build staged have changed ` +
|
|
148
148
|
`since (${formatDivergedPaths(result.detail)}). Extending would vouch for the earlier scope ` +
|
|
149
149
|
`while obj-*/_tests/ still holds its stale staging. ${remedy}`);
|
|
150
150
|
case 'mozconfig-changed':
|
|
@@ -156,7 +156,7 @@ export function formatExtendCoverageRefusal(result) {
|
|
|
156
156
|
function formatDivergedPaths(paths) {
|
|
157
157
|
const head = paths.slice(0, MAX_DIVERGED_REPORTED).join(', ');
|
|
158
158
|
const extra = paths.length - Math.min(paths.length, MAX_DIVERGED_REPORTED);
|
|
159
|
-
return extra > 0 ? `${head}, … (+${
|
|
159
|
+
return extra > 0 ? `${head}, … (+${extra} more)` : head;
|
|
160
160
|
}
|
|
161
161
|
/**
|
|
162
162
|
* Hex-encoded SHA-256 of an engine-relative file, matching the digest the
|
|
@@ -168,7 +168,7 @@ function formatDivergedPaths(paths) {
|
|
|
168
168
|
export async function hashEngineFile(engineDir, relPath) {
|
|
169
169
|
try {
|
|
170
170
|
const buffer = await readFile(join(engineDir, relPath));
|
|
171
|
-
return
|
|
171
|
+
return sha256Hex(buffer);
|
|
172
172
|
}
|
|
173
173
|
catch (error) {
|
|
174
174
|
if (getNodeErrorCode(error) === 'ENOENT') {
|
|
@@ -226,22 +226,22 @@ export function summarizeDiscardBaselines(plans, succeeded) {
|
|
|
226
226
|
const deleted = plans.filter((p) => p.kind === 'patch-deleted').length;
|
|
227
227
|
const unmanaged = plans.filter((p) => p.kind === 'unmanaged').length;
|
|
228
228
|
if (patchBacked + created + deleted === 0) {
|
|
229
|
-
return `${
|
|
229
|
+
return `${succeeded} file(s) restored to upstream state`;
|
|
230
230
|
}
|
|
231
231
|
const parts = [];
|
|
232
232
|
if (patchBacked > 0)
|
|
233
|
-
parts.push(`${
|
|
233
|
+
parts.push(`${patchBacked} to patch baseline`);
|
|
234
234
|
if (created > 0)
|
|
235
|
-
parts.push(`${
|
|
235
|
+
parts.push(`${created} re-materialized`);
|
|
236
236
|
if (deleted > 0)
|
|
237
|
-
parts.push(`${
|
|
237
|
+
parts.push(`${deleted} removed per patch baseline`);
|
|
238
238
|
if (unmanaged > 0)
|
|
239
|
-
parts.push(`${
|
|
240
|
-
return `${
|
|
239
|
+
parts.push(`${unmanaged} to upstream state`);
|
|
240
|
+
return `${succeeded} file(s) restored: ${parts.join(', ')}`;
|
|
241
241
|
}
|
|
242
242
|
/** Warn text for a multi-owner (conflicted) restore. */
|
|
243
243
|
export function describeConflictWarning(plan) {
|
|
244
|
-
return (`${plan.entry.file} is claimed by ${
|
|
244
|
+
return (`${plan.entry.file} is claimed by ${plan.owners.length} patches ` +
|
|
245
245
|
`(${plan.owners.join(', ')}). Restored the cumulative baseline; run ` +
|
|
246
246
|
'"fireforge status --ownership" to resolve ownership.');
|
|
247
247
|
}
|
|
@@ -19,13 +19,6 @@ export interface EngineGitReadyOptions {
|
|
|
19
19
|
*/
|
|
20
20
|
unbornHeadSuffix?: string;
|
|
21
21
|
}
|
|
22
|
-
/**
|
|
23
|
-
* Error factory for callers whose domain uses a class other than
|
|
24
|
-
* `GeneralError`. The furnace family raises `FurnaceError`, which carries
|
|
25
|
-
* its own `userMessage` and exit code 9, so a helper hardcoding
|
|
26
|
-
* `GeneralError` would silently change both.
|
|
27
|
-
*/
|
|
28
|
-
export type PreconditionErrorFactory = (message: string) => Error;
|
|
29
22
|
/**
|
|
30
23
|
* Asserts only that the engine checkout EXISTS.
|
|
31
24
|
*
|
|
@@ -36,9 +29,8 @@ export type PreconditionErrorFactory = (message: string) => Error;
|
|
|
36
29
|
* would refuse runs that work today.
|
|
37
30
|
*
|
|
38
31
|
* @param engineDir - Absolute path to the engine checkout
|
|
39
|
-
* @param errorFactory - Optional domain-specific error constructor
|
|
40
32
|
*/
|
|
41
|
-
export declare function assertEngineExists(engineDir: string
|
|
33
|
+
export declare function assertEngineExists(engineDir: string): Promise<void>;
|
|
42
34
|
/**
|
|
43
35
|
* Asserts that `engineDir` exists, is a git repository, and has a baseline
|
|
44
36
|
* commit — in that order, so the operator gets the most specific applicable
|
|
@@ -20,8 +20,6 @@ const UNBORN_HEAD_REMEDIATION = 'Engine repository has no baseline commit yet
|
|
|
20
20
|
// No terminating period: the suffix below supplies its own punctuation, and
|
|
21
21
|
// hardcoding one here rendered as "…cleanly., then retry the rebase."
|
|
22
22
|
'Re-run "fireforge download --force" to recreate the baseline repository cleanly';
|
|
23
|
-
/** Default factory: the message every non-furnace command already used. */
|
|
24
|
-
const defaultErrorFactory = (message) => new GeneralError(message);
|
|
25
23
|
/**
|
|
26
24
|
* Asserts only that the engine checkout EXISTS.
|
|
27
25
|
*
|
|
@@ -32,11 +30,10 @@ const defaultErrorFactory = (message) => new GeneralError(message);
|
|
|
32
30
|
* would refuse runs that work today.
|
|
33
31
|
*
|
|
34
32
|
* @param engineDir - Absolute path to the engine checkout
|
|
35
|
-
* @param errorFactory - Optional domain-specific error constructor
|
|
36
33
|
*/
|
|
37
|
-
export async function assertEngineExists(engineDir
|
|
34
|
+
export async function assertEngineExists(engineDir) {
|
|
38
35
|
if (!(await pathExists(engineDir))) {
|
|
39
|
-
throw
|
|
36
|
+
throw new GeneralError('Firefox source not found. Run "fireforge download" first.');
|
|
40
37
|
}
|
|
41
38
|
}
|
|
42
39
|
/**
|
|
@@ -48,9 +45,7 @@ export async function assertEngineExists(engineDir, errorFactory = defaultErrorF
|
|
|
48
45
|
* @param options - Optional per-command message tailoring
|
|
49
46
|
*/
|
|
50
47
|
export async function assertEngineGitReady(engineDir, options = {}) {
|
|
51
|
-
|
|
52
|
-
throw new GeneralError('Firefox source not found. Run "fireforge download" first.');
|
|
53
|
-
}
|
|
48
|
+
await assertEngineExists(engineDir);
|
|
54
49
|
if (!(await isGitRepository(engineDir))) {
|
|
55
50
|
throw new GeneralError('Engine directory is not a git repository. Run "fireforge download" to initialize.');
|
|
56
51
|
}
|
|
@@ -3,6 +3,7 @@ import { join } from 'node:path';
|
|
|
3
3
|
import { InconclusiveVerdictError } from '../errors/base.js';
|
|
4
4
|
import { toError } from '../utils/errors.js';
|
|
5
5
|
import { info, warn } from '../utils/logger.js';
|
|
6
|
+
import { sleep } from '../utils/sleep.js';
|
|
6
7
|
import { readLockStatus, withFileLock } from './file-lock.js';
|
|
7
8
|
import { git } from './git-base.js';
|
|
8
9
|
const ENGINE_SESSION_LOCK_PATH = join('.fireforge', 'engine-session.lock');
|
|
@@ -21,19 +22,19 @@ const MAX_WAIT_LOCK_MS = 3600 * 1000;
|
|
|
21
22
|
* owner file degrades to the anonymous form.
|
|
22
23
|
*/
|
|
23
24
|
function formatWaitProgressLine(waitedMs, timeoutMs, holder, queue) {
|
|
24
|
-
const progress = `${
|
|
25
|
+
const progress = `${Math.round(waitedMs / 1000)}s of up to ${Math.round(timeoutMs / 1000)}s`;
|
|
25
26
|
// Queue position: under several concurrent sessions the wait
|
|
26
27
|
// line alone left operators inferring their place from `ps`.
|
|
27
28
|
const position = queue === undefined || queue.depth === 0
|
|
28
29
|
? ''
|
|
29
30
|
: queue.ahead === 0
|
|
30
|
-
? ` You are next in a queue of ${
|
|
31
|
-
: ` ${
|
|
31
|
+
? ` You are next in a queue of ${queue.depth}.`
|
|
32
|
+
: ` ${queue.ahead} ahead of you (queue of ${queue.depth}).`;
|
|
32
33
|
if (holder === undefined) {
|
|
33
34
|
return `Waiting for the FireForge engine lock — ${progress}.${position}`;
|
|
34
35
|
}
|
|
35
36
|
const details = holder.metadata.length > 0 ? ` (${holder.metadata.join(', ')})` : '';
|
|
36
|
-
return `Waiting for the FireForge engine lock held by PID ${
|
|
37
|
+
return `Waiting for the FireForge engine lock held by PID ${holder.pid}${details} — ${progress}.${position}`;
|
|
37
38
|
}
|
|
38
39
|
/**
|
|
39
40
|
* Formats the notice printed when the wait budget is EXTENDED because the
|
|
@@ -45,10 +46,10 @@ function formatWaitProgressLine(waitedMs, timeoutMs, holder, queue) {
|
|
|
45
46
|
* number they typed would otherwise conclude the timeout is broken.
|
|
46
47
|
*/
|
|
47
48
|
export function formatWaitExtendedLine(ahead, budgetMs, requestedMs) {
|
|
48
|
-
const position = ahead === 0 ? 'you are now next' : `${
|
|
49
|
+
const position = ahead === 0 ? 'you are now next' : `${ahead} still ahead of you`;
|
|
49
50
|
return (`The queue advanced (${position}), so the engine-lock wait was extended to ` +
|
|
50
|
-
`${
|
|
51
|
-
`${
|
|
51
|
+
`${Math.round(budgetMs / 1000)}s total (you asked for ` +
|
|
52
|
+
`${Math.round(requestedMs / 1000)}s). A queue that stops moving still ` +
|
|
52
53
|
`gives up on the budget you asked for.`);
|
|
53
54
|
}
|
|
54
55
|
/**
|
|
@@ -128,15 +129,15 @@ export function formatEngineSessionLockStatus(snapshot, holderCpuSeconds) {
|
|
|
128
129
|
}
|
|
129
130
|
const elapsed = snapshot.heldForMs === undefined
|
|
130
131
|
? 'unknown duration'
|
|
131
|
-
: `${
|
|
132
|
+
: `${Math.round(snapshot.heldForMs / 1000)}s`;
|
|
132
133
|
const holder = snapshot.holder;
|
|
133
134
|
const identity = holder === undefined
|
|
134
135
|
? 'held by an unidentified process (no readable owner record)'
|
|
135
|
-
: `held by PID ${
|
|
136
|
+
: `held by PID ${holder.pid}${holder.alive ? '' : ' (NOT RUNNING — stale lock)'}` +
|
|
136
137
|
(holder.metadata.length > 0 ? ` (${holder.metadata.join(', ')})` : '');
|
|
137
138
|
const lines = [
|
|
138
139
|
`Engine session lock: ${identity}, for ${elapsed}.`,
|
|
139
|
-
`Queue depth: ${
|
|
140
|
+
`Queue depth: ${snapshot.queueDepth} waiter(s).`,
|
|
140
141
|
];
|
|
141
142
|
// Liveness answers "does the process exist"; it does not answer "is it
|
|
142
143
|
// getting anywhere". A holder alive for minutes having used a fraction of
|
|
@@ -182,7 +183,7 @@ export async function snapshotEngineGeneration(engineDir) {
|
|
|
182
183
|
// and is reported immediately.
|
|
183
184
|
if (!isIndexLockError(error) || attempt === GENERATION_PROBE_ATTEMPTS - 1)
|
|
184
185
|
break;
|
|
185
|
-
await
|
|
186
|
+
await sleep(GENERATION_PROBE_RETRY_MS);
|
|
186
187
|
}
|
|
187
188
|
}
|
|
188
189
|
return `${UNAVAILABLE_PREFIX}${toError(lastError).message}`;
|
|
@@ -191,11 +192,6 @@ export async function snapshotEngineGeneration(engineDir) {
|
|
|
191
192
|
const GENERATION_PROBE_ATTEMPTS = 3;
|
|
192
193
|
/** Pause between generation-probe attempts. */
|
|
193
194
|
const GENERATION_PROBE_RETRY_MS = 250;
|
|
194
|
-
function delay(ms) {
|
|
195
|
-
return new Promise((resolveDelay) => {
|
|
196
|
-
setTimeout(resolveDelay, ms);
|
|
197
|
-
});
|
|
198
|
-
}
|
|
199
195
|
/**
|
|
200
196
|
* True when a git failure is the transient index-lock contention shape
|
|
201
197
|
* (`Unable to create '…/.git/index.lock': File exists`) rather than a
|
|
@@ -290,7 +286,7 @@ export function describeEngineGenerationDelta(before, after) {
|
|
|
290
286
|
return;
|
|
291
287
|
const head = records.slice(0, DELTA_ENTRY_LIMIT);
|
|
292
288
|
const truncated = records.length - head.length;
|
|
293
|
-
lines.push(`${label}: ${head.join(', ')}${truncated > 0 ? `, … (+${
|
|
289
|
+
lines.push(`${label}: ${head.join(', ')}${truncated > 0 ? `, … (+${truncated} more)` : ''}`);
|
|
294
290
|
};
|
|
295
291
|
render('Working-tree entries that appeared', appeared);
|
|
296
292
|
render('Working-tree entries that went away', disappeared);
|
|
@@ -114,14 +114,14 @@ export function formatAdoptThenSplitRemedy(overlaps, newPatchName, insertionOrde
|
|
|
114
114
|
const suggestedName = newPatchName.replace(/^\d+-/, '').replace(/\.patch$/, '');
|
|
115
115
|
return [
|
|
116
116
|
'',
|
|
117
|
-
`This looks like a new-file + moved-code slice: ${
|
|
117
|
+
`This looks like a new-file + moved-code slice: ${strongest.sharedLines} substantive ` +
|
|
118
118
|
`line(s) the new file(s) would add are still carried by ${strongest.sourcePatch}. ` +
|
|
119
119
|
'Exporting them as their own patch cannot pass cross-patch lint while the source patch ' +
|
|
120
120
|
'still owns the same code, and re-exporting cannot adopt them while they are unmanaged — ' +
|
|
121
121
|
'each guard is right, and together they dead-end.',
|
|
122
122
|
'Working sequence (adopt, then split):',
|
|
123
123
|
` 1. fireforge re-export ${strongest.sourcePatch} --scan ${fileArgs}`,
|
|
124
|
-
` 2. fireforge patch move-files ${strongest.sourcePatch} ${suggestedName} --create --order ${
|
|
124
|
+
` 2. fireforge patch move-files ${strongest.sourcePatch} ${suggestedName} --create --order ${insertionOrder} ${moveArgs}`,
|
|
125
125
|
'Step 1 adopts the new files into the patch that already owns the moved lines; step 2 ' +
|
|
126
126
|
'splits them back out as one transaction, so the queue is never in a state where two ' +
|
|
127
127
|
'patches own the same code.',
|