@hominis/fireforge 0.50.0 → 0.51.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 +47 -0
- package/dist/build-info.json +4 -4
- package/dist/src/commands/check-report.d.ts +17 -4
- package/dist/src/commands/check-report.js +19 -6
- package/dist/src/commands/check-run.d.ts +5 -0
- package/dist/src/commands/check-run.js +9 -1
- package/dist/src/commands/check-steps.d.ts +8 -0
- package/dist/src/commands/check-steps.js +31 -6
- package/dist/src/commands/check.js +1 -1
- package/dist/src/commands/declarations-report.d.ts +43 -0
- package/dist/src/commands/declarations-report.js +76 -0
- package/dist/src/commands/export-placement-gate.js +8 -0
- package/dist/src/commands/furnace/rename-registrations.js +7 -2
- package/dist/src/commands/furnace/validate.js +26 -1
- package/dist/src/commands/lint-per-patch.js +6 -2
- package/dist/src/commands/lint-per-run-checkjs.js +2 -2
- package/dist/src/commands/lint-report.js +7 -2
- package/dist/src/commands/manifest.js +2 -0
- package/dist/src/commands/park.d.ts +22 -0
- package/dist/src/commands/park.js +232 -0
- package/dist/src/commands/patch/delete.js +11 -6
- package/dist/src/commands/patch/index.d.ts +3 -3
- package/dist/src/commands/patch/index.js +8 -4
- package/dist/src/commands/patch/lint-ignore-from.d.ts +44 -0
- package/dist/src/commands/patch/lint-ignore-from.js +142 -0
- package/dist/src/commands/patch/lint-ignore.js +39 -29
- package/dist/src/commands/patch/move-files-create.js +11 -4
- package/dist/src/commands/patch/move-files-into.js +10 -4
- package/dist/src/commands/patch/move-files.js +3 -1
- package/dist/src/commands/patch/removal-created-files.d.ts +48 -0
- package/dist/src/commands/patch/removal-created-files.js +83 -0
- package/dist/src/commands/patch/rename.js +15 -3
- package/dist/src/commands/patch/reserve.d.ts +33 -0
- package/dist/src/commands/patch/reserve.js +152 -0
- package/dist/src/commands/patch/retire.js +11 -6
- package/dist/src/commands/patch/split-plan.d.ts +10 -1
- package/dist/src/commands/patch/split-plan.js +16 -1
- package/dist/src/commands/patch/split.js +14 -5
- package/dist/src/commands/patch/stage.d.ts +34 -0
- package/dist/src/commands/patch/stage.js +153 -0
- package/dist/src/commands/patch/staged-dependency-remove.d.ts +31 -0
- package/dist/src/commands/patch/staged-dependency-remove.js +80 -0
- package/dist/src/commands/patch/staged-dependency.js +40 -98
- package/dist/src/commands/re-export-adjacent.d.ts +5 -0
- package/dist/src/commands/re-export-adjacent.js +4 -3
- package/dist/src/commands/re-export-bulk-scan.d.ts +2 -1
- package/dist/src/commands/re-export-bulk-scan.js +22 -7
- package/dist/src/commands/re-export-hunks.d.ts +35 -0
- package/dist/src/commands/re-export-hunks.js +220 -0
- package/dist/src/commands/re-export-options.js +29 -2
- package/dist/src/commands/re-export-purity-scope.d.ts +18 -0
- package/dist/src/commands/re-export-purity-scope.js +131 -0
- package/dist/src/commands/re-export-register.js +15 -1
- package/dist/src/commands/re-export-removed.d.ts +1 -1
- package/dist/src/commands/re-export-removed.js +17 -2
- package/dist/src/commands/re-export-scan.d.ts +2 -0
- package/dist/src/commands/re-export-scan.js +4 -1
- package/dist/src/commands/re-export-single.js +102 -42
- package/dist/src/commands/re-export-stale-registrations.d.ts +18 -0
- package/dist/src/commands/re-export-stale-registrations.js +62 -0
- package/dist/src/commands/re-export.js +35 -34
- package/dist/src/commands/test-build-furnace.d.ts +3 -2
- package/dist/src/commands/test-build-furnace.js +8 -2
- package/dist/src/commands/test-build-phase.js +24 -7
- package/dist/src/commands/test-checkjs.js +3 -2
- package/dist/src/commands/test-dispatch.d.ts +39 -0
- package/dist/src/commands/test-dispatch.js +211 -0
- package/dist/src/commands/test-furnace-reach.d.ts +37 -0
- package/dist/src/commands/test-furnace-reach.js +132 -0
- package/dist/src/commands/test-orchestrate.d.ts +9 -0
- package/dist/src/commands/test-orchestrate.js +22 -4
- package/dist/src/commands/test-perf-scope.d.ts +21 -0
- package/dist/src/commands/test-perf-scope.js +55 -0
- package/dist/src/commands/test-prelock.js +100 -4
- package/dist/src/commands/test-register.js +18 -3
- package/dist/src/commands/test-results.d.ts +62 -0
- package/dist/src/commands/test-results.js +62 -0
- package/dist/src/commands/test-run.d.ts +13 -55
- package/dist/src/commands/test-run.js +73 -101
- package/dist/src/commands/test-shards.d.ts +110 -0
- package/dist/src/commands/test-shards.js +255 -0
- package/dist/src/commands/test-stale-gate.d.ts +5 -1
- package/dist/src/commands/test-stale-gate.js +4 -2
- package/dist/src/commands/test-verdict.d.ts +2 -1
- package/dist/src/commands/test.d.ts +1 -1
- package/dist/src/commands/test.js +65 -75
- package/dist/src/commands/token-lint.js +48 -8
- package/dist/src/commands/verify.js +9 -2
- package/dist/src/core/build-prepare.d.ts +6 -0
- package/dist/src/core/build-prepare.js +29 -3
- 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-check.js +83 -7
- package/dist/src/core/config-validate-test-toolchains.js +14 -0
- package/dist/src/core/config-validate-token-scales.d.ts +3 -0
- package/dist/src/core/config-validate-token-scales.js +47 -5
- package/dist/src/core/config-validate.js +6 -0
- package/dist/src/core/coverage-extend.js +2 -2
- package/dist/src/core/coverage-records.d.ts +11 -0
- package/dist/src/core/coverage-records.js +34 -0
- package/dist/src/core/engine-session-lock.d.ts +6 -0
- package/dist/src/core/engine-session-lock.js +10 -2
- package/dist/src/core/file-lock-shared.d.ts +57 -0
- package/dist/src/core/file-lock-shared.js +117 -0
- package/dist/src/core/file-lock.d.ts +9 -0
- package/dist/src/core/file-lock.js +58 -2
- package/dist/src/core/furnace-apply.js +10 -10
- package/dist/src/core/furnace-config-validate-exceptions.d.ts +10 -3
- package/dist/src/core/furnace-config-validate-exceptions.js +39 -8
- package/dist/src/core/furnace-config-validate.js +7 -1
- package/dist/src/core/furnace-registration-jar-inplace.d.ts +12 -0
- package/dist/src/core/furnace-registration-jar-inplace.js +80 -0
- package/dist/src/core/furnace-validate-accessibility.d.ts +4 -2
- package/dist/src/core/furnace-validate-accessibility.js +61 -27
- package/dist/src/core/furnace-validate-exceptions.d.ts +12 -4
- package/dist/src/core/furnace-validate-exceptions.js +41 -4
- package/dist/src/core/furnace-validate-icon-button.js +30 -1
- package/dist/src/core/history-log-file.js +8 -1
- package/dist/src/core/line-prefixer.d.ts +18 -0
- package/dist/src/core/line-prefixer.js +34 -0
- package/dist/src/core/mach.d.ts +7 -0
- package/dist/src/core/mach.js +24 -9
- package/dist/src/core/module-resolution-preflight.d.ts +19 -3
- package/dist/src/core/module-resolution-preflight.js +24 -6
- package/dist/src/core/negative-control-compiled.d.ts +27 -0
- package/dist/src/core/negative-control-compiled.js +73 -0
- package/dist/src/core/negative-control-diff.d.ts +14 -2
- package/dist/src/core/negative-control-diff.js +49 -6
- package/dist/src/core/negative-control.d.ts +12 -0
- package/dist/src/core/negative-control.js +48 -5
- package/dist/src/core/park-store.d.ts +63 -0
- package/dist/src/core/park-store.js +216 -0
- package/dist/src/core/patch-export-update.d.ts +4 -0
- package/dist/src/core/patch-export-update.js +12 -3
- package/dist/src/core/patch-export.d.ts +3 -2
- package/dist/src/core/patch-export.js +21 -14
- package/dist/src/core/patch-files.js +26 -8
- package/dist/src/core/patch-lint-checkjs-any-import.d.ts +6 -4
- package/dist/src/core/patch-lint-checkjs-any-import.js +9 -7
- package/dist/src/core/patch-lint-checkjs-diagnostics.d.ts +17 -0
- package/dist/src/core/patch-lint-checkjs-diagnostics.js +32 -0
- package/dist/src/core/patch-lint-checkjs.d.ts +10 -1
- package/dist/src/core/patch-lint-checkjs.js +13 -3
- package/dist/src/core/patch-lint-cross.d.ts +3 -1
- package/dist/src/core/patch-lint-cross.js +5 -0
- package/dist/src/core/patch-lint-css.js +14 -10
- package/dist/src/core/patch-lint-forward-registration-extended.d.ts +5 -0
- package/dist/src/core/patch-lint-forward-registration-extended.js +1 -1
- package/dist/src/core/patch-lint-ignore.d.ts +56 -12
- package/dist/src/core/patch-lint-ignore.js +173 -43
- package/dist/src/core/patch-lint-ownership.d.ts +10 -0
- package/dist/src/core/patch-lint-ownership.js +26 -0
- package/dist/src/core/patch-lint-size.js +9 -1
- package/dist/src/core/patch-lint-token-scale.d.ts +26 -8
- package/dist/src/core/patch-lint-token-scale.js +206 -29
- package/dist/src/core/patch-lint.js +2 -2
- package/dist/src/core/patch-manifest-io.js +8 -1
- package/dist/src/core/patch-manifest-key-order.d.ts +24 -0
- package/dist/src/core/patch-manifest-key-order.js +61 -0
- package/dist/src/core/patch-manifest-stage.d.ts +46 -0
- package/dist/src/core/patch-manifest-stage.js +96 -0
- package/dist/src/core/patch-manifest-validate.js +27 -1
- package/dist/src/core/patch-order-legal.d.ts +2 -1
- package/dist/src/core/patch-order-legal.js +7 -2
- package/dist/src/core/patch-policy.d.ts +6 -2
- package/dist/src/core/patch-policy.js +14 -7
- package/dist/src/core/patch-retired.d.ts +26 -4
- package/dist/src/core/patch-retired.js +40 -2
- package/dist/src/core/run-log.d.ts +6 -0
- package/dist/src/core/run-log.js +17 -1
- package/dist/src/core/test-harness-crash.d.ts +2 -1
- package/dist/src/core/test-harness-crash.js +5 -8
- package/dist/src/core/test-harness-signature.d.ts +16 -0
- package/dist/src/core/test-harness-signature.js +19 -1
- package/dist/src/core/test-raw-log.d.ts +66 -0
- package/dist/src/core/test-raw-log.js +122 -0
- package/dist/src/core/test-run-completeness.d.ts +36 -3
- package/dist/src/core/test-run-completeness.js +57 -17
- package/dist/src/core/test-xpcshell-retry.d.ts +2 -0
- package/dist/src/core/test-xpcshell-retry.js +2 -1
- package/dist/src/core/token-lint.d.ts +5 -2
- package/dist/src/core/token-lint.js +8 -3
- package/dist/src/core/token-sibling-sheets.d.ts +46 -0
- package/dist/src/core/token-sibling-sheets.js +119 -0
- package/dist/src/core/tree-guard.js +2 -0
- package/dist/src/core/typecheck-shim.d.ts +5 -1
- package/dist/src/core/typecheck-shim.js +115 -2
- package/dist/src/core/typecheck.js +3 -1
- package/dist/src/index.d.ts +9 -3
- package/dist/src/index.js +4 -1
- package/dist/src/types/commands/index.d.ts +2 -2
- package/dist/src/types/commands/options.d.ts +68 -7
- package/dist/src/types/commands/patches.d.ts +43 -0
- package/dist/src/types/config.d.ts +74 -2
- package/dist/src/types/furnace.d.ts +20 -0
- package/dist/src/types/index.d.ts +1 -1
- package/dist/src/utils/options.d.ts +3 -2
- package/dist/src/utils/options.js +9 -4
- package/docs/configuration.md +94 -9
- package/docs/furnace.md +43 -1
- package/docs/lifecycle-invariants.md +13 -11
- package/docs/machine-output.md +64 -18
- package/docs/patch-workflow.md +174 -13
- package/docs/testing.md +73 -4
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,52 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.51.0
|
|
4
|
+
|
|
5
|
+
### Test harness
|
|
6
|
+
|
|
7
|
+
- `started=` counts start lines as the output streams, or reads the mozlog raw log when the run wrote one. A suite printing more than 2 MB, or any `--full-output` run, no longer reports files that ran as unstarted. A refusal names its evidence with `started-source=`.
|
|
8
|
+
- Sharded runs print a `FIREFORGE-SHARD:` line per shard and `failed-shards=` on the verdict. New `--shard-size <n>` runs n files per mach invocation, judged per file. New `--results <path>` writes per-file JSON (status, expected, duration, log offset) from mozlog and adds `results=` to the verdict.
|
|
9
|
+
- Negative controls accept a plain unified diff, and a `git diff --no-index` against an edited copy. A control on compiled source is refused without `--build`. With `--build` it goes in before the build and is restored afterwards (`control-build=compiled`). A build-less control on stale packaging is refused before the lock.
|
|
10
|
+
- Mid-run engine edits the running browser cannot see no longer make a run inconclusive (`drift-tolerated=<n>`). A scoped `--build` of one file keeps its manifest siblings' coverage, and packaging refusals arrive before the lock.
|
|
11
|
+
- `--furnace-apply all|reached|none` and `test.furnaceApply` scope the pre-test Furnace apply; components left out are named in `furnace-skipped=`. The default stays `all`.
|
|
12
|
+
|
|
13
|
+
### Locks
|
|
14
|
+
|
|
15
|
+
- New opt-in `--shared-lock` and `test.lockMode: "shared"` let build-less test runs with auto ports overlap on one objdir; builds, re-exports and every other writer stay exclusive. New `test --jobs <n>` runs a request's shards on n harness instances inside one hold, each on its own ports, with `[job i]`-prefixed output.
|
|
16
|
+
- `--wait-lock` and `FIREFORGE_WAIT_LOCK` accept up to 86400 seconds.
|
|
17
|
+
|
|
18
|
+
### Patch queue
|
|
19
|
+
|
|
20
|
+
- Added `patch reserve <order>`, which holds a claimed order under `allowGaps: false`; default allocation skips it, and the export that takes it fills it. Added `patch stage <name>...`, which stages only the named patches' rows of `patches.json` (validated) and their patch files. Every writer emits one canonical `patches.json` key order, so the first full save may reorder keys once.
|
|
21
|
+
- Added `re-export --hunks-from <diff>` and `--exclude-hunks <diff>`, which export a shared file hunk by hunk. Added `fireforge park`, which sets a session's edits to a patch's files aside and merges them back three-way, warning about Fluent ids and files still referenced outside the patch.
|
|
22
|
+
- `re-export --dry-run` takes no engine lock and runs on a private git index. Its purity guard covers only the selected patches, their rows and the engine files they own, and names a concurrent writer instead of calling a peer's write a FireForge bug.
|
|
23
|
+
- Refused `re-export --scan`, `patch move-files` and `patch split` write one `--from`-ready declaration report (`--declarations-out`) and name one `staged-dependency --add --from` call per holding patch. `--refuse-adjacent-unmanaged` combines with `--scan`.
|
|
24
|
+
- `staged-dependency --remove` accepts `--creates` alone, matches registration lines regardless of whitespace, and exits 8 when nothing matches. Re-export drops a staged registration whose `creates` path no patch owns. An `--expect-removed` path owned by an unselected patch names that owner.
|
|
25
|
+
- `patch delete` and `retire` name created files still on disk and gain `--delete-created`. A description-only `patch rename` needs no `--yes`.
|
|
26
|
+
- An unscoped `verify`, and so `fireforge check`, also reads unexported engine files for imports of unregistered `resource:///modules` modules.
|
|
27
|
+
|
|
28
|
+
### Lint and tokens
|
|
29
|
+
|
|
30
|
+
- `lintIgnore` ceilings take a free-form `meta` object and `share: "test"|"product"`, and a notice no longer exceeds a ceiling. `lint --report` adds `lintIgnoreWaivers` for bare waivers. `patch lint-ignore` gains `--add <rule>=<max>`, `--share` and `--from <json|report>`.
|
|
31
|
+
- `patchLint.tokenScales` skips `@font-face` and other descriptor at-rules. Each scale also takes a `prefix` list, `allowInCalc`, `offsetCap`, `exclude` globs and a `minFiles` floor (`token-scale-vacuous`). `token lint` treats a `var()` with a fallback as optional and resolves tokens declared by sibling sheets: from document links, from `@import`, or from `furnace.json#tokenSiblingSheets`.
|
|
32
|
+
- A test script's per-patch checkJs resolves modules created by earlier patches. Opt-in `typecheck.litTypes` types `LitElement` and `MozLitElement`. A scoped `lint --per-patch --patches` keeps stale staged declarations. `discoverPatches` follows symlinked patch files.
|
|
33
|
+
- The API exports `measurePatchSize`, `isPatchSizeRegistrationFile`, `codeLineMask` and `evaluatePatchPolicyContentForQueue`.
|
|
34
|
+
|
|
35
|
+
### Gates
|
|
36
|
+
|
|
37
|
+
- `fireforge check` gains `check.timeout` and `check.timeouts.<step>` for built-in steps, `check.failOnSkipped`, `check.lintReport` (passed to extra steps as `FIREFORGE_CHECK_LINT_REPORT`), and `extraSteps[].after`. The verdict line names `skipped-steps=`.
|
|
38
|
+
|
|
39
|
+
### Furnace
|
|
40
|
+
|
|
41
|
+
- `furnace validate`: an exception no longer suppresses an error (`validation-exception-on-error`), and an exception can name a `tag`. `minComponents` sets a floor (`component-floor`). `panel-item` counts as natively interactive, and the accessibility checks judge the host module together with its companion modules.
|
|
42
|
+
- `furnace deploy` and `furnace rename` edit `jar.mn` lines in place instead of re-sorting the widget's block.
|
|
43
|
+
- The `icon-button-label` check scans a button's content for text outside tags instead of stripping tags with a regex. A `>` inside a quoted attribute no longer ends the tag.
|
|
44
|
+
|
|
45
|
+
### Other
|
|
46
|
+
|
|
47
|
+
- Windows: when two commands race to migrate the legacy history log, the loser stands down instead of failing on a claim the other command holds. The `check` process test no longer assumes a POSIX shell, and the `build` and `test` pre-lock tests no longer assume POSIX paths.
|
|
48
|
+
- Dev tooling: `@types/node` 26.6.5, `@vitest/coverage-v8` and `vitest` 5.0.5, `eslint-plugin-jsdoc` 65.3.6, `knip` 6.41.0, `prettier` 3.9.10, `typescript-eslint` 8.71.2 and `vite` 8.3.4. Runtime: `acorn` 8.19.0 and `magic-string` 1.4.3. CI: `actions/setup-node` 7.1.0 and `github/codeql-action` 4.38.3.
|
|
49
|
+
|
|
3
50
|
## 0.50.0
|
|
4
51
|
|
|
5
52
|
### Size budget
|
package/dist/build-info.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"version": "0.
|
|
4
|
-
"commit": "
|
|
5
|
-
"shortCommit": "
|
|
3
|
+
"version": "0.51.0",
|
|
4
|
+
"commit": "bd93338b037832fdcd69259a70a3a4b134b98906",
|
|
5
|
+
"shortCommit": "bd93338b0378",
|
|
6
6
|
"dirty": false,
|
|
7
7
|
"dirtyHash": null,
|
|
8
|
-
"buildTime": "2026-10-
|
|
8
|
+
"buildTime": "2026-10-19T12:33:48.474Z"
|
|
9
9
|
}
|
|
@@ -3,8 +3,15 @@ import type { CheckStepResult } from './check-run.js';
|
|
|
3
3
|
* Why a check failed. `step-failures`: at least one step went red.
|
|
4
4
|
* `lock-timeout`: every failing step exited 15, so nothing was judged red,
|
|
5
5
|
* the steps just never got their locks. Re-queue rather than fix.
|
|
6
|
+
* `skipped-steps`: nothing went red, but `check.failOnSkipped` is set and a
|
|
7
|
+
* step had nothing to run.
|
|
6
8
|
*/
|
|
7
|
-
export type CheckVerdictReason = 'step-failures' | 'lock-timeout';
|
|
9
|
+
export type CheckVerdictReason = 'step-failures' | 'lock-timeout' | 'skipped-steps';
|
|
10
|
+
/** How the verdict treats skipped steps. */
|
|
11
|
+
export interface CheckJudgeOptions {
|
|
12
|
+
/** `check.failOnSkipped`: a step skipped for want of anything to run fails the check. */
|
|
13
|
+
failOnSkipped?: boolean | undefined;
|
|
14
|
+
}
|
|
8
15
|
/** The verdict over all steps. */
|
|
9
16
|
export interface CheckVerdict {
|
|
10
17
|
passed: boolean;
|
|
@@ -13,11 +20,17 @@ export interface CheckVerdict {
|
|
|
13
20
|
skipped: string[];
|
|
14
21
|
steps: number;
|
|
15
22
|
}
|
|
16
|
-
/**
|
|
17
|
-
|
|
23
|
+
/**
|
|
24
|
+
* Judges the results: any failing step fails the check. Under
|
|
25
|
+
* `failOnSkipped`, so does a step skipped because the project has nothing
|
|
26
|
+
* for it to run; it is then listed in `failed` too.
|
|
27
|
+
*/
|
|
28
|
+
export declare function judgeCheck(results: readonly CheckStepResult[], options?: CheckJudgeOptions): CheckVerdict;
|
|
18
29
|
/**
|
|
19
30
|
* The verdict line: `FIREFORGE-VERDICT: PASS steps=<n> skipped=<n>` or
|
|
20
|
-
* `FIREFORGE-VERDICT: FAIL reason=<reason> failed=<a,b> steps=<n> skipped=<n
|
|
31
|
+
* `FIREFORGE-VERDICT: FAIL reason=<reason> failed=<a,b> steps=<n> skipped=<n>`,
|
|
32
|
+
* with an additive `skipped-steps=<a,b>` naming the skipped steps when
|
|
33
|
+
* there are any.
|
|
21
34
|
*/
|
|
22
35
|
export declare function formatCheckVerdictLine(verdict: CheckVerdict): string;
|
|
23
36
|
/** The per-step table: one aligned row per step, with its detail. */
|
|
@@ -8,12 +8,19 @@
|
|
|
8
8
|
import { setStdoutSealed } from '../utils/logger.js';
|
|
9
9
|
/** Exit code a step reports when its lock wait expired. */
|
|
10
10
|
const LOCK_TIMEOUT_EXIT = 15;
|
|
11
|
-
/**
|
|
12
|
-
|
|
11
|
+
/**
|
|
12
|
+
* Judges the results: any failing step fails the check. Under
|
|
13
|
+
* `failOnSkipped`, so does a step skipped because the project has nothing
|
|
14
|
+
* for it to run; it is then listed in `failed` too.
|
|
15
|
+
*/
|
|
16
|
+
export function judgeCheck(results, options = {}) {
|
|
13
17
|
const failedResults = results.filter((result) => result.status === 'fail');
|
|
18
|
+
const gaps = options.failOnSkipped === true
|
|
19
|
+
? results.filter((result) => result.status === 'skipped' && result.skipKind !== 'config')
|
|
20
|
+
: [];
|
|
14
21
|
const verdict = {
|
|
15
|
-
passed: failedResults.length === 0,
|
|
16
|
-
failed: failedResults.map((result) => result.name),
|
|
22
|
+
passed: failedResults.length === 0 && gaps.length === 0,
|
|
23
|
+
failed: [...failedResults, ...gaps].map((result) => result.name),
|
|
17
24
|
skipped: results.filter((result) => result.status === 'skipped').map((result) => result.name),
|
|
18
25
|
steps: results.length,
|
|
19
26
|
};
|
|
@@ -22,14 +29,20 @@ export function judgeCheck(results) {
|
|
|
22
29
|
? 'lock-timeout'
|
|
23
30
|
: 'step-failures';
|
|
24
31
|
}
|
|
32
|
+
else if (gaps.length > 0) {
|
|
33
|
+
verdict.reason = 'skipped-steps';
|
|
34
|
+
}
|
|
25
35
|
return verdict;
|
|
26
36
|
}
|
|
27
37
|
/**
|
|
28
38
|
* The verdict line: `FIREFORGE-VERDICT: PASS steps=<n> skipped=<n>` or
|
|
29
|
-
* `FIREFORGE-VERDICT: FAIL reason=<reason> failed=<a,b> steps=<n> skipped=<n
|
|
39
|
+
* `FIREFORGE-VERDICT: FAIL reason=<reason> failed=<a,b> steps=<n> skipped=<n>`,
|
|
40
|
+
* with an additive `skipped-steps=<a,b>` naming the skipped steps when
|
|
41
|
+
* there are any.
|
|
30
42
|
*/
|
|
31
43
|
export function formatCheckVerdictLine(verdict) {
|
|
32
|
-
const
|
|
44
|
+
const skippedNames = verdict.skipped.length > 0 ? ` skipped-steps=${verdict.skipped.join(',')}` : '';
|
|
45
|
+
const counts = `steps=${verdict.steps} skipped=${verdict.skipped.length}${skippedNames}`;
|
|
33
46
|
if (verdict.passed)
|
|
34
47
|
return `FIREFORGE-VERDICT: PASS ${counts}`;
|
|
35
48
|
return `FIREFORGE-VERDICT: FAIL reason=${verdict.reason ?? 'step-failures'} failed=${verdict.failed.join(',')} ${counts}`;
|
|
@@ -6,6 +6,11 @@ export interface CheckStepResult {
|
|
|
6
6
|
/** The command line, as the table prints it. */
|
|
7
7
|
command: string;
|
|
8
8
|
status: 'pass' | 'fail' | 'skipped';
|
|
9
|
+
/**
|
|
10
|
+
* For a skipped step: `config` when `check.skip` names it, `unavailable`
|
|
11
|
+
* when the project has nothing for it to run.
|
|
12
|
+
*/
|
|
13
|
+
skipKind?: 'config' | 'unavailable';
|
|
9
14
|
/** The child's exit code. Null when it never ran or was killed at its budget. */
|
|
10
15
|
exitCode: number | null;
|
|
11
16
|
durationMs: number;
|
|
@@ -19,7 +19,14 @@ function tailLines(text) {
|
|
|
19
19
|
async function runStep(plan, now) {
|
|
20
20
|
const base = { name: plan.name, kind: plan.kind, command: plan.display };
|
|
21
21
|
if (plan.skipReason !== undefined) {
|
|
22
|
-
return {
|
|
22
|
+
return {
|
|
23
|
+
...base,
|
|
24
|
+
status: 'skipped',
|
|
25
|
+
skipKind: plan.skipKind ?? 'unavailable',
|
|
26
|
+
exitCode: null,
|
|
27
|
+
durationMs: 0,
|
|
28
|
+
detail: plan.skipReason,
|
|
29
|
+
};
|
|
23
30
|
}
|
|
24
31
|
let stdout = '';
|
|
25
32
|
let combined = '';
|
|
@@ -34,6 +41,7 @@ async function runStep(plan, now) {
|
|
|
34
41
|
cwd: plan.cwd,
|
|
35
42
|
processGroup: true,
|
|
36
43
|
...(plan.timeoutMs !== undefined ? { timeout: plan.timeoutMs } : {}),
|
|
44
|
+
...(plan.env !== undefined ? { env: plan.env } : {}),
|
|
37
45
|
onStdout: (chunk) => {
|
|
38
46
|
stdout += chunk;
|
|
39
47
|
append(chunk);
|
|
@@ -15,6 +15,14 @@ export interface CheckStepPlan {
|
|
|
15
15
|
after: string[];
|
|
16
16
|
/** Set when the step is planned but not run, with the reason. */
|
|
17
17
|
skipReason?: string | undefined;
|
|
18
|
+
/**
|
|
19
|
+
* Why it is skipped: `config` when `check.skip` names it (a decision),
|
|
20
|
+
* `unavailable` when the project has nothing for it to run (a gap,
|
|
21
|
+
* which `check.failOnSkipped` fails).
|
|
22
|
+
*/
|
|
23
|
+
skipKind?: 'config' | 'unavailable' | undefined;
|
|
24
|
+
/** Extra environment for the child. */
|
|
25
|
+
env?: Record<string, string> | undefined;
|
|
18
26
|
/** Condenses a finished run into the table's detail cell. */
|
|
19
27
|
summarize: (output: {
|
|
20
28
|
stdout: string;
|
|
@@ -110,11 +110,18 @@ function builtinStep(invocation, projectRoot, name, args, summarize, extra = {})
|
|
|
110
110
|
args: [...invocation.prefix, ...args],
|
|
111
111
|
display: `fireforge ${args.join(' ')}`,
|
|
112
112
|
cwd: projectRoot,
|
|
113
|
+
timeoutMs: extra.timeoutMs,
|
|
113
114
|
after: extra.after ?? [],
|
|
114
115
|
skipReason: extra.skipReason,
|
|
116
|
+
skipKind: extra.skipReason !== undefined ? 'unavailable' : undefined,
|
|
115
117
|
summarize,
|
|
116
118
|
};
|
|
117
119
|
}
|
|
120
|
+
/** A built-in step's budget: `check.timeouts.<step>`, else `check.timeout`. */
|
|
121
|
+
function builtinTimeoutMs(check, name) {
|
|
122
|
+
const seconds = check.timeouts?.[name] ?? check.timeout;
|
|
123
|
+
return seconds !== undefined ? seconds * 1000 : undefined;
|
|
124
|
+
}
|
|
118
125
|
/** Runs a project command line through the platform shell. */
|
|
119
126
|
function shellInvocation(commandLine) {
|
|
120
127
|
return process.platform === 'win32'
|
|
@@ -139,18 +146,33 @@ export function buildCheckPlan(input) {
|
|
|
139
146
|
const furnaceWarnings = check.maxWarnings?.furnace !== undefined
|
|
140
147
|
? ['--max-warnings', String(check.maxWarnings.furnace)]
|
|
141
148
|
: [];
|
|
149
|
+
const lintReport = check.lintReport !== undefined ? join(projectRoot, check.lintReport) : undefined;
|
|
150
|
+
const lintReportArgs = lintReport !== undefined ? ['--report', lintReport] : [];
|
|
151
|
+
const timeoutMs = (name) => builtinTimeoutMs(check, name);
|
|
142
152
|
const plan = [
|
|
143
|
-
builtinStep(invocation, projectRoot, 'status', ['status', '--json', '--summary', '--check', ...statusPolicyArgs(check.status), ...patchArgs], summarizeStatus),
|
|
144
|
-
builtinStep(invocation, projectRoot, 'verify', ['verify', ...patchArgs], generic
|
|
145
|
-
|
|
153
|
+
builtinStep(invocation, projectRoot, 'status', ['status', '--json', '--summary', '--check', ...statusPolicyArgs(check.status), ...patchArgs], summarizeStatus, { timeoutMs: timeoutMs('status') }),
|
|
154
|
+
builtinStep(invocation, projectRoot, 'verify', ['verify', ...patchArgs], generic, {
|
|
155
|
+
timeoutMs: timeoutMs('verify'),
|
|
156
|
+
}),
|
|
157
|
+
builtinStep(invocation, projectRoot, 'lint', [
|
|
158
|
+
'lint',
|
|
159
|
+
'--per-patch',
|
|
160
|
+
'--notices',
|
|
161
|
+
'summary',
|
|
162
|
+
...lintWarnings,
|
|
163
|
+
...lintReportArgs,
|
|
164
|
+
...patchArgs,
|
|
165
|
+
], generic, { timeoutMs: timeoutMs('lint') }),
|
|
146
166
|
builtinStep(invocation, projectRoot, 'typecheck', ['typecheck'], generic, {
|
|
147
167
|
skipReason: config.typecheck === undefined ? 'no "typecheck" block in fireforge.json' : undefined,
|
|
168
|
+
timeoutMs: timeoutMs('typecheck'),
|
|
148
169
|
}),
|
|
149
170
|
builtinStep(invocation, projectRoot, 'furnace-validate', ['furnace', 'validate', '--json', ...furnaceWarnings], summarizeFurnace, {
|
|
150
171
|
after: ['typecheck'],
|
|
151
172
|
skipReason: input.furnaceConfigured ? undefined : 'no furnace.json',
|
|
173
|
+
timeoutMs: timeoutMs('furnace-validate'),
|
|
152
174
|
}),
|
|
153
|
-
builtinStep(invocation, projectRoot, 'token-coverage', ['token', 'coverage'], summarizeTokenCoverage),
|
|
175
|
+
builtinStep(invocation, projectRoot, 'token-coverage', ['token', 'coverage'], summarizeTokenCoverage, { timeoutMs: timeoutMs('token-coverage') }),
|
|
154
176
|
];
|
|
155
177
|
for (const step of check.extraSteps ?? []) {
|
|
156
178
|
const shell = shellInvocation(step.command);
|
|
@@ -162,14 +184,17 @@ export function buildCheckPlan(input) {
|
|
|
162
184
|
display: step.command,
|
|
163
185
|
cwd: step.cwd !== undefined ? join(projectRoot, step.cwd) : projectRoot,
|
|
164
186
|
timeoutMs: step.timeout !== undefined ? step.timeout * 1000 : undefined,
|
|
165
|
-
after: [],
|
|
187
|
+
after: step.after ?? [],
|
|
188
|
+
env: lintReport !== undefined ? { FIREFORGE_CHECK_LINT_REPORT: lintReport } : undefined,
|
|
166
189
|
summarize: generic,
|
|
167
190
|
});
|
|
168
191
|
}
|
|
169
192
|
for (const name of check.skip ?? []) {
|
|
170
193
|
const step = plan.find((entry) => entry.name === name);
|
|
171
|
-
if (step !== undefined)
|
|
194
|
+
if (step !== undefined) {
|
|
172
195
|
step.skipReason = 'skipped by check.skip in fireforge.json';
|
|
196
|
+
step.skipKind = 'config';
|
|
197
|
+
}
|
|
173
198
|
}
|
|
174
199
|
return plan;
|
|
175
200
|
}
|
|
@@ -123,7 +123,7 @@ async function runCheck(projectRoot, options) {
|
|
|
123
123
|
const runnable = plan.filter((step) => step.skipReason === undefined).length;
|
|
124
124
|
info(`Running ${runnable} step(s), up to ${Math.min(jobs, Math.max(1, runnable))} at once${patches ? `, judging ${patches.join(', ')}` : ''}.`);
|
|
125
125
|
const results = await runCheckSteps(plan, jobs);
|
|
126
|
-
const verdict = judgeCheck(results);
|
|
126
|
+
const verdict = judgeCheck(results, { failOnSkipped: config.check?.failOnSkipped });
|
|
127
127
|
if (options.json === true) {
|
|
128
128
|
writeCheckJson(results, verdict);
|
|
129
129
|
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { ConflictReport } from '../core/destructive.js';
|
|
2
|
+
import type { PatchLintIssue, PatchLintStagedDeclaration } from '../types/commands/index.js';
|
|
3
|
+
/** A written declaration report and the commands that apply it. */
|
|
4
|
+
export interface DeclarationsReport {
|
|
5
|
+
/** Absolute path of the JSON report. */
|
|
6
|
+
path: string;
|
|
7
|
+
/** Declarations written. */
|
|
8
|
+
count: number;
|
|
9
|
+
/** One `patch staged-dependency <holder> --add --from` call per holder. */
|
|
10
|
+
commands: string[];
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* A declaration as the report records it. `owner` is dropped when it names
|
|
14
|
+
* a patch the refused change would have created, which does not exist yet.
|
|
15
|
+
*/
|
|
16
|
+
export type ReportedDeclaration = Omit<PatchLintStagedDeclaration, 'owner'> & {
|
|
17
|
+
owner?: string;
|
|
18
|
+
};
|
|
19
|
+
/** Rewrites a projected declaration into the names the current queue uses. */
|
|
20
|
+
export type DeclarationRenamer = (declaration: PatchLintStagedDeclaration) => ReportedDeclaration;
|
|
21
|
+
/**
|
|
22
|
+
* Writes every declaration `issues` propose into one report, or returns
|
|
23
|
+
* undefined when they propose none.
|
|
24
|
+
*
|
|
25
|
+
* @param projectRoot - Project root (the default report directory lives under it)
|
|
26
|
+
* @param issues - The refusal's findings
|
|
27
|
+
* @param out - `--declarations-out`, relative to the working directory
|
|
28
|
+
* @param rename - Maps projected patch names back to current ones
|
|
29
|
+
*/
|
|
30
|
+
export declare function writeDeclarationsReport(projectRoot: string, issues: readonly PatchLintIssue[], out?: string, rename?: DeclarationRenamer): Promise<DeclarationsReport | undefined>;
|
|
31
|
+
/** The refusal lines that point at a written report. */
|
|
32
|
+
export declare function formatDeclarationsReport(report: DeclarationsReport): string[];
|
|
33
|
+
/**
|
|
34
|
+
* Appends the report lines to a refusal, writing the report from the
|
|
35
|
+
* refusal's findings. A refusal that proposes no declaration is returned
|
|
36
|
+
* unchanged, and so is no refusal at all.
|
|
37
|
+
*/
|
|
38
|
+
export declare function withDeclarationsReport(conflicts: ConflictReport | null, args: {
|
|
39
|
+
projectRoot: string;
|
|
40
|
+
regressions: readonly PatchLintIssue[];
|
|
41
|
+
out?: string | undefined;
|
|
42
|
+
rename?: DeclarationRenamer;
|
|
43
|
+
}): Promise<ConflictReport | null>;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* One `--from`-ready declaration report for a refused queue change.
|
|
4
|
+
*
|
|
5
|
+
* Moving a test out of a patch that owns a shared manifest refused with one
|
|
6
|
+
* `forward-registration` error per manifest line, and each line needed its
|
|
7
|
+
* own `patch staged-dependency --add` call: a slice that added seven suites
|
|
8
|
+
* wrote eighteen declarations by hand. `patch move-files`, `patch split`
|
|
9
|
+
* and `re-export --scan` now write every declaration their refusal proposes
|
|
10
|
+
* into one lint-report-shaped JSON (`queueIssues[].stagedDeclarations`) and
|
|
11
|
+
* name the single `patch staged-dependency <holder> --add --from <file>`
|
|
12
|
+
* call per holding patch that discharges them.
|
|
13
|
+
*/
|
|
14
|
+
import { join, resolve } from 'node:path';
|
|
15
|
+
import { STAGED_DEPENDENCY_REASON_PLACEHOLDER } from '../core/patch-lint-staged-registration.js';
|
|
16
|
+
import { ensureDir, writeJson } from '../utils/fs.js';
|
|
17
|
+
/** Where an unnamed report goes, relative to the project root. */
|
|
18
|
+
const DEFAULT_REPORT_DIR = join('.fireforge', 'declarations');
|
|
19
|
+
/** The checks whose findings carry declarations that discharge them. */
|
|
20
|
+
const DECLARABLE_CHECKS = new Set(['forward-import', 'forward-registration']);
|
|
21
|
+
/**
|
|
22
|
+
* Writes every declaration `issues` propose into one report, or returns
|
|
23
|
+
* undefined when they propose none.
|
|
24
|
+
*
|
|
25
|
+
* @param projectRoot - Project root (the default report directory lives under it)
|
|
26
|
+
* @param issues - The refusal's findings
|
|
27
|
+
* @param out - `--declarations-out`, relative to the working directory
|
|
28
|
+
* @param rename - Maps projected patch names back to current ones
|
|
29
|
+
*/
|
|
30
|
+
export async function writeDeclarationsReport(projectRoot, issues, out, rename = (declaration) => declaration) {
|
|
31
|
+
const queueIssues = issues
|
|
32
|
+
.filter((issue) => DECLARABLE_CHECKS.has(issue.check))
|
|
33
|
+
.map((issue) => ({
|
|
34
|
+
check: issue.check,
|
|
35
|
+
file: issue.file,
|
|
36
|
+
message: issue.message,
|
|
37
|
+
stagedDeclarations: (issue.stagedDeclarations ?? []).map(rename),
|
|
38
|
+
}))
|
|
39
|
+
.filter((issue) => issue.stagedDeclarations.length > 0);
|
|
40
|
+
const declarations = queueIssues.flatMap((issue) => issue.stagedDeclarations);
|
|
41
|
+
if (declarations.length === 0)
|
|
42
|
+
return undefined;
|
|
43
|
+
const path = out !== undefined
|
|
44
|
+
? resolve(out)
|
|
45
|
+
: join(projectRoot, DEFAULT_REPORT_DIR, `${new Date().toISOString().replace(/[:.]/g, '-')}-${process.pid}.json`);
|
|
46
|
+
await ensureDir(resolve(path, '..'));
|
|
47
|
+
await writeJson(path, { queueIssues });
|
|
48
|
+
const holders = [...new Set(declarations.map((declaration) => declaration.patch))].sort();
|
|
49
|
+
return {
|
|
50
|
+
path,
|
|
51
|
+
count: declarations.length,
|
|
52
|
+
commands: holders.map((holder) => `fireforge patch staged-dependency ${holder} --add --from ${path} ` +
|
|
53
|
+
STAGED_DEPENDENCY_REASON_PLACEHOLDER),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
/** The refusal lines that point at a written report. */
|
|
57
|
+
export function formatDeclarationsReport(report) {
|
|
58
|
+
return [
|
|
59
|
+
`All ${report.count} declaration(s) this refusal proposes are in ${report.path}; ` +
|
|
60
|
+
'declare them in one call per holding patch:',
|
|
61
|
+
...report.commands.map((command) => ` ${command}`),
|
|
62
|
+
];
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Appends the report lines to a refusal, writing the report from the
|
|
66
|
+
* refusal's findings. A refusal that proposes no declaration is returned
|
|
67
|
+
* unchanged, and so is no refusal at all.
|
|
68
|
+
*/
|
|
69
|
+
export async function withDeclarationsReport(conflicts, args) {
|
|
70
|
+
if (conflicts === null)
|
|
71
|
+
return null;
|
|
72
|
+
const report = await writeDeclarationsReport(args.projectRoot, args.regressions, args.out, args.rename);
|
|
73
|
+
if (report === undefined)
|
|
74
|
+
return conflicts;
|
|
75
|
+
return { ...conflicts, details: [...conflicts.details, ...formatDeclarationsReport(report)] };
|
|
76
|
+
}
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import { confirmDestructive } from '../core/destructive.js';
|
|
9
9
|
import { loadPatchesManifest } from '../core/patch-manifest.js';
|
|
10
10
|
import { applyRenameMapToManifest, buildProjectedManifest, enforcePatchPolicy, } from '../core/patch-policy.js';
|
|
11
|
+
import { reservationAt } from '../core/patch-retired.js';
|
|
11
12
|
import { buildPatchSourceMetadata } from '../core/patch-source-metadata.js';
|
|
12
13
|
import { InvalidArgumentError } from '../errors/base.js';
|
|
13
14
|
import { proceedAfterDecision } from './destructive-decision.js';
|
|
@@ -68,6 +69,13 @@ export async function gatePlacementPlan(args) {
|
|
|
68
69
|
forceUnsafe: options.forceUnsafe === true,
|
|
69
70
|
});
|
|
70
71
|
const summary = placementSummary(placementPlan);
|
|
72
|
+
const reservation = reservationAt(currentManifest, placementPlan.insertionOrder);
|
|
73
|
+
if (reservation !== undefined) {
|
|
74
|
+
const notice = `order ${placementPlan.insertionOrder} is reserved by ${reservation.owner}; ` +
|
|
75
|
+
'this export fills the reservation';
|
|
76
|
+
summary.push(notice);
|
|
77
|
+
s.message(notice);
|
|
78
|
+
}
|
|
71
79
|
const renameCount = placementPlan.renameMap.size;
|
|
72
80
|
// Route through confirmDestructive when the operation is destructive
|
|
73
81
|
// enough to warrant a prompt (more than one rename) or when the user asked
|
|
@@ -10,9 +10,11 @@ import { join } from 'node:path';
|
|
|
10
10
|
import { loadConfig } from '../../core/config.js';
|
|
11
11
|
import { resolveFtlChromeSubPath, resolveFtlLocaleJarMnPath, } from '../../core/furnace-constants.js';
|
|
12
12
|
import { resolveFurnaceMarkerComment } from '../../core/furnace-marker.js';
|
|
13
|
-
import { addCustomElementRegistration, addJarMnEntries, addLocaleFtlJarMnEntry, removeCustomElementRegistration,
|
|
13
|
+
import { addCustomElementRegistration, addJarMnEntries, addLocaleFtlJarMnEntry, removeCustomElementRegistration, removeLocaleFtlJarMnEntry, renameCustomElementRegistration, } from '../../core/furnace-registration.js';
|
|
14
|
+
import { renameJarMnEntriesInPlace } from '../../core/furnace-registration-jar-inplace.js';
|
|
14
15
|
import { snapshotFile } from '../../core/furnace-rollback.js';
|
|
15
16
|
import { pathExists, readText, removeFile, writeText } from '../../utils/fs.js';
|
|
17
|
+
import { renameComponentFileName } from './rename-helpers.js';
|
|
16
18
|
/**
|
|
17
19
|
* The project marker (`// <MARKER>:`) a registration line written by the
|
|
18
20
|
* rename carries, resolved exactly as `furnace apply` resolves it. An
|
|
@@ -50,10 +52,13 @@ export async function updateEngineRegistrations(options) {
|
|
|
50
52
|
}
|
|
51
53
|
if (await pathExists(jarMnPath)) {
|
|
52
54
|
await snapshotFile(journal, jarMnPath);
|
|
53
|
-
await removeJarMnEntries(engineDir, oldName);
|
|
54
55
|
const files = (await readdir(newDir, { withFileTypes: true }))
|
|
55
56
|
.filter((e) => e.isFile() && (e.name.endsWith('.mjs') || e.name.endsWith('.css')))
|
|
56
57
|
.map((e) => e.name);
|
|
58
|
+
// Rewrite the old lines where they stand, like the customElements.js
|
|
59
|
+
// entry above, so the rename moves no other line of the block; only a
|
|
60
|
+
// file with no line yet is inserted.
|
|
61
|
+
await renameJarMnEntriesInPlace(engineDir, oldName, newName, files, (file) => renameComponentFileName(file, oldName, newName));
|
|
57
62
|
if (files.length > 0) {
|
|
58
63
|
await addJarMnEntries(engineDir, newName, files);
|
|
59
64
|
}
|
|
@@ -7,6 +7,7 @@ import { assertFurnaceReady } from '../../core/furnace-precondition.js';
|
|
|
7
7
|
import { addCustomElementRegistration, addJarMnEntries, pruneStaleJarMnEntries, } from '../../core/furnace-registration.js';
|
|
8
8
|
import { validateAllComponents, validateComponent } from '../../core/furnace-validate.js';
|
|
9
9
|
import { applyValidationExceptions, } from '../../core/furnace-validate-exceptions.js';
|
|
10
|
+
import { createIssue } from '../../core/furnace-validate-helpers.js';
|
|
10
11
|
import { FurnaceError } from '../../errors/furnace.js';
|
|
11
12
|
import { toError } from '../../utils/errors.js';
|
|
12
13
|
import { pathExists } from '../../utils/fs.js';
|
|
@@ -189,6 +190,20 @@ async function applyAutoFix(projectRoot, config, furnacePaths, run, countedIssue
|
|
|
189
190
|
function exceptRun(run, config) {
|
|
190
191
|
return applyValidationExceptions([...run.results.values()].flat(), config.validationExceptions, run.judged);
|
|
191
192
|
}
|
|
193
|
+
/**
|
|
194
|
+
* The `furnace.json#minComponents` floor of a full run: an error when it
|
|
195
|
+
* judged fewer components, so a run over an emptied or moved components
|
|
196
|
+
* tree cannot pass by validating nothing. A run naming one component is
|
|
197
|
+
* not judged against it.
|
|
198
|
+
*/
|
|
199
|
+
function componentFloorIssue(config, name, judged) {
|
|
200
|
+
if (name !== undefined || config.minComponents === undefined)
|
|
201
|
+
return undefined;
|
|
202
|
+
if (judged >= config.minComponents)
|
|
203
|
+
return undefined;
|
|
204
|
+
return createIssue('furnace.json', 'error', 'component-floor', `Validated ${judged} component(s), under furnace.json minComponents ${config.minComponents}. ` +
|
|
205
|
+
'Check that the components tree is where furnace.json says, or lower the floor.');
|
|
206
|
+
}
|
|
192
207
|
/**
|
|
193
208
|
* Runs the furnace validate command to perform static analysis on components.
|
|
194
209
|
* @param projectRoot - Root directory of the project
|
|
@@ -216,22 +231,32 @@ async function runFurnaceValidate(projectRoot, name, options) {
|
|
|
216
231
|
const furnacePaths = getFurnacePaths(projectRoot);
|
|
217
232
|
const run = await runValidationPass(projectRoot, name, config, furnacePaths);
|
|
218
233
|
if (run === undefined) {
|
|
234
|
+
const floor = componentFloorIssue(config, name, 0);
|
|
219
235
|
if (options.json === true) {
|
|
220
236
|
renderValidateJson({
|
|
221
237
|
components: [],
|
|
222
|
-
outcome: { issues: [], excepted: [], stale: [] },
|
|
238
|
+
outcome: { issues: floor ? [floor] : [], excepted: [], stale: [] },
|
|
223
239
|
maxWarnings: options.maxWarnings,
|
|
224
240
|
});
|
|
225
241
|
}
|
|
242
|
+
if (floor !== undefined) {
|
|
243
|
+
renderValidateHuman([], [floor]);
|
|
244
|
+
throw new FurnaceError('Validation failed with 1 error(s).');
|
|
245
|
+
}
|
|
226
246
|
outro(name ? 'Validation complete' : 'Done');
|
|
227
247
|
return;
|
|
228
248
|
}
|
|
249
|
+
const floor = componentFloorIssue(config, name, run.results.size);
|
|
229
250
|
const firstPass = exceptRun(run, config);
|
|
251
|
+
if (floor !== undefined)
|
|
252
|
+
firstPass.issues.push(floor);
|
|
230
253
|
renderValidateHuman([...run.results.keys()], firstPass.issues);
|
|
231
254
|
let outcome = firstPass;
|
|
232
255
|
if (options.fix && firstPass.issues.length > 0) {
|
|
233
256
|
await applyAutoFix(projectRoot, config, furnacePaths, run, firstPass.issues);
|
|
234
257
|
outcome = exceptRun(run, config);
|
|
258
|
+
if (floor !== undefined)
|
|
259
|
+
outcome.issues.push(floor);
|
|
235
260
|
}
|
|
236
261
|
const report = {
|
|
237
262
|
components: [...run.results.keys()],
|
|
@@ -205,7 +205,8 @@ async function applyPerPatchResults(subset, results, issues, checkJs, cache, hel
|
|
|
205
205
|
heldNotices.push({ check: suppressedIssue.check, patch: patch.filename, suppressed: true });
|
|
206
206
|
continue;
|
|
207
207
|
}
|
|
208
|
-
const
|
|
208
|
+
const ceilings = (patch.lintIgnore ?? []).filter((entry) => isLintIgnoreCeiling(entry) && entry.rule === suppressedIssue.check);
|
|
209
|
+
const ceiling = ceilings.find((entry) => isLintIgnoreCeiling(entry) && entry.share === suppressedIssue.share) ?? ceilings[0];
|
|
209
210
|
const waiver = ceiling === undefined ? 'lintIgnore' : `lintIgnore (${formatLintIgnoreEntry(ceiling)})`;
|
|
210
211
|
info(`NOTICE [${suppressedIssue.check}] ${patch.filename}: suppressed by ${waiver} — ${suppressedIssue.message}`);
|
|
211
212
|
}
|
|
@@ -404,7 +405,10 @@ async function lintPerPatchInner(projectRoot, paths, options = {}) {
|
|
|
404
405
|
const { linted, skipped, cacheDirty, reusedCacheEntries, suppressed } = await applyPerPatchResults(subset, results, issues, checkJs, cache, heldNotices);
|
|
405
406
|
const queueIssues = [];
|
|
406
407
|
for (const issue of lintPatchQueue(ctx)) {
|
|
407
|
-
|
|
408
|
+
// A finding that names a subset patch stays in scope even when its file
|
|
409
|
+
// is one the patch no longer touches (a stale staged declaration).
|
|
410
|
+
const namesSubset = issue.patches?.some((name) => subsetNames.has(name)) === true;
|
|
411
|
+
if (isSubset && !namesSubset && !subsetTouchedFiles.has(issue.file))
|
|
408
412
|
continue;
|
|
409
413
|
issues.push(issue);
|
|
410
414
|
queueIssues.push(issue);
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
* cold" via `probeCheckJsGlobalIssues` instead of building anything.
|
|
22
22
|
*/
|
|
23
23
|
import { invokePatchLintCheckJsGrouped, probeCheckJsGlobalIssues, runCheckJsTestFilesGrouped, } from '../core/patch-lint-checkjs.js';
|
|
24
|
-
import { isTestScriptFile, resolvePatchOwnedSysMjs, resolvePatchOwnedTestScripts, } from '../core/patch-lint-ownership.js';
|
|
24
|
+
import { isTestScriptFile, resolvableModulesForTests, resolvePatchOwnedSysMjs, resolvePatchOwnedTestScripts, } from '../core/patch-lint-ownership.js';
|
|
25
25
|
/**
|
|
26
26
|
* Builds the per-run checkJs program controller when `patchLint.checkJs` is
|
|
27
27
|
* enabled, or returns undefined. With `rootScopePatches` set (the
|
|
@@ -69,7 +69,7 @@ export function buildPerRunCheckJs(projectRoot, paths, config, ctx, rootScopePat
|
|
|
69
69
|
const sys = await invokePatchLintCheckJsGrouped(paths.engine, resolvePatchOwnedSysMjs(new Set(), ctx), patchLint, projectRoot, scopedRoots);
|
|
70
70
|
if (!testFilesEnabled)
|
|
71
71
|
return sys;
|
|
72
|
-
const tests = await runCheckJsTestFilesGrouped(paths.engine, resolvePatchOwnedTestScripts(new Set(), ctx), patchLint, projectRoot, scopedRoots);
|
|
72
|
+
const tests = await runCheckJsTestFilesGrouped(paths.engine, resolvePatchOwnedTestScripts(new Set(), ctx), patchLint, projectRoot, scopedRoots, resolvableModulesForTests(ctx));
|
|
73
73
|
const byFile = new Map(sys.byFile);
|
|
74
74
|
for (const [rel, list] of tests.byFile) {
|
|
75
75
|
byFile.set(rel, [...(byFile.get(rel) ?? []), ...list]);
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* none is wanted.
|
|
11
11
|
*/
|
|
12
12
|
import { getPatchSizeThresholds, resolvePatchSizeTier, } from '../core/patch-lint.js';
|
|
13
|
-
import { evaluateLintIgnoreCeilings, } from '../core/patch-lint-ignore.js';
|
|
13
|
+
import { evaluateLintIgnoreCeilings, evaluateLintIgnoreWaivers, } from '../core/patch-lint-ignore.js';
|
|
14
14
|
import { writeJson } from '../utils/fs.js';
|
|
15
15
|
import { info } from '../utils/logger.js';
|
|
16
16
|
const LINT_REPORT_SCHEMA_VERSION = 1;
|
|
@@ -37,6 +37,8 @@ export async function writePerPatchLintReport(reportPath, subset, results, confi
|
|
|
37
37
|
if (!patch || !result)
|
|
38
38
|
continue;
|
|
39
39
|
const tier = resolvePatchSizeTier(result.existingFiles, patch.tier);
|
|
40
|
+
const findings = [...result.rawIssues, ...result.suppressedIssues];
|
|
41
|
+
const measurements = { lines: result.lineCount, files: patch.filesAffected.length };
|
|
40
42
|
patches.push({
|
|
41
43
|
filename: patch.filename,
|
|
42
44
|
name: patch.name,
|
|
@@ -50,7 +52,10 @@ export async function writePerPatchLintReport(reportPath, subset, results, confi
|
|
|
50
52
|
suppressedIssues: result.suppressedIssues,
|
|
51
53
|
lintIgnoreCeilings: result.status === 'skipped'
|
|
52
54
|
? []
|
|
53
|
-
: evaluateLintIgnoreCeilings(patch.lintIgnore,
|
|
55
|
+
: evaluateLintIgnoreCeilings(patch.lintIgnore, findings, measurements),
|
|
56
|
+
lintIgnoreWaivers: result.status === 'skipped'
|
|
57
|
+
? []
|
|
58
|
+
: evaluateLintIgnoreWaivers(patch.lintIgnore, findings, measurements),
|
|
54
59
|
});
|
|
55
60
|
if (result.status === 'skipped')
|
|
56
61
|
skipped++;
|
|
@@ -11,6 +11,7 @@ import { registerFurnace } from './furnace/index.js';
|
|
|
11
11
|
import { registerImport } from './import.js';
|
|
12
12
|
import { registerLint } from './lint.js';
|
|
13
13
|
import { registerPackage } from './package.js';
|
|
14
|
+
import { registerPark } from './park.js';
|
|
14
15
|
import { registerPatch } from './patch/index.js';
|
|
15
16
|
import { registerReExport } from './re-export-register.js';
|
|
16
17
|
import { registerRebase } from './rebase/index.js';
|
|
@@ -47,6 +48,7 @@ export const COMMAND_MANIFEST = [
|
|
|
47
48
|
{ name: 'export', group: 'workflow', register: registerExport },
|
|
48
49
|
{ name: 'export-all', group: 'workflow', register: registerExportAll },
|
|
49
50
|
{ name: 're-export', group: 'workflow', register: registerReExport },
|
|
51
|
+
{ name: 'park', group: 'workflow', register: registerPark },
|
|
50
52
|
{ name: 'patch', group: 'workflow', register: registerPatch },
|
|
51
53
|
{ name: 'rebase', group: 'workflow', register: registerRebase },
|
|
52
54
|
{ name: 'package', group: 'workflow', register: registerPackage },
|