@hominis/fireforge 0.40.0 → 0.42.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 +85 -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/branding.d.ts +16 -0
- package/dist/src/core/branding.js +18 -2
- 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-mozconfig.js +9 -1
- 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
|
@@ -16,9 +16,58 @@ export function updateBrowserChromeTestContent(content, oldName, newName, binary
|
|
|
16
16
|
const newUnderscored = newName.replace(/-/g, '_');
|
|
17
17
|
const oldTestStem = deriveTestStem(oldName, binaryName);
|
|
18
18
|
const newTestStem = deriveTestStem(newName, binaryName);
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
19
|
+
// ONE pass over the content, not four chained `.replace()` calls.
|
|
20
|
+
//
|
|
21
|
+
// Chaining let each rule see the previous rule's output. Renaming
|
|
22
|
+
// `acme-widget` to `acme-widget-v2` rewrote `test_acme_widget_defined` to
|
|
23
|
+
// `test_acme_widget_v2_defined` via the underscored form, and then the stem
|
|
24
|
+
// rule matched the `acme_widget` still inside that result and produced
|
|
25
|
+
// `test_acme_widget_v2_v2_defined`. A single alternation with a callback
|
|
26
|
+
// cannot re-enter its own output.
|
|
27
|
+
//
|
|
28
|
+
// Ordering is longest-first, because JS alternation takes the first
|
|
29
|
+
// alternative that matches at a given position.
|
|
30
|
+
const alternatives = [
|
|
31
|
+
// The stem is a short bare fragment (`moz-` and binary prefixes stripped,
|
|
32
|
+
// hyphens underscored) — `panel` for `moz-panel`. Matching it loose, even
|
|
33
|
+
// with hyphen guards, rewrote unrelated underscore-delimited identifiers
|
|
34
|
+
// such as `test_panel_group_integration`, because `_` must stay a legal
|
|
35
|
+
// neighbour for the generated name to match at all. The scaffold emits the
|
|
36
|
+
// stem in exactly one shape, so anchor to that shape instead of guessing
|
|
37
|
+
// from boundaries — with its own guards, so the anchored shape cannot
|
|
38
|
+
// match inside a longer identifier (`mytest_panel_defined_extra`).
|
|
39
|
+
{
|
|
40
|
+
pattern: `(?<![\\w-])test_${escapeRegex(oldTestStem)}_defined(?![\\w-])`,
|
|
41
|
+
to: `test_${newTestStem}_defined`,
|
|
42
|
+
},
|
|
43
|
+
// Word-boundary-aware, matching `furnace/rename.ts` and
|
|
44
|
+
// `rename-xpcshell.ts`: without the guards a rename of `moz-panel` rewrote
|
|
45
|
+
// `moz-panel-group`. `_` stays a legal neighbour for the underscored form
|
|
46
|
+
// because it is deliberately embedded in generated identifiers; `-` does
|
|
47
|
+
// not.
|
|
48
|
+
//
|
|
49
|
+
// Skipped when the underscored form IS the tag name (a hyphen-less
|
|
50
|
+
// component): both rules then match the same text and this one would win
|
|
51
|
+
// on order, substituting `newUnderscored` where the tag `newName` was
|
|
52
|
+
// meant. The stem rule still covers the one generated identifier shape.
|
|
53
|
+
...(oldUnderscored === oldName
|
|
54
|
+
? []
|
|
55
|
+
: [
|
|
56
|
+
{
|
|
57
|
+
pattern: `(?<![A-Za-z0-9-])${escapeRegex(oldUnderscored)}(?![A-Za-z0-9-])`,
|
|
58
|
+
to: newUnderscored,
|
|
59
|
+
},
|
|
60
|
+
]),
|
|
61
|
+
{ pattern: `(?<![\\w-])${escapeRegex(oldName)}(?![\\w-])`, to: newName },
|
|
62
|
+
{ pattern: `\\b${escapeRegex(oldClassName)}\\b`, to: newClassName },
|
|
63
|
+
];
|
|
64
|
+
// Each alternative is its own capture group and the replacement is picked by
|
|
65
|
+
// the group that matched — not by matched TEXT, which is ambiguous whenever
|
|
66
|
+
// two rules can produce the same match with different substitutions.
|
|
67
|
+
const combined = new RegExp(alternatives.map(({ pattern }) => `(${pattern})`).join('|'), 'g');
|
|
68
|
+
return content.replace(combined, (matched, ...args) => {
|
|
69
|
+
const groups = args.slice(0, alternatives.length);
|
|
70
|
+
const index = groups.findIndex((group) => group !== undefined);
|
|
71
|
+
return alternatives[index]?.to ?? matched;
|
|
72
|
+
});
|
|
24
73
|
}
|
|
@@ -28,17 +28,10 @@ async function promptAddComponents(components, tracked, projectRoot) {
|
|
|
28
28
|
}
|
|
29
29
|
const selected = await multiselect({
|
|
30
30
|
message: 'Select components to add as stock',
|
|
31
|
-
options: untrackedComponents.map((c) => {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
if (c.hasFTL)
|
|
36
|
-
features.push('FTL');
|
|
37
|
-
if (c.isRegistered)
|
|
38
|
-
features.push('registered');
|
|
39
|
-
const label = features.length > 0 ? `${c.tagName} — ${features.join(', ')}` : c.tagName;
|
|
40
|
-
return { value: c.tagName, label };
|
|
41
|
-
}),
|
|
31
|
+
options: untrackedComponents.map((c) => ({
|
|
32
|
+
value: c.tagName,
|
|
33
|
+
label: `${c.tagName}${formatComponentFeatures(c)}`,
|
|
34
|
+
})),
|
|
42
35
|
});
|
|
43
36
|
if (isCancel(selected)) {
|
|
44
37
|
cancel('Cancelled');
|
|
@@ -110,6 +103,22 @@ async function persistStockComponents(projectRoot, names) {
|
|
|
110
103
|
}
|
|
111
104
|
});
|
|
112
105
|
}
|
|
106
|
+
/**
|
|
107
|
+
* Renders a component's discovered capabilities as a display suffix.
|
|
108
|
+
* Written twice before 0.41.0 — once for the interactive multiselect labels
|
|
109
|
+
* and once for the report rows — which is part of what pinned
|
|
110
|
+
* `furnaceScanCommand` at complexity 30/30.
|
|
111
|
+
*/
|
|
112
|
+
function formatComponentFeatures(component) {
|
|
113
|
+
const features = [];
|
|
114
|
+
if (component.hasCSS)
|
|
115
|
+
features.push('CSS');
|
|
116
|
+
if (component.hasFTL)
|
|
117
|
+
features.push('FTL');
|
|
118
|
+
if (component.isRegistered)
|
|
119
|
+
features.push('registered');
|
|
120
|
+
return features.length > 0 ? ` — ${features.join(', ')}` : '';
|
|
121
|
+
}
|
|
113
122
|
/**
|
|
114
123
|
* Runs the furnace scan command to discover MozLitElement components.
|
|
115
124
|
* @param projectRoot - Root directory of the project
|
|
@@ -153,33 +162,15 @@ export async function furnaceScanCommand(projectRoot, options = {}) {
|
|
|
153
162
|
tracked.set(name, 'custom');
|
|
154
163
|
}
|
|
155
164
|
}
|
|
156
|
-
//
|
|
165
|
+
// Partition once and reuse: the tracked/untracked split was computed twice
|
|
166
|
+
// (imperatively here, declaratively again for --track) before 0.41.0.
|
|
167
|
+
const untrackedComponentList = components.filter((c) => !tracked.has(c.tagName));
|
|
168
|
+
const untrackedCount = untrackedComponentList.length;
|
|
169
|
+
const trackedCount = components.length - untrackedCount;
|
|
157
170
|
for (const component of components) {
|
|
158
|
-
const features = [];
|
|
159
|
-
if (component.hasCSS)
|
|
160
|
-
features.push('CSS');
|
|
161
|
-
if (component.hasFTL)
|
|
162
|
-
features.push('FTL');
|
|
163
|
-
if (component.isRegistered)
|
|
164
|
-
features.push('registered');
|
|
165
|
-
let line = component.tagName;
|
|
166
|
-
if (features.length > 0) {
|
|
167
|
-
line += ` — ${features.join(', ')}`;
|
|
168
|
-
}
|
|
169
171
|
const type = tracked.get(component.tagName);
|
|
170
|
-
|
|
171
|
-
line += ` [${type}]`;
|
|
172
|
-
}
|
|
173
|
-
info(line);
|
|
174
|
-
}
|
|
175
|
-
// Summary
|
|
176
|
-
let trackedCount = 0;
|
|
177
|
-
for (const component of components) {
|
|
178
|
-
if (tracked.has(component.tagName)) {
|
|
179
|
-
trackedCount++;
|
|
180
|
-
}
|
|
172
|
+
info(`${component.tagName}${formatComponentFeatures(component)}${type ? ` [${type}]` : ''}`);
|
|
181
173
|
}
|
|
182
|
-
const untrackedCount = components.length - trackedCount;
|
|
183
174
|
note(`Total: ${components.length} Tracked: ${trackedCount} Untracked: ${untrackedCount}`, 'Summary');
|
|
184
175
|
// --track: persist the discovered untracked inventory into the `stock`
|
|
185
176
|
// section without prompting (works non-interactively). Without it, scan
|
|
@@ -191,7 +182,7 @@ export async function furnaceScanCommand(projectRoot, options = {}) {
|
|
|
191
182
|
outro('Scan complete');
|
|
192
183
|
return;
|
|
193
184
|
}
|
|
194
|
-
const untrackedNames =
|
|
185
|
+
const untrackedNames = untrackedComponentList.map((c) => c.tagName);
|
|
195
186
|
await persistStockComponents(projectRoot, untrackedNames);
|
|
196
187
|
success(`Tracked ${untrackedNames.length} component${untrackedNames.length === 1 ? '' : 's'} in the stock section of furnace.json`);
|
|
197
188
|
outro('Scan complete');
|
|
@@ -157,42 +157,44 @@ export async function furnaceStatusCommand(projectRoot, name) {
|
|
|
157
157
|
// hints, so report them separately rather than collapsing into a single
|
|
158
158
|
// "something is off" message.
|
|
159
159
|
if (await pathExists(paths.engine)) {
|
|
160
|
-
let
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
for (const [
|
|
178
|
-
|
|
160
|
+
// Held in an object rather than two `let`s: the scanner below mutates
|
|
161
|
+
// them from inside a closure, and TS's control-flow analysis does not
|
|
162
|
+
// propagate that through the call — it would narrow both to the literal
|
|
163
|
+
// `false` and flag every later read as always-falsy.
|
|
164
|
+
const drift = { workspaceChanged: false, engineDrifted: false };
|
|
165
|
+
/**
|
|
166
|
+
* Scans one component family for workspace or engine drift.
|
|
167
|
+
*
|
|
168
|
+
* The engine probe sits behind `else if` deliberately: a component whose
|
|
169
|
+
* workspace already changed does not need an engine comparison to decide
|
|
170
|
+
* what `status` reports, and the probe is the expensive half (it re-hashes
|
|
171
|
+
* deployed files). The consequence is that engine drift can go UNREPORTED
|
|
172
|
+
* for a component that also has workspace changes — the summary says
|
|
173
|
+
* "workspace changed", not both. Preserved verbatim from the two
|
|
174
|
+
* hand-written copies this replaced.
|
|
175
|
+
*/
|
|
176
|
+
const scanFamily = async (entries, baseDir, kind, probeEngineDrift) => {
|
|
177
|
+
for (const [componentName, componentConfig] of entries) {
|
|
178
|
+
if (drift.workspaceChanged && drift.engineDrifted)
|
|
179
|
+
return;
|
|
180
|
+
const componentDir = join(baseDir, componentName);
|
|
179
181
|
if (!(await pathExists(componentDir)))
|
|
180
182
|
continue;
|
|
181
|
-
const previous = extractComponentChecksums(state.appliedChecksums,
|
|
183
|
+
const previous = extractComponentChecksums(state.appliedChecksums, kind, componentName);
|
|
182
184
|
if (await hasComponentChanged(componentDir, previous)) {
|
|
183
|
-
workspaceChanged = true;
|
|
185
|
+
drift.workspaceChanged = true;
|
|
184
186
|
}
|
|
185
|
-
else if (await
|
|
186
|
-
engineDrifted = true;
|
|
187
|
+
else if (await probeEngineDrift(componentName, componentDir, componentConfig)) {
|
|
188
|
+
drift.engineDrifted = true;
|
|
187
189
|
}
|
|
188
|
-
if (workspaceChanged && engineDrifted)
|
|
189
|
-
break;
|
|
190
190
|
}
|
|
191
|
-
}
|
|
192
|
-
|
|
191
|
+
};
|
|
192
|
+
await scanFamily(Object.entries(config.overrides), furnacePaths.overridesDir, 'override', (_name, componentDir, cfg) => hasOverrideEngineDrift(paths.engine, componentDir, cfg, ftlDir));
|
|
193
|
+
await scanFamily(Object.entries(config.custom), furnacePaths.customDir, 'custom', (name, componentDir, cfg) => hasCustomEngineDrift(projectRoot, name, componentDir, cfg, ftlDir));
|
|
194
|
+
if (drift.workspaceChanged) {
|
|
193
195
|
warn('Components have been modified since last apply. Run `fireforge build` or `fireforge furnace apply`.');
|
|
194
196
|
}
|
|
195
|
-
if (engineDrifted) {
|
|
197
|
+
if (drift.engineDrifted) {
|
|
196
198
|
warn('Engine drift detected since last apply (reset/download/manual edit). Run `fireforge furnace apply` to re-deploy.');
|
|
197
199
|
}
|
|
198
200
|
}
|
|
@@ -6,6 +6,7 @@ import { furnaceConfigExists, getFurnacePaths, loadFurnaceConfig, } from '../../
|
|
|
6
6
|
import { addCustomElementRegistration, addJarMnEntries, pruneStaleJarMnEntries, } from '../../core/furnace-registration.js';
|
|
7
7
|
import { validateAllComponents, validateComponent } from '../../core/furnace-validate.js';
|
|
8
8
|
import { FurnaceError } from '../../errors/furnace.js';
|
|
9
|
+
import { toError } from '../../utils/errors.js';
|
|
9
10
|
import { pathExists } from '../../utils/fs.js';
|
|
10
11
|
import { info, intro, note, outro, success, warn } from '../../utils/logger.js';
|
|
11
12
|
import { displayValidationIssues } from './validation-output.js';
|
|
@@ -15,6 +16,7 @@ const FIXABLE_CHECKS = new Set([
|
|
|
15
16
|
'missing-jar-mn-css',
|
|
16
17
|
'wrong-registration-pattern',
|
|
17
18
|
'stale-jar-registration',
|
|
19
|
+
'missing-custom-element-registration',
|
|
18
20
|
]);
|
|
19
21
|
/**
|
|
20
22
|
* Auto-fixes registration issues that have deterministic solutions.
|
|
@@ -53,7 +55,7 @@ async function autoFixIssues(projectRoot, issues) {
|
|
|
53
55
|
}
|
|
54
56
|
}
|
|
55
57
|
catch (err) {
|
|
56
|
-
warn(`Could not prune stale jar.mn lines: ${err
|
|
58
|
+
warn(`Could not prune stale jar.mn lines: ${toError(err).message}`);
|
|
57
59
|
}
|
|
58
60
|
}
|
|
59
61
|
// Fix jar.mn entries
|
|
@@ -72,21 +74,26 @@ async function autoFixIssues(projectRoot, issues) {
|
|
|
72
74
|
}
|
|
73
75
|
}
|
|
74
76
|
catch (err) {
|
|
75
|
-
warn(`Could not fix jar.mn for ${componentName}: ${err
|
|
77
|
+
warn(`Could not fix jar.mn for ${componentName}: ${toError(err).message}`);
|
|
76
78
|
}
|
|
77
79
|
}
|
|
78
|
-
//
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
//
|
|
87
|
-
//
|
|
88
|
-
|
|
89
|
-
|
|
80
|
+
// `wrong-registration-pattern` is deliberately NOT auto-fixed: it means the
|
|
81
|
+
// component IS registered but in the wrong block, and moving code between
|
|
82
|
+
// blocks is too risky to do unattended. Only truly missing registrations are
|
|
83
|
+
// repaired, below.
|
|
84
|
+
// Repair components missing from customElements.js entirely (reported by
|
|
85
|
+
// validate as `missing-custom-element-registration`).
|
|
86
|
+
//
|
|
87
|
+
// Scoped to the components named in `issues`. Until 0.41.0 this iterated
|
|
88
|
+
// every entry in `config.custom`, so `furnace validate <one-component> --fix`
|
|
89
|
+
// wrote customElements.js registrations for EVERY custom component — outside
|
|
90
|
+
// the issue list it was handed, and invisibly, since `fixed` was never
|
|
91
|
+
// incremented here. The block's own comment already claimed this scoping;
|
|
92
|
+
// the code did not implement it.
|
|
93
|
+
const componentsWithIssues = new Set(issues.map((issue) => issue.component));
|
|
94
|
+
for (const componentName of componentsWithIssues) {
|
|
95
|
+
const customConfig = config.custom[componentName];
|
|
96
|
+
if (!customConfig?.register)
|
|
90
97
|
continue;
|
|
91
98
|
const componentDir = join(furnacePaths.customDir, componentName);
|
|
92
99
|
if (!(await pathExists(componentDir)))
|
|
@@ -97,13 +104,13 @@ async function autoFixIssues(projectRoot, issues) {
|
|
|
97
104
|
continue;
|
|
98
105
|
const modulePath = `chrome://global/content/elements/${componentName}.mjs`;
|
|
99
106
|
try {
|
|
107
|
+
// Idempotent: returns without error when already registered, so this
|
|
108
|
+
// stays a no-op for components whose registration is already correct.
|
|
100
109
|
await addCustomElementRegistration(engineDir, componentName, modulePath);
|
|
101
|
-
// addCustomElementRegistration is idempotent — it returns without error
|
|
102
|
-
// if already registered. We only count it as fixed if a matching issue
|
|
103
|
-
// existed in the input.
|
|
104
110
|
}
|
|
105
111
|
catch {
|
|
106
|
-
//
|
|
112
|
+
// Best-effort repair — the re-validation pass at the call site reports
|
|
113
|
+
// whatever is still outstanding.
|
|
107
114
|
}
|
|
108
115
|
}
|
|
109
116
|
return fixed;
|
|
@@ -5,12 +5,13 @@ import { Command } from 'commander';
|
|
|
5
5
|
import { getProjectPaths, loadConfig, loadState, updateState } from '../core/config.js';
|
|
6
6
|
import { getHead } from '../core/git.js';
|
|
7
7
|
import { getDirtyFiles } from '../core/git-status.js';
|
|
8
|
-
import { applyPatchesWithContinue, countPatches,
|
|
8
|
+
import { applyPatchesWithContinue, countPatches, createPatchedContentContext, discoverPatches, extractAffectedFiles, matchesUntilFilename, PatchError, } from '../core/patch-apply.js';
|
|
9
9
|
import { checkVersionCompatibility, loadPatchesManifest, validatePatchesManifestConsistency, validatePatchIntegrity, } from '../core/patch-manifest.js';
|
|
10
10
|
import { getPatchSourceVersion } from '../core/patch-source-metadata.js';
|
|
11
11
|
import { warnIfStaticComponentsStale } from '../core/test-stale-check.js';
|
|
12
12
|
import { GeneralError } from '../errors/base.js';
|
|
13
|
-
import {
|
|
13
|
+
import { mapWithConcurrency } from '../utils/concurrency.js';
|
|
14
|
+
import { getNodeErrorCode, toError } from '../utils/errors.js';
|
|
14
15
|
import { pathExists, readText } from '../utils/fs.js';
|
|
15
16
|
import { error, info, intro, isCancel, outro, spinner, success, verbose, warn, } from '../utils/logger.js';
|
|
16
17
|
import { pickDefined } from '../utils/options.js';
|
|
@@ -24,8 +25,8 @@ import { pickDefined } from '../utils/options.js';
|
|
|
24
25
|
*/
|
|
25
26
|
const SAFE_IO_FALLBACK_CODES = new Set(['ENOENT', 'EACCES', 'EPERM', 'EISDIR', 'EBUSY']);
|
|
26
27
|
function isSafeIoFallback(error) {
|
|
27
|
-
const code = error
|
|
28
|
-
return
|
|
28
|
+
const code = getNodeErrorCode(error);
|
|
29
|
+
return code !== undefined && SAFE_IO_FALLBACK_CODES.has(code);
|
|
29
30
|
}
|
|
30
31
|
/** Concurrency bound for per-file classification (each call spawns git). */
|
|
31
32
|
const UNMANAGED_CLASSIFY_CONCURRENCY = 8;
|
|
@@ -33,7 +34,7 @@ async function getUnmanagedDirtyFiles(engineDir, patchesDir, dirtyFiles) {
|
|
|
33
34
|
// One manifest+patch-discovery load for the whole batch (the per-call
|
|
34
35
|
// computePatchedContent re-read everything for every file), and bounded
|
|
35
36
|
// concurrency instead of an unbounded Promise.all over git spawns.
|
|
36
|
-
const computeExpected = await
|
|
37
|
+
const { computePatched: computeExpected } = await createPatchedContentContext(patchesDir, engineDir);
|
|
37
38
|
const classifications = await mapWithConcurrency(dirtyFiles, UNMANAGED_CLASSIFY_CONCURRENCY, async (file) => {
|
|
38
39
|
try {
|
|
39
40
|
const [expected, exists] = await Promise.all([
|
|
@@ -62,21 +63,6 @@ async function getUnmanagedDirtyFiles(engineDir, patchesDir, dirtyFiles) {
|
|
|
62
63
|
});
|
|
63
64
|
return classifications.filter((file) => file !== null).sort();
|
|
64
65
|
}
|
|
65
|
-
/** Maps items with at most `limit` in-flight promises. Preserves order. */
|
|
66
|
-
async function mapWithConcurrency(items, limit, fn) {
|
|
67
|
-
const results = new Array(items.length);
|
|
68
|
-
let next = 0;
|
|
69
|
-
const workers = Array.from({ length: Math.min(limit, items.length) }, async () => {
|
|
70
|
-
for (;;) {
|
|
71
|
-
const index = next++;
|
|
72
|
-
if (index >= items.length)
|
|
73
|
-
return;
|
|
74
|
-
results[index] = await fn(items[index]);
|
|
75
|
-
}
|
|
76
|
-
});
|
|
77
|
-
await Promise.all(workers);
|
|
78
|
-
return results;
|
|
79
|
-
}
|
|
80
66
|
function reportForcedOverwriteRisk(unmanagedDirtyFiles) {
|
|
81
67
|
warn(`--force will overwrite ${unmanagedDirtyFiles.length} unmanaged change${unmanagedDirtyFiles.length === 1 ? '' : 's'} in patch-touched file${unmanagedDirtyFiles.length === 1 ? '' : 's'}:`);
|
|
82
68
|
for (const file of unmanagedDirtyFiles) {
|
|
@@ -3,14 +3,14 @@ import { join } from 'node:path';
|
|
|
3
3
|
import { getProjectPaths, loadConfig } from '../core/config.js';
|
|
4
4
|
import { getDiffForFilesAgainstHead } from '../core/git-diff.js';
|
|
5
5
|
import { buildPerPatchLintCacheKey, getCachedPerPatchLintIssues, getPerPatchLintCacheHeadSha, loadPerPatchLintCache, savePerPatchLintCache, setCachedPerPatchLintIssues, } from '../core/lint-cache.js';
|
|
6
|
-
import { buildPatchQueueContext, countNonBinaryDiffLines, lintExportedPatch, lintPatchQueue, resolvePatchSizeTier, } from '../core/patch-lint.js';
|
|
7
|
-
import { invokePatchLintCheckJsGrouped, runCheckJsTestFilesGrouped, } from '../core/patch-lint-checkjs.js';
|
|
8
|
-
import { isTestScriptFile, resolvePatchOwnedSysMjs, resolvePatchOwnedTestScripts, } from '../core/patch-lint-ownership.js';
|
|
6
|
+
import { buildPatchQueueContext, countNonBinaryDiffLines, formatPatchLintIssue, lintExportedPatch, lintPatchQueue, resolvePatchSizeTier, } from '../core/patch-lint.js';
|
|
9
7
|
import { loadPatchesManifest } from '../core/patch-manifest.js';
|
|
10
8
|
import { evaluatePatchPolicy } from '../core/patch-policy.js';
|
|
11
9
|
import { GeneralError } from '../errors/base.js';
|
|
10
|
+
import { mapWithConcurrency } from '../utils/concurrency.js';
|
|
12
11
|
import { pathExists } from '../utils/fs.js';
|
|
13
12
|
import { info, outro, success, warn } from '../utils/logger.js';
|
|
13
|
+
import { buildPerRunCheckJs } from './lint-per-run-checkjs.js';
|
|
14
14
|
import { writePerPatchLintReport } from './lint-report.js';
|
|
15
15
|
function buildPerPatchMaxWarningsMessage(count, maxWarnings, linted) {
|
|
16
16
|
return (`Patch lint found ${count} warning(s) across ${linted} patch(es), exceeding --max-warnings ${maxWarnings}.` +
|
|
@@ -34,11 +34,8 @@ function emitTierNotice(filename, files, tier) {
|
|
|
34
34
|
*/
|
|
35
35
|
async function lintQueuedPatch(patch, lintCtx) {
|
|
36
36
|
const { projectRoot, paths, config, ctx, cache, engineHeadSha } = lintCtx;
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
if (await pathExists(join(paths.engine, f)))
|
|
40
|
-
existing.push(f);
|
|
41
|
-
}
|
|
37
|
+
const present = await mapWithConcurrency(patch.filesAffected, 8, (f) => pathExists(join(paths.engine, f)));
|
|
38
|
+
const existing = patch.filesAffected.filter((_, index) => present[index] === true);
|
|
42
39
|
if (existing.length === 0) {
|
|
43
40
|
return {
|
|
44
41
|
status: 'skipped',
|
|
@@ -237,11 +234,11 @@ function reportPerPatchOutcome(issues, linted, skipped, options) {
|
|
|
237
234
|
const warnings = issues.filter((i) => i.severity === 'warning');
|
|
238
235
|
const notices = issues.filter((i) => i.severity === 'notice');
|
|
239
236
|
for (const issue of notices)
|
|
240
|
-
info(`NOTICE
|
|
237
|
+
info(`NOTICE ${formatPatchLintIssue(issue)}`);
|
|
241
238
|
for (const issue of warnings)
|
|
242
|
-
warn(
|
|
239
|
+
warn(formatPatchLintIssue(issue));
|
|
243
240
|
for (const issue of errors)
|
|
244
|
-
warn(`ERROR
|
|
241
|
+
warn(`ERROR ${formatPatchLintIssue(issue)}`);
|
|
245
242
|
info(`\nLint (per-patch over ${linted} patch(es)): ${errors.length} error(s), ${warnings.length} warning(s)`);
|
|
246
243
|
if (errors.length > 0) {
|
|
247
244
|
outro('Lint failed');
|
|
@@ -305,53 +302,6 @@ function selectPatchSubset(manifest, requested) {
|
|
|
305
302
|
}
|
|
306
303
|
return selected;
|
|
307
304
|
}
|
|
308
|
-
/**
|
|
309
|
-
* Builds the per-run checkJs program controller when `patchLint.checkJs` is
|
|
310
|
-
* enabled, or returns undefined. The program is built lazily on the first
|
|
311
|
-
* cache miss (so an all-warm run never pays for it) and reused for every
|
|
312
|
-
* subsequent patch in the run.
|
|
313
|
-
*/
|
|
314
|
-
function buildPerRunCheckJs(projectRoot, paths, config, ctx) {
|
|
315
|
-
const patchLint = config.patchLint;
|
|
316
|
-
if (!patchLint?.checkJs)
|
|
317
|
-
return undefined;
|
|
318
|
-
const testFilesEnabled = patchLint.checkJsTestFiles === true;
|
|
319
|
-
const ownedByPatch = new Map();
|
|
320
|
-
for (const entry of ctx.entries) {
|
|
321
|
-
const owned = new Set();
|
|
322
|
-
for (const f of entry.newFiles.keys()) {
|
|
323
|
-
if (f.endsWith('.sys.mjs'))
|
|
324
|
-
owned.add(f);
|
|
325
|
-
else if (testFilesEnabled && isTestScriptFile(f))
|
|
326
|
-
owned.add(f);
|
|
327
|
-
}
|
|
328
|
-
if (owned.size > 0)
|
|
329
|
-
ownedByPatch.set(entry.filename, owned);
|
|
330
|
-
}
|
|
331
|
-
// One build for the whole run: the queue-wide `.sys.mjs` program plus —
|
|
332
|
-
// when `patchLint.checkJsTestFiles` is on (FORGE G5) — one small
|
|
333
|
-
// script-scope program per patch-owned test file, merged by file.
|
|
334
|
-
const buildAll = async () => {
|
|
335
|
-
const sys = await invokePatchLintCheckJsGrouped(paths.engine, resolvePatchOwnedSysMjs(new Set(), ctx), patchLint, projectRoot);
|
|
336
|
-
if (!testFilesEnabled)
|
|
337
|
-
return sys;
|
|
338
|
-
const tests = await runCheckJsTestFilesGrouped(paths.engine, resolvePatchOwnedTestScripts(new Set(), ctx), patchLint, projectRoot);
|
|
339
|
-
const byFile = new Map(sys.byFile);
|
|
340
|
-
for (const [rel, list] of tests.byFile) {
|
|
341
|
-
byFile.set(rel, [...(byFile.get(rel) ?? []), ...list]);
|
|
342
|
-
}
|
|
343
|
-
return { byFile, global: [...sys.global, ...tests.global] };
|
|
344
|
-
};
|
|
345
|
-
// Memoise the *promise*, not the resolved value: under the bounded pool
|
|
346
|
-
// several patches can reach `getGrouped` before the first build resolves, and
|
|
347
|
-
// `??=` on the promise (a synchronous expression) guarantees a single build.
|
|
348
|
-
let groupedPromise;
|
|
349
|
-
return {
|
|
350
|
-
ownedByPatch,
|
|
351
|
-
getGrouped: () => (groupedPromise ??= buildAll()),
|
|
352
|
-
getGlobal: async () => (await (groupedPromise ??= buildAll())).global,
|
|
353
|
-
};
|
|
354
|
-
}
|
|
355
305
|
/**
|
|
356
306
|
* Lints each patch in the queue as its own isolated diff, honouring
|
|
357
307
|
* per-patch `lintIgnore` entries. Cross-patch rules still run once over
|
|
@@ -400,7 +350,10 @@ export async function lintPerPatch(projectRoot, paths, options = {}) {
|
|
|
400
350
|
severity: issue.severity,
|
|
401
351
|
});
|
|
402
352
|
}
|
|
403
|
-
|
|
353
|
+
// With `--patches`, the checkJs program roots at only the subset's owned
|
|
354
|
+
// files — the full queue stays resolvable, so a cold subset run costs the
|
|
355
|
+
// subset's import closure instead of the whole queue (FORGE J1c).
|
|
356
|
+
const checkJs = buildPerRunCheckJs(projectRoot, paths, config, ctx, isSubset ? subsetNames : undefined);
|
|
404
357
|
// Lint patches concurrently, then apply every side effect in patch order so
|
|
405
358
|
// the issue rows, the run-level checkJs errors, and the saved cache are
|
|
406
359
|
// identical to a serial run.
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-run checkJs program controller, shared by `lint --per-patch` and
|
|
3
|
+
* `re-export` (FORGE J1).
|
|
4
|
+
*
|
|
5
|
+
* The dominant fixed cost of patch lint is one `ts.createProgram` over the
|
|
6
|
+
* queue's patch-owned `.sys.mjs` files (~37 s on a 290-patch consumer
|
|
7
|
+
* queue). This controller builds that program at most ONCE per command
|
|
8
|
+
* invocation (lazily, promise-memoised) and lets callers slice per-patch
|
|
9
|
+
* findings out of the grouped result. Three levers keep the cost paid only
|
|
10
|
+
* when it buys something:
|
|
11
|
+
*
|
|
12
|
+
* - **Lazy build**: nothing is built until the first cache miss asks.
|
|
13
|
+
* - **Root scoping** (`rootScopePatches`): with `--patches <subset>` the
|
|
14
|
+
* program's roots are only the subset's owned files; the full queue
|
|
15
|
+
* stays resolvable, so cross-patch imports type-check identically while
|
|
16
|
+
* unrelated files are never parsed (FORGE J1c).
|
|
17
|
+
* - **Warm-run probe**: run-level ("global") checkJs findings come only
|
|
18
|
+
* from a missing `typescript` package or an unreadable shim — never
|
|
19
|
+
* from the built program — so an all-cache-hit run satisfies "warm
|
|
20
|
+
* never reports less than cold" (FORGE F5) via
|
|
21
|
+
* `probeCheckJsGlobalIssues` instead of building anything.
|
|
22
|
+
*/
|
|
23
|
+
import type { getProjectPaths, loadConfig } from '../core/config.js';
|
|
24
|
+
import { type GroupedCheckJsResult } from '../core/patch-lint-checkjs.js';
|
|
25
|
+
import type { PatchQueueContext } from '../core/patch-lint-cross.js';
|
|
26
|
+
import type { PatchLintIssue } from '../types/commands/index.js';
|
|
27
|
+
/**
|
|
28
|
+
* Queue-wide checkJs program built once per run and sliced per patch, so a
|
|
29
|
+
* single type regression surfaces once against its owning patch instead of
|
|
30
|
+
* being duplicated for every patch in the queue.
|
|
31
|
+
*/
|
|
32
|
+
export interface PerRunCheckJs {
|
|
33
|
+
/** Patch filename → the checkJs-relevant files that patch creates. */
|
|
34
|
+
ownedByPatch: Map<string, Set<string>>;
|
|
35
|
+
/** Every checkJs-relevant file in the queue (sys + test scripts) — the
|
|
36
|
+
* resolution universe the program can see. Callers use it to detect
|
|
37
|
+
* files the hoisted program has never heard of (fresh `--scan`
|
|
38
|
+
* adoptions) and fall back to a per-patch build. */
|
|
39
|
+
resolutionSet: ReadonlySet<string>;
|
|
40
|
+
/** Builds (once, lazily on first cache miss) and returns the grouped run.
|
|
41
|
+
* Promise-memoised so the bounded per-patch pool builds the program exactly
|
|
42
|
+
* once even when several patches reach it concurrently. */
|
|
43
|
+
getGrouped: () => Promise<GroupedCheckJsResult>;
|
|
44
|
+
/** Run-level checkJs errors (e.g. TypeScript missing). When the program
|
|
45
|
+
* was never built (all-warm run) this probes the two global failure
|
|
46
|
+
* sources directly instead of building it — FORGE F5 holds because the
|
|
47
|
+
* built program never contributes globals of its own. */
|
|
48
|
+
getGlobal: () => Promise<PatchLintIssue[]>;
|
|
49
|
+
/** The byFile slices for `files` (globals deliberately EXCLUDED — they
|
|
50
|
+
* are run-level, never cached, and emitted once per invocation via
|
|
51
|
+
* `getGlobal`). Feed to `LintExportedPatchOptions.precomputedCheckJs`. */
|
|
52
|
+
sliceFor: (files: ReadonlySet<string>) => Promise<PatchLintIssue[]>;
|
|
53
|
+
/** True when this file kind participates in the run's checkJs surface
|
|
54
|
+
* (`.sys.mjs`, plus test scripts when `checkJsTestFiles` is on). */
|
|
55
|
+
isRelevant: (file: string) => boolean;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Builds the per-run checkJs program controller when `patchLint.checkJs` is
|
|
59
|
+
* enabled, or returns undefined. With `rootScopePatches` set (the
|
|
60
|
+
* `--patches` subset, by patch filename), the program roots at only the
|
|
61
|
+
* subset's owned files and the test-file pass runs only the subset's own
|
|
62
|
+
* scripts (head.js discovery still spans the queue).
|
|
63
|
+
*/
|
|
64
|
+
export declare function buildPerRunCheckJs(projectRoot: string, paths: ReturnType<typeof getProjectPaths>, config: Awaited<ReturnType<typeof loadConfig>>, ctx: PatchQueueContext, rootScopePatches?: ReadonlySet<string>): PerRunCheckJs | undefined;
|