@hominis/fireforge 0.39.0 → 0.41.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 +100 -0
- package/README.md +35 -5
- package/dist/bin/fireforge.js +25 -4
- package/dist/build-info.json +9 -0
- package/dist/src/cli.js +27 -3
- package/dist/src/commands/bootstrap.js +5 -1
- package/dist/src/commands/discard.js +2 -22
- package/dist/src/commands/doctor/post-rebase-audit.js +27 -4
- package/dist/src/commands/doctor-check-core.d.ts +20 -0
- package/dist/src/commands/doctor-check-core.js +28 -0
- package/dist/src/commands/doctor-external-toolchains.js +2 -0
- package/dist/src/commands/doctor-furnace-manifest-sync.js +144 -46
- package/dist/src/commands/doctor-furnace.js +13 -11
- package/dist/src/commands/doctor-orphaned-harness.js +2 -1
- package/dist/src/commands/doctor.js +2 -2
- package/dist/src/commands/export-all.js +9 -1
- package/dist/src/commands/export-flow.js +27 -9
- package/dist/src/commands/export-placement-conflicts.d.ts +17 -0
- package/dist/src/commands/export-placement-conflicts.js +74 -0
- package/dist/src/commands/export-placement-gate.js +1 -1
- package/dist/src/commands/export-shared.d.ts +8 -1
- package/dist/src/commands/export-shared.js +30 -10
- package/dist/src/commands/export.js +2 -2
- package/dist/src/commands/furnace/apply.js +4 -3
- package/dist/src/commands/furnace/chrome-doc-remove.js +13 -2
- package/dist/src/commands/furnace/chrome-doc.js +13 -2
- package/dist/src/commands/furnace/create-browser-test.d.ts +5 -0
- package/dist/src/commands/furnace/create-browser-test.js +5 -0
- package/dist/src/commands/furnace/create.js +2 -2
- package/dist/src/commands/furnace/diff.js +2 -1
- package/dist/src/commands/furnace/init.js +4 -6
- package/dist/src/commands/furnace/override.js +2 -2
- package/dist/src/commands/furnace/remove-mutations.d.ts +40 -0
- package/dist/src/commands/furnace/remove-mutations.js +171 -0
- package/dist/src/commands/furnace/remove.js +21 -128
- package/dist/src/commands/furnace/rename-browser-test.js +54 -5
- package/dist/src/commands/furnace/scan.js +27 -36
- package/dist/src/commands/furnace/status.js +30 -28
- package/dist/src/commands/furnace/validate.js +25 -18
- package/dist/src/commands/import.js +6 -20
- package/dist/src/commands/lint-per-patch.js +73 -51
- package/dist/src/commands/lint-per-run-checkjs.d.ts +64 -0
- package/dist/src/commands/lint-per-run-checkjs.js +102 -0
- package/dist/src/commands/lint-report.d.ts +21 -0
- package/dist/src/commands/lint-report.js +62 -0
- package/dist/src/commands/lint.js +26 -22
- package/dist/src/commands/manifest.js +3 -1
- package/dist/src/commands/package.js +9 -16
- package/dist/src/commands/patch/compact.js +5 -5
- package/dist/src/commands/patch/delete.js +5 -5
- package/dist/src/commands/patch/lint-ignore.js +3 -2
- package/dist/src/commands/patch/move-files-create.js +22 -4
- package/dist/src/commands/patch/move-files-into.js +14 -10
- package/dist/src/commands/patch/move-files.js +6 -5
- package/dist/src/commands/patch/rename.d.ts +2 -2
- package/dist/src/commands/patch/rename.js +152 -51
- package/dist/src/commands/patch/reorder.d.ts +9 -0
- package/dist/src/commands/patch/reorder.js +14 -10
- package/dist/src/commands/patch/split-plan.d.ts +13 -6
- package/dist/src/commands/patch/split-plan.js +22 -12
- package/dist/src/commands/patch/split.d.ts +1 -1
- package/dist/src/commands/patch/split.js +23 -9
- package/dist/src/commands/patch/staged-dependency-validate.d.ts +15 -0
- package/dist/src/commands/patch/staged-dependency-validate.js +49 -0
- package/dist/src/commands/patch/staged-dependency.js +112 -14
- package/dist/src/commands/re-export-adjacent.d.ts +34 -0
- package/dist/src/commands/re-export-adjacent.js +105 -0
- package/dist/src/commands/re-export-bulk-scan.d.ts +13 -0
- package/dist/src/commands/re-export-bulk-scan.js +142 -2
- package/dist/src/commands/re-export-drift.d.ts +74 -0
- package/dist/src/commands/re-export-drift.js +234 -0
- package/dist/src/commands/re-export-files.js +5 -3
- package/dist/src/commands/re-export-lint.d.ts +97 -0
- package/dist/src/commands/re-export-lint.js +210 -0
- package/dist/src/commands/re-export-options.js +11 -0
- package/dist/src/commands/re-export-register.d.ts +18 -0
- package/dist/src/commands/re-export-register.js +61 -0
- package/dist/src/commands/re-export-scan.js +13 -7
- package/dist/src/commands/re-export-single.d.ts +20 -0
- package/dist/src/commands/re-export-single.js +261 -0
- package/dist/src/commands/re-export.d.ts +0 -4
- package/dist/src/commands/re-export.js +123 -289
- package/dist/src/commands/rebase/abort.js +13 -4
- package/dist/src/commands/rebase/continue.js +11 -4
- package/dist/src/commands/rebase/index.js +19 -29
- package/dist/src/commands/rebase/patch-loop.js +12 -0
- package/dist/src/commands/reset.js +4 -23
- package/dist/src/commands/resolve.js +2 -9
- package/dist/src/commands/run.js +29 -23
- package/dist/src/commands/source.js +3 -9
- package/dist/src/commands/status-check.d.ts +48 -0
- package/dist/src/commands/status-check.js +106 -0
- package/dist/src/commands/status-json.d.ts +31 -0
- package/dist/src/commands/status-json.js +76 -0
- package/dist/src/commands/status-output.d.ts +1 -0
- package/dist/src/commands/status-output.js +10 -1
- package/dist/src/commands/status-ownership.d.ts +52 -0
- package/dist/src/commands/status-ownership.js +57 -0
- package/dist/src/commands/status.js +190 -170
- package/dist/src/commands/test-build-phase.d.ts +19 -0
- package/dist/src/commands/test-build-phase.js +168 -0
- package/dist/src/commands/test-diagnose.js +30 -2
- package/dist/src/commands/test-modes.d.ts +9 -1
- package/dist/src/commands/test-modes.js +46 -13
- package/dist/src/commands/test-register.js +59 -27
- package/dist/src/commands/test-run.d.ts +29 -3
- package/dist/src/commands/test-run.js +59 -7
- package/dist/src/commands/test-verdict.d.ts +32 -0
- package/dist/src/commands/test-verdict.js +48 -0
- package/dist/src/commands/test.d.ts +9 -0
- package/dist/src/commands/test.js +100 -123
- package/dist/src/commands/token-coverage.js +4 -9
- package/dist/src/commands/token.js +4 -1
- package/dist/src/commands/tree.d.ts +19 -0
- package/dist/src/commands/tree.js +320 -0
- package/dist/src/commands/verify.js +2 -2
- package/dist/src/commands/watch.js +22 -21
- package/dist/src/commands/wire.js +1 -1
- package/dist/src/core/ast-utils.d.ts +11 -0
- package/dist/src/core/ast-utils.js +11 -0
- package/dist/src/core/build-audit-platform.js +6 -1
- package/dist/src/core/build-audit-registration.js +2 -0
- package/dist/src/core/build-audit-resolve.d.ts +13 -0
- package/dist/src/core/build-audit-resolve.js +34 -13
- package/dist/src/core/build-audit.js +4 -20
- package/dist/src/core/build-baseline-types.d.ts +29 -8
- package/dist/src/core/build-baseline-types.js +2 -1
- package/dist/src/core/build-baseline.d.ts +10 -2
- package/dist/src/core/build-baseline.js +39 -8
- package/dist/src/core/build-prepare.d.ts +4 -0
- package/dist/src/core/build-prepare.js +7 -1
- package/dist/src/core/config-paths.d.ts +1 -1
- package/dist/src/core/config-paths.js +2 -0
- package/dist/src/core/config-validate-test-toolchains.js +3 -2
- package/dist/src/core/config-validate.js +45 -30
- package/dist/src/core/config.js +16 -3
- package/dist/src/core/coverage-extend.d.ts +89 -0
- package/dist/src/core/coverage-extend.js +188 -0
- package/dist/src/core/destructive.js +3 -1
- package/dist/src/core/discard-baseline.js +18 -4
- package/dist/src/core/engine-precondition.d.ts +35 -0
- package/dist/src/core/engine-precondition.js +50 -0
- package/dist/src/core/engine-session-lock.d.ts +38 -1
- package/dist/src/core/engine-session-lock.js +78 -4
- package/dist/src/core/file-lock.d.ts +7 -0
- package/dist/src/core/file-lock.js +20 -30
- package/dist/src/core/firefox.js +5 -2
- package/dist/src/core/furnace-apply-helpers.d.ts +12 -1
- package/dist/src/core/furnace-apply-helpers.js +12 -3
- package/dist/src/core/furnace-apply-output.js +9 -0
- package/dist/src/core/furnace-apply-overwrite-warn.d.ts +63 -0
- package/dist/src/core/furnace-apply-overwrite-warn.js +123 -0
- package/dist/src/core/furnace-apply-undeploy-actions.d.ts +11 -0
- package/dist/src/core/furnace-apply-undeploy-actions.js +49 -0
- package/dist/src/core/furnace-apply.d.ts +16 -4
- package/dist/src/core/furnace-apply.js +39 -43
- package/dist/src/core/furnace-config.js +2 -0
- package/dist/src/core/furnace-css-fragments.js +2 -0
- package/dist/src/core/furnace-jsconfig.js +14 -7
- package/dist/src/core/furnace-registration-ast.js +8 -8
- package/dist/src/core/furnace-registration-remove.js +6 -3
- package/dist/src/core/furnace-registration.js +9 -12
- package/dist/src/core/furnace-rollback.js +1 -1
- package/dist/src/core/furnace-scanner.js +2 -1
- package/dist/src/core/furnace-stale-export.js +2 -1
- package/dist/src/core/furnace-state-persist.d.ts +4 -6
- package/dist/src/core/furnace-validate-compatibility.js +4 -7
- package/dist/src/core/furnace-validate-helpers.js +33 -1
- package/dist/src/core/furnace-validate-registration.js +16 -0
- package/dist/src/core/furnace-validate.js +4 -2
- package/dist/src/core/git-diff.js +37 -2
- package/dist/src/core/lint-cache.d.ts +15 -5
- package/dist/src/core/lint-cache.js +49 -14
- package/dist/src/core/mach-build-artifacts.d.ts +63 -1
- package/dist/src/core/mach-build-artifacts.js +108 -2
- package/dist/src/core/mach-resource-shim.js +1 -0
- package/dist/src/core/marionette-port.d.ts +16 -0
- package/dist/src/core/marionette-port.js +93 -8
- package/dist/src/core/marionette-preflight.js +6 -5
- package/dist/src/core/patch-apply.d.ts +20 -13
- package/dist/src/core/patch-apply.js +32 -33
- package/dist/src/core/patch-export.d.ts +2 -2
- package/dist/src/core/patch-export.js +4 -6
- package/dist/src/core/patch-lint-checkjs.d.ts +42 -2
- package/dist/src/core/patch-lint-checkjs.js +132 -8
- package/dist/src/core/patch-lint-chrome-jsdoc.js +9 -0
- package/dist/src/core/patch-lint-creators.d.ts +30 -0
- package/dist/src/core/patch-lint-creators.js +45 -0
- package/dist/src/core/patch-lint-cross.d.ts +1 -17
- package/dist/src/core/patch-lint-cross.js +16 -32
- package/dist/src/core/patch-lint-css.js +64 -16
- package/dist/src/core/patch-lint-diff.d.ts +8 -0
- package/dist/src/core/patch-lint-diff.js +31 -0
- package/dist/src/core/patch-lint-jsdoc.d.ts +3 -1
- package/dist/src/core/patch-lint-jsdoc.js +15 -5
- package/dist/src/core/patch-lint-module-registration.d.ts +15 -0
- package/dist/src/core/patch-lint-module-registration.js +88 -0
- package/dist/src/core/patch-lint-mozbuild.js +99 -11
- package/dist/src/core/patch-lint-ownership.d.ts +21 -0
- package/dist/src/core/patch-lint-ownership.js +30 -0
- package/dist/src/core/patch-lint-reexports.d.ts +2 -1
- package/dist/src/core/patch-lint-reexports.js +1 -0
- package/dist/src/core/patch-lint.d.ts +31 -0
- package/dist/src/core/patch-lint.js +114 -8
- package/dist/src/core/patch-lock.d.ts +12 -1
- package/dist/src/core/patch-lock.js +40 -4
- package/dist/src/core/patch-manifest-consistency.js +15 -3
- package/dist/src/core/patch-manifest.d.ts +1 -1
- package/dist/src/core/patch-parse.d.ts +8 -0
- package/dist/src/core/patch-parse.js +10 -0
- package/dist/src/core/patch-policy.d.ts +7 -0
- package/dist/src/core/patch-policy.js +4 -1
- package/dist/src/core/patch-transform.d.ts +10 -0
- package/dist/src/core/patch-transform.js +13 -2
- package/dist/src/core/rebase-session.d.ts +44 -2
- package/dist/src/core/rebase-session.js +114 -17
- package/dist/src/core/register-browser-content.js +2 -1
- package/dist/src/core/register-shared-css.js +2 -1
- package/dist/src/core/smoke-patterns.d.ts +24 -16
- package/dist/src/core/smoke-patterns.js +26 -14
- package/dist/src/core/status-binary.d.ts +55 -0
- package/dist/src/core/status-binary.js +124 -0
- package/dist/src/core/status-classify.d.ts +22 -1
- package/dist/src/core/status-classify.js +112 -70
- package/dist/src/core/test-harness-crash.d.ts +32 -1
- package/dist/src/core/test-harness-crash.js +59 -4
- package/dist/src/core/test-path-scope.js +2 -0
- package/dist/src/core/test-stale-check.js +1 -20
- package/dist/src/core/token-category.d.ts +40 -0
- package/dist/src/core/token-category.js +264 -0
- package/dist/src/core/token-manager.d.ts +7 -0
- package/dist/src/core/token-manager.js +48 -196
- package/dist/src/core/toolchain-preflight.js +24 -26
- package/dist/src/core/tree-cow.d.ts +18 -0
- package/dist/src/core/tree-cow.js +71 -0
- package/dist/src/core/tree-guard.d.ts +35 -0
- package/dist/src/core/tree-guard.js +179 -0
- package/dist/src/core/tree-store.d.ts +129 -0
- package/dist/src/core/tree-store.js +452 -0
- package/dist/src/core/typecheck-shim.d.ts +14 -0
- package/dist/src/core/typecheck-shim.js +41 -2
- package/dist/src/core/typecheck.js +2 -1
- package/dist/src/core/wire-destroy.js +14 -3
- package/dist/src/core/wire-init.js +37 -40
- package/dist/src/core/wire-subscript.js +11 -3
- package/dist/src/core/wire-utils.js +4 -4
- package/dist/src/core/xpcshell-appdir.js +3 -0
- package/dist/src/core/xpcshell-profile-dir.d.ts +12 -0
- package/dist/src/core/xpcshell-profile-dir.js +45 -0
- package/dist/src/errors/base.d.ts +13 -0
- package/dist/src/errors/base.js +13 -0
- package/dist/src/errors/git.d.ts +8 -0
- package/dist/src/errors/git.js +17 -0
- package/dist/src/errors/rebase.d.ts +13 -0
- package/dist/src/errors/rebase.js +17 -0
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.js +1 -0
- package/dist/src/types/commands/options.d.ts +119 -3
- package/dist/src/types/commands/patches.d.ts +8 -0
- package/dist/src/types/config.d.ts +19 -0
- package/dist/src/types/furnace.d.ts +7 -0
- package/dist/src/utils/build-info.d.ts +31 -0
- package/dist/src/utils/build-info.js +98 -0
- package/dist/src/utils/concurrency.d.ts +8 -0
- package/dist/src/utils/concurrency.js +21 -0
- package/dist/src/utils/errors.d.ts +39 -0
- package/dist/src/utils/errors.js +55 -0
- package/dist/src/utils/fs.js +5 -12
- package/dist/src/utils/logger.d.ts +14 -2
- package/dist/src/utils/logger.js +43 -13
- package/dist/src/utils/options.d.ts +21 -4
- package/dist/src/utils/options.js +26 -6
- package/dist/src/utils/package-root.js +7 -4
- package/dist/src/utils/process-group.js +13 -2
- package/dist/src/utils/stdio-drain.d.ts +28 -0
- package/dist/src/utils/stdio-drain.js +54 -0
- package/dist/src/utils/validation.d.ts +33 -2
- package/dist/src/utils/validation.js +57 -2
- package/package.json +13 -13
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* Read-only guard for verification trees (FORGE G15).
|
|
4
|
+
*
|
|
5
|
+
* A tree is a snapshot: its `patches/` and `components/` are copies with
|
|
6
|
+
* no merge-back model, so any command that mutates project or engine
|
|
7
|
+
* state must run in the primary tree. Enforcement is DEFAULT-DENY via an
|
|
8
|
+
* explicit verdict table over every top-level command — a newly added
|
|
9
|
+
* command is refused inside trees until its author classifies it (the
|
|
10
|
+
* drift test in `tree-guard.test.ts` fails otherwise) — and lives in ONE
|
|
11
|
+
* commander `preAction` hook installed by `createProgram()`, so the
|
|
12
|
+
* guard fires no matter how the cwd ended up inside a tree (`tree exec`
|
|
13
|
+
* or a plain `cd`).
|
|
14
|
+
*/
|
|
15
|
+
import { existsSync } from 'node:fs';
|
|
16
|
+
import { dirname, join, resolve } from 'node:path';
|
|
17
|
+
import { GeneralError } from '../errors/base.js';
|
|
18
|
+
import { getTreeMarkerPath, readTreeMarkerState, } from './tree-store.js';
|
|
19
|
+
/**
|
|
20
|
+
* Per-command verdicts. `allowed` runs unconditionally; `refused` never
|
|
21
|
+
* runs in a tree; `conditional` consults `TREE_CONDITIONAL_CHECKS` with
|
|
22
|
+
* the invoked subcommand chain, parsed options, and the tree marker.
|
|
23
|
+
*/
|
|
24
|
+
export const TREE_COMMAND_VERDICTS = {
|
|
25
|
+
setup: 'refused',
|
|
26
|
+
source: 'refused',
|
|
27
|
+
download: 'refused',
|
|
28
|
+
bootstrap: 'refused',
|
|
29
|
+
import: 'refused',
|
|
30
|
+
resolve: 'refused',
|
|
31
|
+
build: 'refused',
|
|
32
|
+
run: 'refused',
|
|
33
|
+
status: 'allowed',
|
|
34
|
+
reset: 'refused',
|
|
35
|
+
discard: 'refused',
|
|
36
|
+
export: 'conditional',
|
|
37
|
+
'export-all': 'conditional',
|
|
38
|
+
're-export': 'conditional',
|
|
39
|
+
patch: 'refused',
|
|
40
|
+
rebase: 'refused',
|
|
41
|
+
package: 'refused',
|
|
42
|
+
watch: 'refused',
|
|
43
|
+
// Build-less runs only, and only in trees whose marker records a cloned
|
|
44
|
+
// (mozinfo-rewritten) objdir — see the conditional check below.
|
|
45
|
+
test: 'conditional',
|
|
46
|
+
config: 'conditional',
|
|
47
|
+
doctor: 'conditional',
|
|
48
|
+
register: 'refused',
|
|
49
|
+
wire: 'refused',
|
|
50
|
+
token: 'refused',
|
|
51
|
+
lint: 'allowed',
|
|
52
|
+
typecheck: 'allowed',
|
|
53
|
+
verify: 'allowed',
|
|
54
|
+
furnace: 'refused',
|
|
55
|
+
// No nesting; `tree list` inside a tree is pointless but harmless — the
|
|
56
|
+
// subcommand check below allows only `list`.
|
|
57
|
+
tree: 'conditional',
|
|
58
|
+
};
|
|
59
|
+
/** The command set named in refusal messages. */
|
|
60
|
+
const ALLOWED_SUMMARY = 'Verification trees support: status, lint, typecheck, verify, doctor (read-only), ' +
|
|
61
|
+
'config (read), export --dry-run, export-all --dry-run, re-export --dry-run, and — ' +
|
|
62
|
+
'in trees created with --with-objdir — build-less test.';
|
|
63
|
+
const TREE_CONDITIONAL_CHECKS = {
|
|
64
|
+
// Exports write patches/ + patches.json; only the dry-run preview is a read.
|
|
65
|
+
export: ({ options }) => options['dryRun'] === true,
|
|
66
|
+
'export-all': ({ options }) => options['dryRun'] === true,
|
|
67
|
+
// Same rule for refreshing existing patches: the dry-run projection is a
|
|
68
|
+
// read (proven side-effect free and runtime-enforced by
|
|
69
|
+
// withDryRunPurityGuard — FORGE H1/H3); a real re-export writes patches/.
|
|
70
|
+
're-export': ({ options }) => options['dryRun'] === true,
|
|
71
|
+
// `config` with no positional value prints; with a value it writes.
|
|
72
|
+
config: ({ args }) => args.filter((a) => typeof a === 'string').length <= 1,
|
|
73
|
+
// Doctor reads unless a repair flag is set.
|
|
74
|
+
doctor: ({ options }) => options['repairFurnace'] !== true && options['fix'] !== true,
|
|
75
|
+
// Build-less test needs the objdir the marker records as cloned,
|
|
76
|
+
// mozinfo-rewritten AND reconfigured in-tree; `test --build` rebuilds
|
|
77
|
+
// the engine and stays primary-only (a tree is a snapshot with no
|
|
78
|
+
// merge-back model, so build outputs mutated in-tree go nowhere).
|
|
79
|
+
test: ({ options }, marker) => marker.clonedObjdir !== undefined && options['build'] !== true,
|
|
80
|
+
// No tree lifecycle inside a tree; `tree list` is the only read.
|
|
81
|
+
tree: ({ subcommands }) => subcommands[0] === 'list',
|
|
82
|
+
};
|
|
83
|
+
/**
|
|
84
|
+
* Command-specific refusal hints appended after the generic sentence —
|
|
85
|
+
* for verdicts where "mutates project or engine state" alone would
|
|
86
|
+
* misdiagnose the actual reason.
|
|
87
|
+
*/
|
|
88
|
+
const TREE_REFUSAL_HINTS = {
|
|
89
|
+
test: ({ options }, marker) => {
|
|
90
|
+
if (options['build'] === true) {
|
|
91
|
+
return '"test --build" rebuilds the engine and must run in the primary tree; run build-less "fireforge test" here instead.';
|
|
92
|
+
}
|
|
93
|
+
if (marker.clonedObjdir === undefined) {
|
|
94
|
+
return 'In-tree test requires a tree created with "fireforge tree create <name> --with-objdir".';
|
|
95
|
+
}
|
|
96
|
+
return undefined;
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
/**
|
|
100
|
+
* Throws when `commandName` (with its subcommand chain and options) must
|
|
101
|
+
* not run inside the tree described by `marker`. Exported for the
|
|
102
|
+
* preAction hook in cli.ts and for the guard tests.
|
|
103
|
+
*/
|
|
104
|
+
export function enforceTreeGuard(marker, commandName, input) {
|
|
105
|
+
const verdict = TREE_COMMAND_VERDICTS[commandName] ?? 'refused';
|
|
106
|
+
if (verdict === 'allowed')
|
|
107
|
+
return;
|
|
108
|
+
const conditional = TREE_CONDITIONAL_CHECKS[commandName];
|
|
109
|
+
if (verdict === 'conditional' && conditional && conditional(input, marker))
|
|
110
|
+
return;
|
|
111
|
+
const hint = TREE_REFUSAL_HINTS[commandName]?.(input, marker);
|
|
112
|
+
throw new GeneralError(`This is a FireForge verification tree ("${marker.name}", created from ${marker.primaryRoot}). ` +
|
|
113
|
+
`"${[commandName, ...input.subcommands].join(' ')}" mutates project or engine state and must run in the primary tree. ` +
|
|
114
|
+
(hint === undefined ? '' : `${hint} `) +
|
|
115
|
+
ALLOWED_SUMMARY);
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Commander preAction hook body: locates the enclosing tree (if any) and
|
|
119
|
+
* enforces the verdict table for the invoked command chain.
|
|
120
|
+
*/
|
|
121
|
+
export async function runTreeGuardHook(rootCommandName, actionCommand) {
|
|
122
|
+
// Resolve the top-level command and subcommand chain from the action
|
|
123
|
+
// command's parent links (commander nests subcommands).
|
|
124
|
+
const chain = [];
|
|
125
|
+
let node = actionCommand;
|
|
126
|
+
while (node && node.parent) {
|
|
127
|
+
chain.unshift(node.name());
|
|
128
|
+
node = node.parent;
|
|
129
|
+
}
|
|
130
|
+
const [commandName, ...subcommands] = chain;
|
|
131
|
+
if (commandName === undefined || commandName === rootCommandName)
|
|
132
|
+
return;
|
|
133
|
+
const options = actionCommand.optsWithGlobals();
|
|
134
|
+
const located = await (async () => {
|
|
135
|
+
let current = resolve(process.cwd());
|
|
136
|
+
for (let depth = 0; depth < 50; depth++) {
|
|
137
|
+
if (existsSync(join(current, 'fireforge.json'))) {
|
|
138
|
+
return { root: current, state: await readTreeMarkerState(current) };
|
|
139
|
+
}
|
|
140
|
+
const parent = dirname(current);
|
|
141
|
+
if (parent === current)
|
|
142
|
+
break;
|
|
143
|
+
current = parent;
|
|
144
|
+
}
|
|
145
|
+
return undefined;
|
|
146
|
+
})();
|
|
147
|
+
if (located === undefined)
|
|
148
|
+
return;
|
|
149
|
+
const state = located.state;
|
|
150
|
+
if (state.kind === 'absent')
|
|
151
|
+
return;
|
|
152
|
+
// A marker we cannot read is not evidence that this is not a tree — it is
|
|
153
|
+
// evidence that we cannot tell. Treating it as "not a tree" would grant the
|
|
154
|
+
// full mutating command set to a snapshot on the strength of a file that
|
|
155
|
+
// failed to parse, which is exactly backwards for a default-deny guard.
|
|
156
|
+
if (state.kind === 'corrupt') {
|
|
157
|
+
if (options['ignoreCorruptTreeMarker'] === true)
|
|
158
|
+
return;
|
|
159
|
+
// Unconditionally-allowed commands never consult marker fields — their
|
|
160
|
+
// verdict is the same in every tree — so an unreadable marker cannot
|
|
161
|
+
// change their answer. Letting them through keeps read-only diagnostics
|
|
162
|
+
// (status, lint) usable on the very tree the operator needs to inspect.
|
|
163
|
+
// 'conditional' verdicts stay refused: their predicates can write.
|
|
164
|
+
if (TREE_COMMAND_VERDICTS[commandName] === 'allowed')
|
|
165
|
+
return;
|
|
166
|
+
const markerPath = getTreeMarkerPath(located.root);
|
|
167
|
+
throw new GeneralError(`${markerPath} identifies this directory as a FireForge verification tree, but ${state.reason}. ` +
|
|
168
|
+
`Refusing "${[commandName, ...subcommands].join(' ')}" because it cannot be established ` +
|
|
169
|
+
'whether this is a snapshot or the primary tree.\n\n' +
|
|
170
|
+
'Recreate the tree (fireforge tree remove <name> && fireforge tree create <name>), or — if ' +
|
|
171
|
+
'this really is the primary tree — delete the stray marker. ' +
|
|
172
|
+
'Pass --ignore-corrupt-tree-marker to proceed anyway.');
|
|
173
|
+
}
|
|
174
|
+
enforceTreeGuard(state.marker, commandName, {
|
|
175
|
+
subcommands,
|
|
176
|
+
options,
|
|
177
|
+
args: actionCommand.args,
|
|
178
|
+
});
|
|
179
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { type CowCapability } from './tree-cow.js';
|
|
2
|
+
/** `.fireforge/tree.json` — identifies a directory as a verification tree. */
|
|
3
|
+
export interface TreeMarker {
|
|
4
|
+
schemaVersion: 1;
|
|
5
|
+
name: string;
|
|
6
|
+
primaryRoot: string;
|
|
7
|
+
createdAt: string;
|
|
8
|
+
engineHead: string | null;
|
|
9
|
+
/** Hash of the primary engine's tracked + untracked working-tree diff. */
|
|
10
|
+
engineFingerprint?: string | null;
|
|
11
|
+
patchesFingerprint: string | null;
|
|
12
|
+
/**
|
|
13
|
+
* Name of the obj-* directory cloned into this tree, written only after
|
|
14
|
+
* its mozinfo.json was successfully rewritten to the tree's paths AND the
|
|
15
|
+
* caller's in-tree reconfigure regenerated the remaining configure output.
|
|
16
|
+
* Its presence is what lets the guard admit build-less `test` in-tree
|
|
17
|
+
* (`tree-guard.ts`); absent on trees created without `--with-objdir`.
|
|
18
|
+
*/
|
|
19
|
+
clonedObjdir?: string;
|
|
20
|
+
}
|
|
21
|
+
/** One row of `tree list`. */
|
|
22
|
+
export interface TreeListEntry {
|
|
23
|
+
name: string;
|
|
24
|
+
path: string;
|
|
25
|
+
createdAt: string;
|
|
26
|
+
staleness: 'fresh' | 'stale (engine advanced)' | 'stale (engine worktree changed)' | 'stale (patches changed)' | 'unknown';
|
|
27
|
+
}
|
|
28
|
+
/** Absolute trees directory for a primary root. */
|
|
29
|
+
export declare function getTreesDir(primaryRoot: string): string;
|
|
30
|
+
/** Serialises tree create/remove against each other (primary-side sidecar lock). */
|
|
31
|
+
export declare function withTreeLifecycleLock<T>(primaryRoot: string, operation: () => Promise<T>): Promise<T>;
|
|
32
|
+
/**
|
|
33
|
+
* Outcome of reading a tree marker.
|
|
34
|
+
*
|
|
35
|
+
* The three states must stay distinct because they carry opposite safety
|
|
36
|
+
* meanings. `absent` means "this directory is not a tree", which is what lets
|
|
37
|
+
* every mutating command run. `corrupt` means "this directory claims to be a
|
|
38
|
+
* tree but we cannot read the claim" — collapsing it into `absent` (as a bare
|
|
39
|
+
* `undefined` return does) hands a snapshot the full mutating command set on
|
|
40
|
+
* the strength of a file we failed to parse, inverting `tree-guard.ts`'s
|
|
41
|
+
* default-deny design. Deleting one field from a marker was enough to run
|
|
42
|
+
* `reset` inside a clone.
|
|
43
|
+
*/
|
|
44
|
+
export type TreeMarkerRead = {
|
|
45
|
+
kind: 'absent';
|
|
46
|
+
} | {
|
|
47
|
+
kind: 'valid';
|
|
48
|
+
marker: TreeMarker;
|
|
49
|
+
} | {
|
|
50
|
+
kind: 'corrupt';
|
|
51
|
+
reason: string;
|
|
52
|
+
};
|
|
53
|
+
/** Absolute path of a root's tree marker. */
|
|
54
|
+
export declare function getTreeMarkerPath(root: string): string;
|
|
55
|
+
/**
|
|
56
|
+
* Reads the tree marker for a project root, distinguishing "not a tree" from
|
|
57
|
+
* "a tree whose marker we could not read". Callers that make a safety decision
|
|
58
|
+
* (the guard hook, the no-nesting refusal) must use this rather than
|
|
59
|
+
* {@link readTreeMarker}.
|
|
60
|
+
*
|
|
61
|
+
* Only ENOENT/ENOTDIR mean `absent`: any other probe failure (EACCES on the
|
|
62
|
+
* `.fireforge` directory, EIO, …) reports `corrupt`, because "we cannot read
|
|
63
|
+
* the marker" is not evidence that there is no marker — a `pathExists`
|
|
64
|
+
* pre-check here classified a tree with an unreadable `.fireforge` as
|
|
65
|
+
* `absent` and handed it the full mutating command set.
|
|
66
|
+
*/
|
|
67
|
+
export declare function readTreeMarkerState(root: string): Promise<TreeMarkerRead>;
|
|
68
|
+
/**
|
|
69
|
+
* Reads the tree marker for a project root, or undefined when it is absent
|
|
70
|
+
* OR unreadable.
|
|
71
|
+
*
|
|
72
|
+
* For display and best-effort paths only (`tree list` renders an unreadable
|
|
73
|
+
* marker as staleness 'unknown'). Anything that decides whether a mutation may
|
|
74
|
+
* proceed must call {@link readTreeMarkerState} and handle `corrupt`.
|
|
75
|
+
*/
|
|
76
|
+
export declare function readTreeMarker(root: string): Promise<TreeMarker | undefined>;
|
|
77
|
+
/**
|
|
78
|
+
* Refuses a build-less in-tree `test` when the objdir preflight found is not
|
|
79
|
+
* the one this tree's marker vouched for as rewritten-and-reconfigured. A
|
|
80
|
+
* mismatch means an objdir appeared in the tree through some path other than
|
|
81
|
+
* `tree create --with-objdir` (manual copy, partial restore), so nothing
|
|
82
|
+
* proves it was relocated — running against it could consult primary paths.
|
|
83
|
+
* No-op outside a tree; a corrupt marker is already refused by the guard.
|
|
84
|
+
*/
|
|
85
|
+
export declare function assertObjdirMatchesTreeMarker(projectRoot: string, objDir: string | undefined): Promise<void>;
|
|
86
|
+
/** Current primary-side snapshot identity, including dirty source and patch bodies. */
|
|
87
|
+
export declare function computePrimaryFingerprint(primaryRoot: string): Promise<{
|
|
88
|
+
engineHead: string | null;
|
|
89
|
+
engineFingerprint?: string | null;
|
|
90
|
+
patchesFingerprint: string | null;
|
|
91
|
+
}>;
|
|
92
|
+
/** Validates a tree name: one path segment, no traversal. */
|
|
93
|
+
export declare function assertValidTreeName(name: string): void;
|
|
94
|
+
/**
|
|
95
|
+
* Materialises the tree at `treeRoot` from `primaryRoot` using
|
|
96
|
+
* `capability` ('none' = plain copy, already gated on --force-copy by
|
|
97
|
+
* the caller). The engine is cloned one TOP-LEVEL entry at a time so unwanted
|
|
98
|
+
* `obj-*` directories are never traversed or materialised. That distinction
|
|
99
|
+
* is substantial on Firefox trees: even CoW-cloning and deleting an objdir
|
|
100
|
+
* touches metadata for millions of build outputs. With `withObjdir` exactly
|
|
101
|
+
* that objdir is included — after {@link assertCloneSafeObjdir}
|
|
102
|
+
* refuses symlinked or engine-escaping objdirs before any copying —
|
|
103
|
+
* rewritten to the tree (see below), and
|
|
104
|
+
* reconfigured in-tree via the caller-supplied `reconfigure` hook. A
|
|
105
|
+
* fresh `.fireforge/` is created with only `state.json` (when present),
|
|
106
|
+
* the last-build baseline (withObjdir only), and the tree marker.
|
|
107
|
+
*/
|
|
108
|
+
export declare function cloneTree(args: {
|
|
109
|
+
primaryRoot: string;
|
|
110
|
+
treeRoot: string;
|
|
111
|
+
name: string;
|
|
112
|
+
capability: CowCapability;
|
|
113
|
+
createdAt: string;
|
|
114
|
+
withObjdir?: {
|
|
115
|
+
objDir: string;
|
|
116
|
+
reconfigure: (treeEngineDir: string) => Promise<void>;
|
|
117
|
+
};
|
|
118
|
+
}): Promise<TreeMarker>;
|
|
119
|
+
/** Lists trees with staleness against the current primary state. */
|
|
120
|
+
export declare function listTrees(primaryRoot: string): Promise<TreeListEntry[]>;
|
|
121
|
+
/**
|
|
122
|
+
* Removes one tree. Refuses when a live process holds the tree's build
|
|
123
|
+
* or engine-session lock — or when a lock directory exists whose ownership
|
|
124
|
+
* cannot be established, which `--force` overrides — and never deletes a path
|
|
125
|
+
* outside the trees directory (containment check before `rm -rf`).
|
|
126
|
+
*/
|
|
127
|
+
export declare function removeTree(primaryRoot: string, name: string, options?: {
|
|
128
|
+
force?: boolean;
|
|
129
|
+
}): Promise<void>;
|