@hominis/fireforge 0.40.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 +79 -0
- package/README.md +16 -10
- package/dist/bin/fireforge.js +25 -4
- package/dist/build-info.json +9 -0
- package/dist/src/cli.js +20 -3
- package/dist/src/commands/bootstrap.js +5 -1
- package/dist/src/commands/discard.js +2 -22
- package/dist/src/commands/doctor/post-rebase-audit.js +27 -4
- package/dist/src/commands/doctor-check-core.d.ts +20 -0
- package/dist/src/commands/doctor-check-core.js +28 -0
- package/dist/src/commands/doctor-external-toolchains.js +2 -0
- package/dist/src/commands/doctor-furnace-manifest-sync.js +144 -46
- package/dist/src/commands/doctor-furnace.js +13 -11
- package/dist/src/commands/doctor-orphaned-harness.js +2 -1
- package/dist/src/commands/doctor.js +2 -2
- package/dist/src/commands/export-all.js +9 -1
- package/dist/src/commands/export-flow.js +13 -3
- package/dist/src/commands/export-placement-conflicts.d.ts +17 -0
- package/dist/src/commands/export-placement-conflicts.js +74 -0
- package/dist/src/commands/export-placement-gate.js +1 -1
- package/dist/src/commands/export-shared.d.ts +8 -1
- package/dist/src/commands/export-shared.js +16 -7
- package/dist/src/commands/export.js +2 -2
- package/dist/src/commands/furnace/apply.js +4 -3
- package/dist/src/commands/furnace/chrome-doc-remove.js +13 -2
- package/dist/src/commands/furnace/chrome-doc.js +13 -2
- package/dist/src/commands/furnace/create-browser-test.d.ts +5 -0
- package/dist/src/commands/furnace/create-browser-test.js +5 -0
- package/dist/src/commands/furnace/create.js +2 -2
- package/dist/src/commands/furnace/diff.js +2 -1
- package/dist/src/commands/furnace/init.js +4 -6
- package/dist/src/commands/furnace/override.js +2 -2
- package/dist/src/commands/furnace/remove-mutations.d.ts +40 -0
- package/dist/src/commands/furnace/remove-mutations.js +171 -0
- package/dist/src/commands/furnace/remove.js +21 -128
- package/dist/src/commands/furnace/rename-browser-test.js +54 -5
- package/dist/src/commands/furnace/scan.js +27 -36
- package/dist/src/commands/furnace/status.js +30 -28
- package/dist/src/commands/furnace/validate.js +25 -18
- package/dist/src/commands/import.js +6 -20
- package/dist/src/commands/lint-per-patch.js +12 -59
- package/dist/src/commands/lint-per-run-checkjs.d.ts +64 -0
- package/dist/src/commands/lint-per-run-checkjs.js +102 -0
- package/dist/src/commands/lint.js +4 -4
- package/dist/src/commands/manifest.js +1 -1
- package/dist/src/commands/package.js +9 -16
- package/dist/src/commands/patch/compact.js +5 -5
- package/dist/src/commands/patch/delete.js +5 -5
- package/dist/src/commands/patch/lint-ignore.js +3 -2
- package/dist/src/commands/patch/move-files-create.js +22 -4
- package/dist/src/commands/patch/move-files-into.js +14 -10
- package/dist/src/commands/patch/move-files.js +6 -5
- package/dist/src/commands/patch/rename.d.ts +2 -2
- package/dist/src/commands/patch/rename.js +151 -50
- package/dist/src/commands/patch/reorder.d.ts +9 -0
- package/dist/src/commands/patch/reorder.js +14 -10
- package/dist/src/commands/patch/split-plan.d.ts +13 -6
- package/dist/src/commands/patch/split-plan.js +22 -12
- package/dist/src/commands/patch/split.d.ts +1 -1
- package/dist/src/commands/patch/split.js +23 -9
- package/dist/src/commands/patch/staged-dependency-validate.d.ts +15 -0
- package/dist/src/commands/patch/staged-dependency-validate.js +49 -0
- package/dist/src/commands/patch/staged-dependency.js +10 -0
- package/dist/src/commands/re-export-adjacent.js +5 -6
- package/dist/src/commands/re-export-bulk-scan.d.ts +13 -0
- package/dist/src/commands/re-export-bulk-scan.js +142 -2
- package/dist/src/commands/re-export-drift.d.ts +74 -0
- package/dist/src/commands/re-export-drift.js +234 -0
- package/dist/src/commands/re-export-files.js +5 -3
- package/dist/src/commands/re-export-lint.d.ts +97 -0
- package/dist/src/commands/re-export-lint.js +210 -0
- package/dist/src/commands/re-export-options.js +8 -0
- package/dist/src/commands/re-export-register.d.ts +18 -0
- package/dist/src/commands/re-export-register.js +61 -0
- package/dist/src/commands/re-export-scan.js +13 -7
- package/dist/src/commands/re-export-single.d.ts +20 -0
- package/dist/src/commands/re-export-single.js +261 -0
- package/dist/src/commands/re-export.d.ts +0 -4
- package/dist/src/commands/re-export.js +113 -268
- package/dist/src/commands/rebase/abort.js +13 -4
- package/dist/src/commands/rebase/continue.js +11 -4
- package/dist/src/commands/rebase/index.js +19 -29
- package/dist/src/commands/rebase/patch-loop.js +12 -0
- package/dist/src/commands/reset.js +4 -23
- package/dist/src/commands/resolve.js +2 -9
- package/dist/src/commands/run.js +11 -21
- package/dist/src/commands/source.js +3 -9
- package/dist/src/commands/status-check.d.ts +13 -0
- package/dist/src/commands/status-check.js +31 -13
- package/dist/src/commands/status-json.d.ts +31 -0
- package/dist/src/commands/status-json.js +76 -0
- package/dist/src/commands/status-output.d.ts +1 -0
- package/dist/src/commands/status-output.js +10 -1
- package/dist/src/commands/status-ownership.d.ts +52 -0
- package/dist/src/commands/status-ownership.js +57 -0
- package/dist/src/commands/status.js +184 -175
- package/dist/src/commands/test-build-phase.d.ts +19 -0
- package/dist/src/commands/test-build-phase.js +168 -0
- package/dist/src/commands/test-diagnose.js +30 -2
- package/dist/src/commands/test-modes.d.ts +9 -1
- package/dist/src/commands/test-modes.js +46 -13
- package/dist/src/commands/test-register.js +59 -27
- package/dist/src/commands/test-run.d.ts +29 -3
- package/dist/src/commands/test-run.js +41 -4
- package/dist/src/commands/test-verdict.d.ts +32 -0
- package/dist/src/commands/test-verdict.js +48 -0
- package/dist/src/commands/test.d.ts +9 -0
- package/dist/src/commands/test.js +100 -123
- package/dist/src/commands/token-coverage.js +4 -9
- package/dist/src/commands/tree.d.ts +3 -0
- package/dist/src/commands/tree.js +171 -25
- package/dist/src/commands/verify.js +2 -2
- package/dist/src/commands/watch.js +22 -21
- package/dist/src/commands/wire.js +1 -1
- package/dist/src/core/ast-utils.d.ts +11 -0
- package/dist/src/core/ast-utils.js +11 -0
- package/dist/src/core/build-audit-platform.js +6 -1
- package/dist/src/core/build-audit-registration.js +2 -0
- package/dist/src/core/build-audit-resolve.d.ts +13 -0
- package/dist/src/core/build-audit-resolve.js +34 -13
- package/dist/src/core/build-audit.js +4 -20
- package/dist/src/core/build-baseline-types.d.ts +29 -8
- package/dist/src/core/build-baseline-types.js +2 -1
- package/dist/src/core/build-baseline.d.ts +10 -2
- package/dist/src/core/build-baseline.js +39 -8
- package/dist/src/core/build-prepare.d.ts +4 -0
- package/dist/src/core/build-prepare.js +7 -1
- package/dist/src/core/config-paths.d.ts +1 -1
- package/dist/src/core/config-paths.js +2 -0
- package/dist/src/core/config-validate-test-toolchains.js +3 -2
- package/dist/src/core/config-validate.js +34 -36
- package/dist/src/core/config.js +16 -3
- package/dist/src/core/coverage-extend.d.ts +89 -0
- package/dist/src/core/coverage-extend.js +188 -0
- package/dist/src/core/destructive.js +3 -1
- package/dist/src/core/discard-baseline.js +18 -4
- package/dist/src/core/engine-precondition.d.ts +35 -0
- package/dist/src/core/engine-precondition.js +50 -0
- package/dist/src/core/engine-session-lock.d.ts +38 -1
- package/dist/src/core/engine-session-lock.js +78 -4
- package/dist/src/core/file-lock.d.ts +7 -0
- package/dist/src/core/file-lock.js +20 -30
- package/dist/src/core/firefox.js +5 -2
- package/dist/src/core/furnace-apply-helpers.d.ts +12 -1
- package/dist/src/core/furnace-apply-helpers.js +12 -3
- package/dist/src/core/furnace-apply-output.js +9 -0
- package/dist/src/core/furnace-apply-overwrite-warn.d.ts +63 -0
- package/dist/src/core/furnace-apply-overwrite-warn.js +123 -0
- package/dist/src/core/furnace-apply-undeploy-actions.d.ts +11 -0
- package/dist/src/core/furnace-apply-undeploy-actions.js +49 -0
- package/dist/src/core/furnace-apply.d.ts +16 -4
- package/dist/src/core/furnace-apply.js +39 -43
- package/dist/src/core/furnace-config.js +2 -0
- package/dist/src/core/furnace-css-fragments.js +2 -0
- package/dist/src/core/furnace-jsconfig.js +14 -7
- package/dist/src/core/furnace-registration-ast.js +8 -8
- package/dist/src/core/furnace-registration-remove.js +6 -3
- package/dist/src/core/furnace-registration.js +9 -12
- package/dist/src/core/furnace-rollback.js +1 -1
- package/dist/src/core/furnace-scanner.js +2 -1
- package/dist/src/core/furnace-stale-export.js +2 -1
- package/dist/src/core/furnace-state-persist.d.ts +4 -6
- package/dist/src/core/furnace-validate-compatibility.js +4 -7
- package/dist/src/core/furnace-validate-helpers.js +33 -1
- package/dist/src/core/furnace-validate-registration.js +16 -0
- package/dist/src/core/furnace-validate.js +4 -2
- package/dist/src/core/git-diff.js +37 -2
- package/dist/src/core/lint-cache.d.ts +1 -1
- package/dist/src/core/lint-cache.js +33 -10
- package/dist/src/core/mach-build-artifacts.d.ts +63 -1
- package/dist/src/core/mach-build-artifacts.js +108 -2
- package/dist/src/core/mach-resource-shim.js +1 -0
- package/dist/src/core/marionette-port.d.ts +16 -0
- package/dist/src/core/marionette-port.js +93 -8
- package/dist/src/core/marionette-preflight.js +6 -5
- package/dist/src/core/patch-apply.d.ts +20 -13
- package/dist/src/core/patch-apply.js +32 -33
- package/dist/src/core/patch-export.js +3 -5
- package/dist/src/core/patch-lint-checkjs.d.ts +25 -3
- package/dist/src/core/patch-lint-checkjs.js +78 -7
- package/dist/src/core/patch-lint-chrome-jsdoc.js +9 -0
- package/dist/src/core/patch-lint-creators.d.ts +30 -0
- package/dist/src/core/patch-lint-creators.js +45 -0
- package/dist/src/core/patch-lint-cross.d.ts +1 -17
- package/dist/src/core/patch-lint-cross.js +16 -32
- package/dist/src/core/patch-lint-jsdoc.d.ts +3 -1
- package/dist/src/core/patch-lint-jsdoc.js +15 -5
- package/dist/src/core/patch-lint-module-registration.d.ts +15 -0
- package/dist/src/core/patch-lint-module-registration.js +88 -0
- package/dist/src/core/patch-lint-mozbuild.js +99 -11
- package/dist/src/core/patch-lint-reexports.d.ts +1 -0
- package/dist/src/core/patch-lint-reexports.js +1 -0
- package/dist/src/core/patch-lint.d.ts +8 -0
- package/dist/src/core/patch-lint.js +84 -6
- package/dist/src/core/patch-lock.d.ts +12 -1
- package/dist/src/core/patch-lock.js +40 -4
- package/dist/src/core/patch-manifest-consistency.js +15 -3
- package/dist/src/core/patch-parse.d.ts +8 -0
- package/dist/src/core/patch-parse.js +10 -0
- package/dist/src/core/patch-policy.d.ts +7 -0
- package/dist/src/core/patch-policy.js +4 -1
- package/dist/src/core/patch-transform.d.ts +10 -0
- package/dist/src/core/patch-transform.js +13 -2
- package/dist/src/core/rebase-session.d.ts +44 -2
- package/dist/src/core/rebase-session.js +114 -17
- package/dist/src/core/register-browser-content.js +2 -1
- package/dist/src/core/register-shared-css.js +2 -1
- package/dist/src/core/smoke-patterns.d.ts +0 -8
- package/dist/src/core/smoke-patterns.js +3 -2
- package/dist/src/core/status-binary.d.ts +55 -0
- package/dist/src/core/status-binary.js +124 -0
- package/dist/src/core/status-classify.d.ts +6 -1
- package/dist/src/core/status-classify.js +101 -73
- package/dist/src/core/test-harness-crash.d.ts +32 -1
- package/dist/src/core/test-harness-crash.js +59 -4
- package/dist/src/core/test-path-scope.js +2 -0
- package/dist/src/core/test-stale-check.js +1 -20
- package/dist/src/core/toolchain-preflight.js +24 -26
- package/dist/src/core/tree-guard.d.ts +1 -1
- package/dist/src/core/tree-guard.js +67 -12
- package/dist/src/core/tree-store.d.ts +86 -10
- package/dist/src/core/tree-store.js +310 -72
- package/dist/src/core/typecheck.js +2 -1
- package/dist/src/core/wire-destroy.js +14 -3
- package/dist/src/core/wire-init.js +37 -40
- package/dist/src/core/wire-subscript.js +11 -3
- package/dist/src/core/wire-utils.js +4 -4
- package/dist/src/core/xpcshell-appdir.js +3 -0
- package/dist/src/errors/base.d.ts +13 -0
- package/dist/src/errors/base.js +13 -0
- package/dist/src/errors/rebase.d.ts +13 -0
- package/dist/src/errors/rebase.js +17 -0
- package/dist/src/types/commands/options.d.ts +86 -1
- package/dist/src/types/commands/patches.d.ts +8 -0
- package/dist/src/types/furnace.d.ts +7 -0
- package/dist/src/utils/build-info.d.ts +31 -0
- package/dist/src/utils/build-info.js +98 -0
- package/dist/src/utils/concurrency.d.ts +8 -0
- package/dist/src/utils/concurrency.js +21 -0
- package/dist/src/utils/errors.d.ts +39 -0
- package/dist/src/utils/errors.js +55 -0
- package/dist/src/utils/fs.js +5 -12
- package/dist/src/utils/logger.d.ts +14 -2
- package/dist/src/utils/logger.js +43 -13
- package/dist/src/utils/options.d.ts +21 -4
- package/dist/src/utils/options.js +26 -6
- package/dist/src/utils/package-root.js +7 -4
- package/dist/src/utils/process-group.js +13 -2
- package/dist/src/utils/stdio-drain.d.ts +28 -0
- package/dist/src/utils/stdio-drain.js +54 -0
- package/dist/src/utils/validation.d.ts +23 -2
- package/dist/src/utils/validation.js +29 -3
- package/package.json +9 -9
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,84 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.41.0
|
|
4
|
+
|
|
5
|
+
### Release and build integrity
|
|
6
|
+
|
|
7
|
+
- Release publishing commits version bumps before building and enforces a clean tree with `FIREFORGE_PACK_STRICT=1`.
|
|
8
|
+
- Builds stamp `dist/build-info.json`; installed `--version` output includes the commit and dirty-content hash.
|
|
9
|
+
- Build baselines retain deletion tombstones, so removed packageable files and `components.conf` entries cannot look fresh.
|
|
10
|
+
- `test --build` uses a full build for new `jar.mn` registrations; mixed-harness `--build-only` packages all requested paths once.
|
|
11
|
+
- `--extend-coverage` unions scoped packaging coverage only while HEAD, packageable inputs, and `mozconfig` remain unchanged.
|
|
12
|
+
- Shared build and engine preflights now reject incomplete builds and unborn engine repositories consistently.
|
|
13
|
+
|
|
14
|
+
### Tests and verification trees
|
|
15
|
+
|
|
16
|
+
- Tests emit exactly one final `FIREFORGE-VERDICT:` line with the correct pass/failure reason, including sharded, lock-timeout, crash, and inconclusive runs.
|
|
17
|
+
- `tree exec` preserves the child verdict as the final stdout line; pre-spawn refusals emit no verdict.
|
|
18
|
+
- Engine-integrity checks run before successful verdicts.
|
|
19
|
+
- Browser preflight detects stale apps from the same objdir even after they release Marionette; `--kill-stale-marionette` can terminate them.
|
|
20
|
+
- Mach commands use process groups and reap surviving browser or harness descendants.
|
|
21
|
+
- `tree create --with-objdir` supports build-less tests after relocation and reconfiguration checks.
|
|
22
|
+
- Tree creation refuses symlinked objdirs, cleans failed clones, validates markers, and handles unreadable locks fail-closed.
|
|
23
|
+
- Tree freshness covers dirty engine content and patch bodies.
|
|
24
|
+
- Tree creation skips unwanted `obj-*` directories before cloning and uses bounded concurrency.
|
|
25
|
+
- `tree create --wait-lock` now matches its lock-refusal guidance.
|
|
26
|
+
- Verification trees permit `re-export --dry-run`; mutating operations remain blocked.
|
|
27
|
+
- Test assertion lint now recognizes real assertion calls, and critical modules have explicit coverage floors.
|
|
28
|
+
|
|
29
|
+
### Status and diagnostics
|
|
30
|
+
|
|
31
|
+
- Status classification reuses one patch context and classifies files through a bounded pool.
|
|
32
|
+
- `status --json --summary` returns counts and offenders without the full file payload.
|
|
33
|
+
- `status --json --include-ownership` adds ownership data without another scan.
|
|
34
|
+
- JSON output drains fully through slow pipes, and machine-mode refusals route to stderr.
|
|
35
|
+
- Multi-owner files remain conflicts even when Furnace-managed or generated branding paths.
|
|
36
|
+
- Lock contention and unreadable/corrupt state now produce concise, accurate diagnostics.
|
|
37
|
+
|
|
38
|
+
### Patch, export, and lint workflow
|
|
39
|
+
|
|
40
|
+
- `export-all --exclude-furnace` keeps its filtered scope after license-header repair.
|
|
41
|
+
- `re-export --expect <path>` permits intended drift and reports paths skipped by earlier refusals as not evaluated.
|
|
42
|
+
- Missing drift baselines fail closed, binary patches compare by content, and partial or fully refused re-exports exit non-zero.
|
|
43
|
+
- `re-export --dry-run` uses before/after fingerprints and cannot roll back another completed write.
|
|
44
|
+
- Re-export builds checkJs once per run and reuses the per-patch cache; `--patches` subsets narrow computation.
|
|
45
|
+
- Re-export probes and purity hashing use bounded concurrency; patch writes remain serial.
|
|
46
|
+
- Patch lint rejects new imported system modules without a projected `moz.build` registration.
|
|
47
|
+
- The checkJs cache hashes both configurable shims.
|
|
48
|
+
- Export placement refusals print projected errors and identify renumbering consequences.
|
|
49
|
+
- `patch rename` can recategorize and renumber atomically.
|
|
50
|
+
- `patch move-files` and `patch split` use whole-queue projection lint, normalize names, and provide split guidance.
|
|
51
|
+
- `patch move-files --create` names the required `--description` option.
|
|
52
|
+
- Queue-mutating patch commands support `--wait-lock` and avoid unsafe stale-lock advice.
|
|
53
|
+
- Staged-dependency declarations reject patch-name-shaped file arguments.
|
|
54
|
+
- `CommandError` passes through the top-level handler without duplicate or JSON-corrupting output.
|
|
55
|
+
|
|
56
|
+
### Furnace and validation
|
|
57
|
+
|
|
58
|
+
- Furnace computes checksums once per apply and warns before overwriting divergent patch-owned files.
|
|
59
|
+
- Furnace jsconfig synchronization includes `register: false` components.
|
|
60
|
+
- Manifest-sync failures are visible, and repairs run under the Furnace lock.
|
|
61
|
+
- `furnace validate --fix` changes only reported components and detects missing custom-element registrations.
|
|
62
|
+
- Localization validation recognizes non-Latin text while exempting emoji and punctuation.
|
|
63
|
+
- `furnace rename` uses single-pass, boundary-aware substitutions.
|
|
64
|
+
- Rollback failures retain repair breadcrumbs for `doctor --repair-furnace`.
|
|
65
|
+
- The reported no-change/apply-state disagreement was not reproducible and is regression-tested.
|
|
66
|
+
|
|
67
|
+
### Safety, performance, and maintenance
|
|
68
|
+
|
|
69
|
+
- Nano ID 3.3.18 resolves a high-severity audit finding.
|
|
70
|
+
- Rebase loading distinguishes absent and corrupt sessions; `rebase --abort` can recover invalid state.
|
|
71
|
+
- PID liveness treats `EPERM` as alive, preventing deletion through another user's lock.
|
|
72
|
+
- The moz.build parser handles comments, quoted brackets/apostrophes, and unterminated lists.
|
|
73
|
+
- Parser failures no longer silently certify validation success; internal invariant errors bypass fallback.
|
|
74
|
+
- Toolchain probes, baseline fingerprints, cache hashes, and manifest reads use bounded concurrency.
|
|
75
|
+
- Existing Git tuning remains enabled; fsmonitor stays disabled because of stale-daemon risk.
|
|
76
|
+
- A persistent checkJs daemon remains deferred until safe invalidation is cheaper; see `docs/lint-daemon-design.md`.
|
|
77
|
+
- Shared helpers replace duplicated preflights, error conversion, regex escaping, option parsing, object guards, and ESTree casts.
|
|
78
|
+
- Four local ESLint rules prevent unsafe error casts, duplicate regex escaping, and empty JSDoc.
|
|
79
|
+
- Oversized modules were split or simplified, silent degradations documented, and stale dead-code suppressions removed.
|
|
80
|
+
- The release gate pins weak critical modules so aggregate coverage cannot hide local regressions.
|
|
81
|
+
|
|
3
82
|
## 0.40.0
|
|
4
83
|
|
|
5
84
|
- **Dependencies: adopted all five open dependabot PRs (#48, #47, #45, #44, #42) after Go-based TypeScript 6 compatibility review** — the pinned `typescript ~6.0.0` (6.0.3 installed) constrained which upgrades were feasible; every bumped tool checked out: `typescript-eslint` declares peer `typescript >=4.8.4 <6.1.0`, `knip` carries no TypeScript dependency at all (oxc parser), and `dpdm` bundles its own nested TypeScript 5.9.3, so none touch the root compiler. Production: `acorn` 8.17.0→8.18.0 (lockfile-only) and `magic-string` 0.30.21→**1.1.0** — a major that only drops the CJS/UMD builds for pure ESM (FireForge is pure ESM; the five `MagicString` call sites use the unchanged core API). Dev: `@types/node` 26.1.2, `dpdm` 4.3.0, `eslint` 10.8.0, `eslint-plugin-jsdoc` 63.3.3, `knip` 6.29.0, `lint-staged` 17.3.0, `prettier` 3.9.6, `typescript-eslint` 8.66.0, `vite` 8.2.1, and `eslint-plugin-simple-import-sort` 13→**14** (breaking only for string-literal export names, which the codebase does not use); four of these resolved one patch/minor past PR #48's snapshot within the existing caret ranges (typescript-eslint 8.66.0 re-verified against the same `<6.1.0` peer window). CI: the SHA-pinned actions group (checkout 7.0.1 with `--unset`-escaping and branch-trim fixes, setup-node, codeql init/analyze). knip 6.29's stricter re-export tracking flagged seven type re-exports (`PatchCoverage`, `ClearablePatchMetadataField`, `PatchMetadataMutation{,Result}` in `patch-export.ts`; `ForwardImportEdge` in `patch-lint-reexports.ts`; `PatchManifestRowMutation{,Result}` in `patch-manifest.ts`) that no module, test, or public-API entry consumed via the barrel hop — a real 6.26 blind spot, so the dead re-exports were removed (the types stay exported from their defining modules). Verified by a full green `release:check`.
|
package/README.md
CHANGED
|
@@ -86,7 +86,8 @@ Queue maintenance lives under `fireforge patch`: `patch compact` closes ordinal
|
|
|
86
86
|
(range-aware when `patchPolicy.ranges` is configured), `patch reorder` moves a patch, and
|
|
87
87
|
`patch split <source> --files <paths...> --name <name>` carves files out of a patch into a
|
|
88
88
|
new one as a single transaction — including staged-dependency owner rewrites — with
|
|
89
|
-
`--dry-run` support.
|
|
89
|
+
`--dry-run` support. `patch rename` also supports `--category` and `--order`; every
|
|
90
|
+
queue-mutating patch command accepts `--wait-lock [seconds]`.
|
|
90
91
|
|
|
91
92
|
`fireforge test <directory>` runs exactly that directory: FireForge enumerates the
|
|
92
93
|
directory's test files and passes the explicit file list to mach in one invocation, so
|
|
@@ -98,14 +99,18 @@ instances do not exercise cross-argument state (`--no-shard` restores one combin
|
|
|
98
99
|
invocation). Recognized harness crashes retry up to `--harness-retries <n>` times (default
|
|
99
100
|
2), and `--perf-samples <path>` publishes a perf-sample artifact path to the harness
|
|
100
101
|
(exported as `<BINARYNAME>_PERF_SAMPLE_JSON`).
|
|
102
|
+
Every test run ends with one machine-readable `FIREFORGE-VERDICT: PASS|FAIL ...` line;
|
|
103
|
+
automation should use it instead of the raw process code and treat a missing verdict as
|
|
104
|
+
failure.
|
|
101
105
|
Pathless test runs must choose a mode: `--auto` forwards mach's own auto-selection,
|
|
102
106
|
`--doctor` runs the Marionette preflight only, and `--canary [path]` runs one short
|
|
103
107
|
browser-chrome canary (`test.canaryPath` / `test.canaryTimeoutSeconds` in `fireforge.json`
|
|
104
108
|
provide defaults). When packageable engine files changed since the last successful
|
|
105
109
|
FireForge build, `test` fails before launching stale artifacts; use `--build` to refresh
|
|
106
|
-
or `--allow-stale-build` only for intentional out-of-band rebuilds.
|
|
107
|
-
|
|
108
|
-
|
|
110
|
+
or `--allow-stale-build` only for intentional out-of-band rebuilds. `--build-only`
|
|
111
|
+
packages mixed-harness paths once; `--extend-coverage` safely retains prior scoped
|
|
112
|
+
coverage. `--kill-stale-marionette` terminates recognized stale browsers. In dev builds,
|
|
113
|
+
files under `obj-*/dist/bin`
|
|
109
114
|
may be symlinks back into the source tree (notably prefs), so edit source prefs directly
|
|
110
115
|
and keep a backup before bisection experiments.
|
|
111
116
|
|
|
@@ -141,7 +146,9 @@ override the loose baseline so calls to nonexistent harness members fail at expo
|
|
|
141
146
|
For CI enforcement, `fireforge status --check` exits non-zero when any unmanaged,
|
|
142
147
|
drifted, or conflicted file exists (`--fail-on <class,...>` tunes the policy set, and
|
|
143
148
|
`--json` composes — the JSON stays parseable and its `files[]` entries name the owning
|
|
144
|
-
`patch`). `
|
|
149
|
+
`patch`). `--include-ownership` adds ownership rows to JSON status output. Scan-less
|
|
150
|
+
`re-export` previews drift; `--refuse-foreign-drift --expect <path>` restricts it to
|
|
151
|
+
intended files. `lint --per-patch --report <path>` writes a machine-readable JSON report with
|
|
145
152
|
each patch's line count, tier, active size thresholds, issues, and lintIgnore-suppressed
|
|
146
153
|
issues; the size metrics (`countNonBinaryDiffLines`, `resolvePatchSizeTier`,
|
|
147
154
|
`getPatchSizeThresholds`) are also exported on the programmatic API.
|
|
@@ -149,11 +156,10 @@ issues; the size metrics (`countNonBinaryDiffLines`, `resolvePatchSizeTier`,
|
|
|
149
156
|
For concurrent read-mostly verification beside a busy primary checkout,
|
|
150
157
|
`fireforge tree create <name>` snapshots the project — applied engine tree included,
|
|
151
158
|
`obj-*` excluded — into `.fireforge/trees/<name>` using filesystem copy-on-write (APFS
|
|
152
|
-
clonefile / btrfs-XFS reflink; ~3 s for a 156 MB applied browser tree).
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
(refusing while a live process holds a tree lock), and `tree exec <name> -- <cmd>` runs a
|
|
159
|
+
clonefile / btrfs-XFS reflink; ~3 s for a 156 MB applied browser tree). Read-only commands
|
|
160
|
+
and export/re-export dry runs work inside a tree; mutations are refused. `--with-objdir`
|
|
161
|
+
adds a safely relocated build for build-less tests. `tree list` reports staleness,
|
|
162
|
+
`tree remove` deletes (refusing active locks), and `tree exec <name> -- <cmd>` runs a
|
|
157
163
|
command inside a tree. Filesystems without CoW support refuse unless `--force-copy`
|
|
158
164
|
explicitly accepts a full physical copy.
|
|
159
165
|
|
package/dist/bin/fireforge.js
CHANGED
|
@@ -17,6 +17,7 @@ import { forceReleaseFurnaceLocksForActiveOperations, isSignalRollbackInFlight,
|
|
|
17
17
|
import { waitForActiveCriticalSections } from '../src/core/signal-critical.js';
|
|
18
18
|
import { CommandError } from '../src/errors/base.js';
|
|
19
19
|
import { waitForActiveChildShutdown } from '../src/utils/process.js';
|
|
20
|
+
import { waitForStdioDrain } from '../src/utils/stdio-drain.js';
|
|
20
21
|
/**
|
|
21
22
|
* Upper bound (ms) the signal handler will wait for any in-flight critical
|
|
22
23
|
* section (e.g. rebase apply + session persist) to finish before calling
|
|
@@ -34,13 +35,32 @@ const SIGNAL_CRITICAL_SECTION_TIMEOUT_MS = 5_000;
|
|
|
34
35
|
* immediately, so an impatient operator is never stuck waiting.
|
|
35
36
|
*/
|
|
36
37
|
const CHILD_SHUTDOWN_TIMEOUT_MS = 12_000;
|
|
38
|
+
/**
|
|
39
|
+
* Upper bound (ms) a delayed exit waits for stdout/stderr to drain. When
|
|
40
|
+
* stdout is a pipe it is ASYNC, and `process.exit()` discards anything
|
|
41
|
+
* queued past the 64 KiB kernel buffer — which truncated the
|
|
42
|
+
* `status --json --fail-on` refusal payload for every piped consumer.
|
|
43
|
+
* Bounded so a stalled reader can never wedge a failing process; an
|
|
44
|
+
* EPIPE'd/closed pipe releases the wait immediately.
|
|
45
|
+
*/
|
|
46
|
+
const STDIO_FLUSH_TIMEOUT_MS = 5_000;
|
|
47
|
+
/**
|
|
48
|
+
* Drains stdio, then exits. Every delayed exit routes through here — only
|
|
49
|
+
* the second-Ctrl+C force-exit (an explicit "out NOW") and this helper's
|
|
50
|
+
* own call terminate directly.
|
|
51
|
+
*/
|
|
52
|
+
function exitAfterStdioFlush(code) {
|
|
53
|
+
void waitForStdioDrain(STDIO_FLUSH_TIMEOUT_MS).finally(() => {
|
|
54
|
+
process.exit(code);
|
|
55
|
+
});
|
|
56
|
+
}
|
|
37
57
|
installBrokenPipeHandler();
|
|
38
58
|
process.on('unhandledRejection', (reason) => {
|
|
39
59
|
console.error('Fatal error (unhandled rejection):', reason instanceof Error ? reason.message : reason);
|
|
40
60
|
if (reason instanceof Error && reason.stack) {
|
|
41
61
|
console.error(reason.stack);
|
|
42
62
|
}
|
|
43
|
-
|
|
63
|
+
exitAfterStdioFlush(1);
|
|
44
64
|
});
|
|
45
65
|
// SIGINT / SIGTERM handlers run any in-flight furnace rollback before
|
|
46
66
|
// terminating. The library cannot call process.exit itself (the
|
|
@@ -81,7 +101,7 @@ function installFurnaceSignalHandler(signal, exitCode) {
|
|
|
81
101
|
// best-effort (errors are logged, not thrown).
|
|
82
102
|
.then(() => forceReleaseFurnaceLocksForActiveOperations())
|
|
83
103
|
.finally(() => {
|
|
84
|
-
|
|
104
|
+
exitAfterStdioFlush(exitCode);
|
|
85
105
|
});
|
|
86
106
|
});
|
|
87
107
|
}
|
|
@@ -89,9 +109,10 @@ installFurnaceSignalHandler('SIGINT', 130);
|
|
|
89
109
|
installFurnaceSignalHandler('SIGTERM', 143);
|
|
90
110
|
main().catch((error) => {
|
|
91
111
|
if (error instanceof CommandError) {
|
|
92
|
-
|
|
112
|
+
exitAfterStdioFlush(error.exitCode);
|
|
113
|
+
return;
|
|
93
114
|
}
|
|
94
115
|
// Truly unexpected — CommandError should have been thrown by withErrorHandling
|
|
95
116
|
console.error('Fatal error:', error);
|
|
96
|
-
|
|
117
|
+
exitAfterStdioFlush(1);
|
|
97
118
|
});
|
package/dist/src/cli.js
CHANGED
|
@@ -7,9 +7,9 @@ import { runTreeGuardHook } from './core/tree-guard.js';
|
|
|
7
7
|
import { CancellationError, CommandError, FireForgeError } from './errors/base.js';
|
|
8
8
|
import { ExitCode } from './errors/codes.js';
|
|
9
9
|
import { ConfigNotFoundError } from './errors/config.js';
|
|
10
|
+
import { getCliVersion } from './utils/build-info.js';
|
|
10
11
|
import { toError } from './utils/errors.js';
|
|
11
|
-
import { cancel, error as logError, setVerbose } from './utils/logger.js';
|
|
12
|
-
import { getPackageVersion } from './utils/package-root.js';
|
|
12
|
+
import { cancel, error as logError, setMachineOutputMode, setStdoutSealed, setVerbose, } from './utils/logger.js';
|
|
13
13
|
const brokenPipeInstalledKey = Symbol.for('fireforge.cli.brokenPipeHandlerInstalled');
|
|
14
14
|
const brokenPipeListenerKey = Symbol.for('fireforge.cli.brokenPipeHandlerListener');
|
|
15
15
|
function getProcessState() {
|
|
@@ -100,6 +100,12 @@ export function withErrorHandling(handler) {
|
|
|
100
100
|
await handler(...args);
|
|
101
101
|
}
|
|
102
102
|
catch (error) {
|
|
103
|
+
// Sentinel errors have already been rendered by the command. Passing
|
|
104
|
+
// them through prevents machine-readable refusal paths from acquiring
|
|
105
|
+
// an "Unexpected error" banner and stack trace on stderr.
|
|
106
|
+
if (error instanceof CommandError) {
|
|
107
|
+
throw error;
|
|
108
|
+
}
|
|
103
109
|
if (error instanceof CancellationError) {
|
|
104
110
|
cancel('Operation cancelled');
|
|
105
111
|
// 130 (128+SIGINT) is the conventional "user interrupted" code —
|
|
@@ -118,6 +124,16 @@ export function withErrorHandling(handler) {
|
|
|
118
124
|
}
|
|
119
125
|
throw new CommandError(ExitCode.GENERAL_ERROR);
|
|
120
126
|
}
|
|
127
|
+
finally {
|
|
128
|
+
// Central machine-mode and stdout-seal reset. Commands leave both
|
|
129
|
+
// ENGAGED while an error propagates (a mid-throw restore would route
|
|
130
|
+
// the styled error to stdout, corrupting the `--json`/`--raw` payload
|
|
131
|
+
// stream or displacing the FIREFORGE-VERDICT line); the reset happens
|
|
132
|
+
// here, after logError has picked its stream, so no state leaks into
|
|
133
|
+
// a subsequent in-process invocation.
|
|
134
|
+
setMachineOutputMode(false);
|
|
135
|
+
setStdoutSealed(false);
|
|
136
|
+
}
|
|
121
137
|
};
|
|
122
138
|
}
|
|
123
139
|
/** Human-readable labels for command groups, in display order. */
|
|
@@ -246,6 +262,7 @@ export function createProgram() {
|
|
|
246
262
|
.name('fireforge')
|
|
247
263
|
.description('A build tool for customizing Firefox')
|
|
248
264
|
.option('-v, --verbose', 'Enable debug output')
|
|
265
|
+
.option('--ignore-corrupt-tree-marker', 'Run even when .fireforge/tree.json exists but cannot be parsed. Without this the tree guard refuses, because an unreadable marker leaves it unknown whether this is a snapshot or the primary tree.')
|
|
249
266
|
.hook('preAction', (thisCommand) => {
|
|
250
267
|
const opts = thisCommand.opts();
|
|
251
268
|
if (opts['verbose']) {
|
|
@@ -282,7 +299,7 @@ export async function main() {
|
|
|
282
299
|
const userArgs = process.argv.slice(2);
|
|
283
300
|
const hasSubcommand = userArgs.some((arg) => !arg.startsWith('-'));
|
|
284
301
|
if (!hasSubcommand && userArgs.some((arg) => arg === '--version' || arg === '-V')) {
|
|
285
|
-
process.stdout.write(`${
|
|
302
|
+
process.stdout.write(`${getCliVersion()}\n`);
|
|
286
303
|
return;
|
|
287
304
|
}
|
|
288
305
|
const program = createProgram();
|
|
@@ -10,6 +10,7 @@ import { pathExists } from '../utils/fs.js';
|
|
|
10
10
|
import { error, info, intro, outro, warn } from '../utils/logger.js';
|
|
11
11
|
import { detectBootstrapIssues, runPostBootstrapChecks } from './bootstrap-checks.js';
|
|
12
12
|
import { reportDoctorResults } from './doctor.js';
|
|
13
|
+
import { resolveDoctorSeverity } from './doctor-check-core.js';
|
|
13
14
|
/**
|
|
14
15
|
* Builds a human-readable failure message for hard failures (non-zero exit).
|
|
15
16
|
* Used only when mach bootstrap itself reports failure.
|
|
@@ -67,7 +68,10 @@ export async function bootstrapCommand(projectRoot) {
|
|
|
67
68
|
const issues = detectBootstrapIssues(output);
|
|
68
69
|
if (issues.length > 0) {
|
|
69
70
|
const checks = await runPostBootstrapChecks(issues);
|
|
70
|
-
|
|
71
|
+
// Shares one resolver with reportDoctorResults so the two consumers of
|
|
72
|
+
// this same array cannot drift apart again — before 0.41.0 they
|
|
73
|
+
// disagreed on `passed: false, warning: true` with no severity.
|
|
74
|
+
const hasErrors = checks.some((c) => resolveDoctorSeverity(c) === 'error');
|
|
71
75
|
info('');
|
|
72
76
|
if (hasErrors) {
|
|
73
77
|
warn('Bootstrap completed with issues:');
|
|
@@ -3,13 +3,12 @@ import { confirm } from '@clack/prompts';
|
|
|
3
3
|
import { Command } from 'commander';
|
|
4
4
|
import { getProjectPaths } from '../core/config.js';
|
|
5
5
|
import { applyDiscardBaseline, describeConflictWarning, describeDiscardBaseline, describeDiscardOutcome, planDiscardBaselines, planUpstreamDiscards, summarizeDiscardBaselines, } from '../core/discard-baseline.js';
|
|
6
|
+
import { assertEngineGitReady } from '../core/engine-precondition.js';
|
|
6
7
|
import { collectFurnaceManagedPrefixes } from '../core/furnace-config.js';
|
|
7
|
-
import { getHead, isGitRepository, isMissingHeadError } from '../core/git.js';
|
|
8
8
|
import { expandUntrackedDirectoryEntries, getWorkingTreeStatus } from '../core/git-status.js';
|
|
9
9
|
import { GeneralError, InvalidArgumentError } from '../errors/base.js';
|
|
10
10
|
import { GitError } from '../errors/git.js';
|
|
11
11
|
import { toError } from '../utils/errors.js';
|
|
12
|
-
import { pathExists } from '../utils/fs.js';
|
|
13
12
|
import { info, intro, isCancel, outro, spinner, warn } from '../utils/logger.js';
|
|
14
13
|
import { pickDefined } from '../utils/options.js';
|
|
15
14
|
/**
|
|
@@ -126,26 +125,7 @@ async function discardDirectoryEntries(projectRoot, engineDir, patchesDir, dirPa
|
|
|
126
125
|
export async function discardCommand(projectRoot, file, options = {}) {
|
|
127
126
|
intro('FireForge Discard');
|
|
128
127
|
const paths = getProjectPaths(projectRoot);
|
|
129
|
-
|
|
130
|
-
if (!(await pathExists(paths.engine))) {
|
|
131
|
-
throw new GeneralError('Firefox source not found. Run "fireforge download" first.');
|
|
132
|
-
}
|
|
133
|
-
// Check if it's a git repository
|
|
134
|
-
if (!(await isGitRepository(paths.engine))) {
|
|
135
|
-
throw new GeneralError('Engine directory is not a git repository. Run "fireforge download" to initialize.');
|
|
136
|
-
}
|
|
137
|
-
// Unborn-HEAD guard (mirrors status): an interrupted download leaves a
|
|
138
|
-
// repo with no baseline commit, where the entire ~300k-file tree reads
|
|
139
|
-
// as untracked — a discard against that state is never what the operator
|
|
140
|
-
// wants, and the guidance names the actual fix.
|
|
141
|
-
try {
|
|
142
|
-
await getHead(paths.engine);
|
|
143
|
-
}
|
|
144
|
-
catch (headError) {
|
|
145
|
-
if (!isMissingHeadError(headError))
|
|
146
|
-
throw headError;
|
|
147
|
-
throw new GeneralError('Engine repository has no baseline commit yet — a previous "fireforge download" was interrupted before git created the initial Firefox source commit. Re-run "fireforge download --force" to recreate the baseline repository cleanly.');
|
|
148
|
-
}
|
|
128
|
+
await assertEngineGitReady(paths.engine);
|
|
149
129
|
// Check if the file has changes
|
|
150
130
|
const statusEntries = await expandUntrackedDirectoryEntries(paths.engine, await getWorkingTreeStatus(paths.engine));
|
|
151
131
|
const statusEntry = statusEntries.find((entry) => entry.file === file || entry.originalPath === file);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// SPDX-License-Identifier: EUPL-1.2
|
|
2
2
|
import { readdir } from 'node:fs/promises';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
|
+
import { toError } from '../../utils/errors.js';
|
|
4
5
|
import { pathExists, readText } from '../../utils/fs.js';
|
|
5
6
|
import { ok, warning } from '../doctor-check-core.js';
|
|
6
7
|
async function readEngineText(engineDir, relativePath) {
|
|
@@ -9,17 +10,28 @@ async function readEngineText(engineDir, relativePath) {
|
|
|
9
10
|
return null;
|
|
10
11
|
return readText(fullPath);
|
|
11
12
|
}
|
|
13
|
+
/**
|
|
14
|
+
* Collects every `browser.toml` under the engine's browser-chrome test tree.
|
|
15
|
+
*
|
|
16
|
+
* Reports unreadable directories separately from "none found": the consumer
|
|
17
|
+
* turns an empty result into the issue "no browser.toml files found", which is
|
|
18
|
+
* the opposite diagnosis from "the tree could not be read". A swallowed
|
|
19
|
+
* mid-walk EACCES also silently *shrank* the result set, so one unreadable
|
|
20
|
+
* subdirectory produced a false clean for that subtree.
|
|
21
|
+
*/
|
|
12
22
|
async function collectBrowserTomlFiles(root) {
|
|
13
23
|
const testRoot = join(root, 'browser/base/content/test');
|
|
14
24
|
if (!(await pathExists(testRoot)))
|
|
15
|
-
return [];
|
|
25
|
+
return { files: [], unreadable: [] };
|
|
16
26
|
const result = [];
|
|
27
|
+
const unreadable = [];
|
|
17
28
|
async function walk(absDir, relDir) {
|
|
18
29
|
let entries;
|
|
19
30
|
try {
|
|
20
31
|
entries = await readdir(absDir, { withFileTypes: true });
|
|
21
32
|
}
|
|
22
|
-
catch {
|
|
33
|
+
catch (error) {
|
|
34
|
+
unreadable.push(`${relDir || '.'} (${toError(error).message})`);
|
|
23
35
|
return;
|
|
24
36
|
}
|
|
25
37
|
for (const entry of entries) {
|
|
@@ -34,11 +46,17 @@ async function collectBrowserTomlFiles(root) {
|
|
|
34
46
|
}
|
|
35
47
|
}
|
|
36
48
|
await walk(testRoot, '');
|
|
37
|
-
return result.sort();
|
|
49
|
+
return { files: result.sort(), unreadable };
|
|
38
50
|
}
|
|
39
51
|
async function runPostRebaseAudit(ctx) {
|
|
40
52
|
const issues = [];
|
|
41
53
|
const engineDir = ctx.paths.engine;
|
|
54
|
+
// The four probes below are upstream-Firefox shape markers: each names a
|
|
55
|
+
// file and a token that a healthy post-rebase tree must still contain. A
|
|
56
|
+
// rebase that dropped a fork patch, or an upstream reorganisation, shows up
|
|
57
|
+
// as one of these going missing. The tokens are deliberately coarse — this
|
|
58
|
+
// is a smoke check that runs warn-only behind an opt-in flag, not a
|
|
59
|
+
// structural validator.
|
|
42
60
|
const mozConfigure = await readEngineText(engineDir, 'browser/moz.configure');
|
|
43
61
|
if (mozConfigure === null) {
|
|
44
62
|
issues.push('browser/moz.configure is missing');
|
|
@@ -69,7 +87,12 @@ async function runPostRebaseAudit(ctx) {
|
|
|
69
87
|
issues.push('toolkit/content/jar.mn has no widget/element exposure entries');
|
|
70
88
|
}
|
|
71
89
|
const browserTomls = await collectBrowserTomlFiles(engineDir);
|
|
72
|
-
if (browserTomls.length
|
|
90
|
+
if (browserTomls.unreadable.length > 0) {
|
|
91
|
+
issues.push(`could not read ${String(browserTomls.unreadable.length)} ` +
|
|
92
|
+
`${browserTomls.unreadable.length === 1 ? 'directory' : 'directories'} under ` +
|
|
93
|
+
`browser/base/content/test: ${browserTomls.unreadable.join(', ')}`);
|
|
94
|
+
}
|
|
95
|
+
else if (browserTomls.files.length === 0) {
|
|
73
96
|
issues.push('no browser.toml files found under browser/base/content/test');
|
|
74
97
|
}
|
|
75
98
|
if (issues.length === 0) {
|
|
@@ -87,6 +87,26 @@ export interface DoctorCheckDefinition {
|
|
|
87
87
|
*/
|
|
88
88
|
fix?: string;
|
|
89
89
|
}
|
|
90
|
+
/**
|
|
91
|
+
* Resolves a {@link DoctorCheck} to its effective severity, implementing the
|
|
92
|
+
* field-precedence contract documented on the interface
|
|
93
|
+
* (`src/types/commands/project.ts`):
|
|
94
|
+
*
|
|
95
|
+
* 1. `severity`, when present, is authoritative — regardless of `passed`.
|
|
96
|
+
* 2. Without `severity`, `passed: false` is an error unless `warning: true`
|
|
97
|
+
* downgrades it to a warning.
|
|
98
|
+
*
|
|
99
|
+
* This is the single implementation on purpose. Before 0.41.0 there were two
|
|
100
|
+
* and they disagreed on exactly one cell: `reportDoctorResults` consulted
|
|
101
|
+
* `warning` only inside the `passed === true` branch, so the documented
|
|
102
|
+
* downgrade produced an error and a non-zero doctor exit, while
|
|
103
|
+
* `bootstrap.ts` — reading the same array one line earlier — classified it as
|
|
104
|
+
* a warning. No in-tree producer emitted that combination (`failure()` always
|
|
105
|
+
* sets `severity: 'error'`), so the contradiction was latent rather than
|
|
106
|
+
* observable; the interface is exported, so the next hand-rolled check would
|
|
107
|
+
* have hit it.
|
|
108
|
+
*/
|
|
109
|
+
export declare function resolveDoctorSeverity(check: DoctorCheck): 'ok' | 'warning' | 'error';
|
|
90
110
|
/**
|
|
91
111
|
* Builds a DoctorCheck object representing a successful "OK" check.
|
|
92
112
|
*/
|
|
@@ -4,6 +4,34 @@
|
|
|
4
4
|
* `doctor.ts` and sibling check modules. Kept separate so registries can import
|
|
5
5
|
* these symbols without creating cycles through `doctor.ts`.
|
|
6
6
|
*/
|
|
7
|
+
/**
|
|
8
|
+
* Resolves a {@link DoctorCheck} to its effective severity, implementing the
|
|
9
|
+
* field-precedence contract documented on the interface
|
|
10
|
+
* (`src/types/commands/project.ts`):
|
|
11
|
+
*
|
|
12
|
+
* 1. `severity`, when present, is authoritative — regardless of `passed`.
|
|
13
|
+
* 2. Without `severity`, `passed: false` is an error unless `warning: true`
|
|
14
|
+
* downgrades it to a warning.
|
|
15
|
+
*
|
|
16
|
+
* This is the single implementation on purpose. Before 0.41.0 there were two
|
|
17
|
+
* and they disagreed on exactly one cell: `reportDoctorResults` consulted
|
|
18
|
+
* `warning` only inside the `passed === true` branch, so the documented
|
|
19
|
+
* downgrade produced an error and a non-zero doctor exit, while
|
|
20
|
+
* `bootstrap.ts` — reading the same array one line earlier — classified it as
|
|
21
|
+
* a warning. No in-tree producer emitted that combination (`failure()` always
|
|
22
|
+
* sets `severity: 'error'`), so the contradiction was latent rather than
|
|
23
|
+
* observable; the interface is exported, so the next hand-rolled check would
|
|
24
|
+
* have hit it.
|
|
25
|
+
*/
|
|
26
|
+
export function resolveDoctorSeverity(check) {
|
|
27
|
+
if (check.severity !== undefined) {
|
|
28
|
+
return check.severity;
|
|
29
|
+
}
|
|
30
|
+
if (check.warning) {
|
|
31
|
+
return 'warning';
|
|
32
|
+
}
|
|
33
|
+
return check.passed ? 'ok' : 'error';
|
|
34
|
+
}
|
|
7
35
|
/**
|
|
8
36
|
* Builds a DoctorCheck object representing a successful "OK" check.
|
|
9
37
|
*/
|
|
@@ -15,6 +15,8 @@ async function resolveDeclaredTool(projectRoot, tool) {
|
|
|
15
15
|
return found.exitCode === 0 ? line?.trim() : undefined;
|
|
16
16
|
}
|
|
17
17
|
catch {
|
|
18
|
+
// `xcrun -find` is absent or the tool is unknown — either way the tool has
|
|
19
|
+
// no resolvable path, which is what `undefined` means to the caller.
|
|
18
20
|
return undefined;
|
|
19
21
|
}
|
|
20
22
|
}
|