@hominis/fireforge 0.51.0 → 0.53.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 +46 -0
- package/dist/build-info.json +4 -4
- package/dist/src/commands/bootstrap.js +2 -3
- package/dist/src/commands/build-prelock.d.ts +3 -3
- package/dist/src/commands/build-prelock.js +3 -3
- package/dist/src/commands/build.js +4 -0
- package/dist/src/commands/declarations-report.d.ts +9 -3
- package/dist/src/commands/declarations-report.js +18 -11
- package/dist/src/commands/discard.js +3 -3
- package/dist/src/commands/doctor-furnace-config-sync.js +4 -4
- package/dist/src/commands/doctor-furnace.js +5 -5
- package/dist/src/commands/export-adopt-check.d.ts +5 -6
- package/dist/src/commands/export-adopt-check.js +5 -6
- package/dist/src/commands/export-flow.js +6 -9
- package/dist/src/commands/export-staged-registration.d.ts +4 -4
- package/dist/src/commands/export-staged-registration.js +4 -4
- package/dist/src/commands/forward-import-remedy.d.ts +2 -3
- package/dist/src/commands/forward-import-remedy.js +2 -3
- package/dist/src/commands/furnace/create-dry-run.d.ts +2 -2
- package/dist/src/commands/furnace/deploy-fragment-includers.d.ts +2 -2
- package/dist/src/commands/furnace/deploy-fragment-includers.js +2 -2
- package/dist/src/commands/furnace/index.js +2 -2
- package/dist/src/commands/lint.js +4 -4
- package/dist/src/commands/patch/compact.js +1 -1
- package/dist/src/commands/patch/lint-ignore-from.d.ts +3 -4
- package/dist/src/commands/patch/lint-ignore-from.js +3 -4
- package/dist/src/commands/patch/move-files-adopt.d.ts +33 -0
- package/dist/src/commands/patch/move-files-adopt.js +65 -0
- package/dist/src/commands/patch/move-files-create.js +41 -20
- package/dist/src/commands/patch/move-files-into.d.ts +18 -0
- package/dist/src/commands/patch/move-files-into.js +125 -60
- package/dist/src/commands/patch/move-files-retire.d.ts +39 -0
- package/dist/src/commands/patch/move-files-retire.js +91 -0
- package/dist/src/commands/patch/move-files.d.ts +3 -0
- package/dist/src/commands/patch/move-files.js +42 -12
- package/dist/src/commands/patch/move-size-tier.d.ts +27 -0
- package/dist/src/commands/patch/move-size-tier.js +66 -0
- package/dist/src/commands/patch/rename.js +1 -1
- package/dist/src/commands/patch/reorder-projection.d.ts +17 -0
- package/dist/src/commands/patch/reorder-projection.js +49 -0
- package/dist/src/commands/patch/reorder-shift.d.ts +30 -0
- package/dist/src/commands/patch/reorder-shift.js +307 -0
- package/dist/src/commands/patch/reorder.d.ts +9 -15
- package/dist/src/commands/patch/reorder.js +52 -50
- package/dist/src/commands/patch/split-plan.d.ts +31 -3
- package/dist/src/commands/patch/split-plan.js +36 -12
- package/dist/src/commands/patch/split-staged.d.ts +2 -2
- package/dist/src/commands/patch/split-staged.js +2 -2
- package/dist/src/commands/patch/split.js +18 -5
- package/dist/src/commands/patch/staged-dependency-entries.d.ts +9 -1
- package/dist/src/commands/patch/staged-dependency-entries.js +32 -4
- package/dist/src/commands/patch/staged-dependency-from.js +15 -9
- package/dist/src/commands/patch/staged-dependency.js +13 -9
- package/dist/src/commands/patch/tier.js +2 -2
- package/dist/src/commands/patch-rename-map.d.ts +14 -3
- package/dist/src/commands/patch-rename-map.js +21 -3
- package/dist/src/commands/re-export-adjacent.d.ts +4 -5
- package/dist/src/commands/re-export-lint.d.ts +4 -3
- package/dist/src/commands/re-export-lint.js +4 -3
- package/dist/src/commands/re-export-preflight.js +1 -2
- package/dist/src/commands/re-export-purity-scope.d.ts +1 -1
- package/dist/src/commands/re-export-purity-scope.js +13 -7
- package/dist/src/commands/re-export-retired-declarations.d.ts +22 -0
- package/dist/src/commands/re-export-retired-declarations.js +113 -0
- package/dist/src/commands/re-export-single.js +2 -2
- package/dist/src/commands/re-export-stale-registrations.d.ts +27 -6
- package/dist/src/commands/re-export-stale-registrations.js +65 -21
- package/dist/src/commands/re-export.js +4 -0
- package/dist/src/commands/rebase/continue.js +3 -3
- package/dist/src/commands/register.js +5 -4
- package/dist/src/commands/status-output.js +5 -1
- package/dist/src/commands/status.js +1 -1
- package/dist/src/commands/test-build-furnace.d.ts +1 -2
- package/dist/src/commands/test-build-furnace.js +1 -2
- package/dist/src/commands/test-build-phase.d.ts +10 -0
- package/dist/src/commands/test-build-phase.js +51 -16
- package/dist/src/commands/test-control-restore.d.ts +40 -0
- package/dist/src/commands/test-control-restore.js +86 -0
- package/dist/src/commands/test-dispatch.js +83 -13
- package/dist/src/commands/test-multi-verdict.js +1 -0
- package/dist/src/commands/test-orchestrate.d.ts +11 -0
- package/dist/src/commands/test-orchestrate.js +33 -2
- package/dist/src/commands/test-perf-scope.d.ts +1 -1
- package/dist/src/commands/test-perf-scope.js +1 -1
- package/dist/src/commands/test-prelock.d.ts +7 -8
- package/dist/src/commands/test-prelock.js +15 -12
- package/dist/src/commands/test-register.js +2 -2
- package/dist/src/commands/test-results.d.ts +10 -1
- package/dist/src/commands/test-results.js +20 -3
- package/dist/src/commands/test-run.d.ts +6 -0
- package/dist/src/commands/test-run.js +23 -4
- package/dist/src/commands/test-shards.d.ts +7 -3
- package/dist/src/commands/test-shards.js +24 -7
- package/dist/src/commands/test-stale-gate.d.ts +6 -1
- package/dist/src/commands/test-stale-gate.js +47 -2
- package/dist/src/commands/test-verdict.d.ts +5 -3
- package/dist/src/commands/test.js +14 -1
- package/dist/src/commands/token.js +2 -2
- package/dist/src/commands/verify.js +1 -1
- package/dist/src/commands/watch.js +1 -1
- package/dist/src/commands/wire.js +1 -1
- package/dist/src/core/build-audit-resolve.d.ts +4 -5
- package/dist/src/core/build-audit-resolve.js +4 -5
- package/dist/src/core/build-audit-skip.d.ts +2 -2
- package/dist/src/core/build-audit.d.ts +3 -4
- package/dist/src/core/build-baseline-types.d.ts +37 -3
- package/dist/src/core/build-baseline-validate.d.ts +30 -1
- package/dist/src/core/build-baseline-validate.js +134 -33
- package/dist/src/core/build-baseline.d.ts +8 -2
- package/dist/src/core/build-baseline.js +32 -16
- package/dist/src/core/build-jar-escalation.d.ts +4 -4
- package/dist/src/core/build-jar-escalation.js +4 -4
- package/dist/src/core/build-prepare.js +4 -4
- package/dist/src/core/config-validate.js +4 -4
- package/dist/src/core/coverage-extend.d.ts +5 -3
- package/dist/src/core/coverage-extend.js +8 -3
- package/dist/src/core/coverage-records.d.ts +4 -5
- package/dist/src/core/coverage-records.js +4 -5
- package/dist/src/core/destructive.d.ts +2 -2
- package/dist/src/core/destructive.js +2 -2
- package/dist/src/core/discard-baseline.d.ts +2 -2
- package/dist/src/core/discard-baseline.js +3 -3
- package/dist/src/core/engine-session-lock.d.ts +1 -1
- package/dist/src/core/engine-session-lock.js +1 -1
- package/dist/src/core/file-lock-owner.d.ts +5 -5
- package/dist/src/core/file-lock-owner.js +5 -5
- package/dist/src/core/file-lock-queue.js +6 -8
- package/dist/src/core/file-lock-shared.d.ts +4 -4
- package/dist/src/core/file-lock-shared.js +4 -4
- package/dist/src/core/file-lock.js +7 -10
- package/dist/src/core/firefox.js +2 -2
- package/dist/src/core/furnace-apply-overwrite-warn.d.ts +1 -1
- package/dist/src/core/furnace-apply-overwrite-warn.js +1 -1
- package/dist/src/core/furnace-apply.js +3 -3
- package/dist/src/core/furnace-config-validate-exceptions.d.ts +4 -3
- package/dist/src/core/furnace-config-validate-exceptions.js +26 -6
- package/dist/src/core/furnace-operation.js +5 -5
- package/dist/src/core/furnace-registration-jar-inplace.js +5 -6
- package/dist/src/core/furnace-registration-rename.d.ts +5 -5
- package/dist/src/core/furnace-registration-rename.js +5 -5
- package/dist/src/core/furnace-registration-validate.d.ts +2 -1
- package/dist/src/core/furnace-registration-validate.js +2 -1
- package/dist/src/core/furnace-registration.js +2 -2
- package/dist/src/core/furnace-validate-accessibility.d.ts +1 -1
- package/dist/src/core/furnace-validate-accessibility.js +22 -11
- package/dist/src/core/furnace-validate-exceptions.d.ts +2 -1
- package/dist/src/core/furnace-validate-exceptions.js +31 -13
- package/dist/src/core/git-base.js +4 -3
- package/dist/src/core/git-diff.js +1 -1
- package/dist/src/core/git.js +1 -1
- package/dist/src/core/harness-orphans.d.ts +6 -6
- package/dist/src/core/harness-orphans.js +6 -6
- package/dist/src/core/harness-port-reservation.d.ts +60 -0
- package/dist/src/core/harness-port-reservation.js +122 -0
- package/dist/src/core/history-log-file.d.ts +9 -9
- package/dist/src/core/history-log-file.js +15 -15
- package/dist/src/core/history-log.d.ts +3 -3
- package/dist/src/core/history-log.js +3 -3
- package/dist/src/core/launchable-build.d.ts +39 -0
- package/dist/src/core/launchable-build.js +85 -0
- package/dist/src/core/marionette-port.js +1 -3
- package/dist/src/core/moz-manifest-rules.js +3 -2
- package/dist/src/core/negative-control-compiled.d.ts +22 -3
- package/dist/src/core/negative-control-compiled.js +41 -5
- package/dist/src/core/parser-fallback.d.ts +5 -5
- package/dist/src/core/parser-fallback.js +5 -5
- package/dist/src/core/patch-identifier-suggest.d.ts +6 -0
- package/dist/src/core/patch-identifier-suggest.js +11 -4
- package/dist/src/core/patch-lint-checkjs.d.ts +1 -1
- package/dist/src/core/patch-lint-checkjs.js +1 -1
- package/dist/src/core/patch-lint-css.js +2 -2
- package/dist/src/core/patch-lint-file-size.d.ts +5 -7
- package/dist/src/core/patch-lint-file-size.js +5 -7
- package/dist/src/core/patch-lint-ignore.d.ts +3 -3
- package/dist/src/core/patch-lint-ignore.js +3 -3
- package/dist/src/core/patch-lint-prettier.d.ts +4 -6
- package/dist/src/core/patch-lint-prettier.js +4 -6
- package/dist/src/core/patch-lint-projection.d.ts +1 -1
- package/dist/src/core/patch-lint-projection.js +1 -1
- package/dist/src/core/patch-lint-queue-types.d.ts +2 -2
- package/dist/src/core/patch-lint-queue-types.js +2 -2
- package/dist/src/core/patch-manifest-consistency.d.ts +2 -2
- package/dist/src/core/patch-manifest-consistency.js +2 -2
- package/dist/src/core/patch-manifest-io.d.ts +5 -1
- package/dist/src/core/patch-manifest-io.js +12 -10
- package/dist/src/core/patch-manifest-key-order.d.ts +3 -4
- package/dist/src/core/patch-manifest-key-order.js +3 -4
- package/dist/src/core/patch-parse.d.ts +3 -3
- package/dist/src/core/patch-parse.js +3 -3
- package/dist/src/core/patch-policy.d.ts +1 -1
- package/dist/src/core/patch-policy.js +1 -1
- package/dist/src/core/patch-source-metadata.d.ts +1 -1
- package/dist/src/core/patch-source-metadata.js +1 -1
- package/dist/src/core/patch-subset.d.ts +5 -3
- package/dist/src/core/patch-subset.js +8 -7
- package/dist/src/core/patch-transform.d.ts +2 -2
- package/dist/src/core/patch-transform.js +2 -2
- package/dist/src/core/rebase-session.js +4 -6
- package/dist/src/core/register-xpcshell-test.d.ts +28 -7
- package/dist/src/core/register-xpcshell-test.js +76 -27
- package/dist/src/core/run-log.d.ts +8 -8
- package/dist/src/core/run-log.js +8 -8
- package/dist/src/core/status-classify.d.ts +1 -2
- package/dist/src/core/status-classify.js +1 -2
- package/dist/src/core/test-harness-crash.d.ts +5 -3
- package/dist/src/core/test-harness-crash.js +13 -14
- package/dist/src/core/test-harness-infra.d.ts +32 -0
- package/dist/src/core/test-harness-infra.js +69 -0
- package/dist/src/core/test-harness-signature.d.ts +6 -0
- package/dist/src/core/test-run-completeness.d.ts +12 -5
- package/dist/src/core/test-run-completeness.js +15 -8
- package/dist/src/core/test-stale-check.d.ts +48 -1
- package/dist/src/core/test-stale-check.js +97 -2
- package/dist/src/core/test-stall-triage.js +5 -7
- package/dist/src/core/token-category.js +2 -2
- package/dist/src/core/token-inventory.js +3 -3
- package/dist/src/core/token-manager.d.ts +2 -2
- package/dist/src/core/token-manager.js +5 -7
- package/dist/src/core/token-variant.d.ts +6 -6
- package/dist/src/core/token-variant.js +7 -7
- package/dist/src/core/typecheck-shim.d.ts +2 -1
- package/dist/src/core/typecheck-shim.js +29 -5
- package/dist/src/core/wire-destroy.d.ts +1 -1
- package/dist/src/core/wire-destroy.js +1 -1
- package/dist/src/core/wire-dom-fragment.js +1 -1
- package/dist/src/core/wire-init.d.ts +1 -1
- package/dist/src/core/wire-init.js +1 -1
- package/dist/src/core/wire-subscript.d.ts +1 -1
- package/dist/src/core/wire-subscript.js +1 -1
- package/dist/src/core/wire-utils.d.ts +2 -2
- package/dist/src/core/wire-utils.js +3 -3
- package/dist/src/errors/base.d.ts +1 -1
- package/dist/src/errors/base.js +1 -1
- package/dist/src/errors/rebase.d.ts +1 -1
- package/dist/src/errors/rebase.js +1 -1
- package/dist/src/types/commands/options.d.ts +26 -3
- package/dist/src/types/config.d.ts +5 -7
- package/dist/src/types/furnace.d.ts +9 -0
- package/dist/src/utils/options.js +2 -2
- package/dist/src/utils/process.js +2 -2
- package/dist/src/utils/regex.d.ts +2 -2
- package/dist/src/utils/regex.js +3 -3
- package/dist/src/utils/sleep.d.ts +5 -6
- package/dist/src/utils/sleep.js +5 -6
- package/dist/src/utils/validation.js +2 -2
- package/docs/configuration.md +40 -26
- package/docs/exit-codes.md +8 -11
- package/docs/furnace.md +21 -3
- package/docs/lifecycle-invariants.md +36 -45
- package/docs/machine-output.md +43 -22
- package/docs/patch-workflow.md +129 -24
- package/docs/run-logs.md +8 -9
- package/docs/testing.md +118 -44
- package/package.json +1 -1
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
import { FurnaceError } from '../errors/furnace.js';
|
|
12
12
|
import { FURNACE_OPT_IN_CHECKS, } from '../types/furnace.js';
|
|
13
13
|
import { isArray, isObject, isString } from '../utils/validation.js';
|
|
14
|
-
const EXCEPTION_KEYS = new Set(['component', 'check', 'tag', 'reason']);
|
|
14
|
+
const EXCEPTION_KEYS = new Set(['component', 'check', 'tag', 'file', 'reason']);
|
|
15
15
|
function requireNonEmptyString(entry, key, field) {
|
|
16
16
|
const value = entry[key];
|
|
17
17
|
if (!isString(value) || value.trim().length === 0) {
|
|
@@ -20,9 +20,10 @@ function requireNonEmptyString(entry, key, field) {
|
|
|
20
20
|
return value;
|
|
21
21
|
}
|
|
22
22
|
/**
|
|
23
|
-
* Parses `validationExceptions[]`: `{ component, check, tag?, reason }`
|
|
24
|
-
* objects, no other keys, every field a non-empty string,
|
|
25
|
-
*
|
|
23
|
+
* Parses `validationExceptions[]`: `{ component, check, tag?, file?, reason }`
|
|
24
|
+
* objects, no other keys, every field a non-empty string, at most one of
|
|
25
|
+
* `tag` and `file` (a bare file name of the component), and no two entries
|
|
26
|
+
* for the same component, check and scope.
|
|
26
27
|
*/
|
|
27
28
|
export function parseValidationExceptions(raw) {
|
|
28
29
|
if (!isArray(raw)) {
|
|
@@ -46,9 +47,28 @@ export function parseValidationExceptions(raw) {
|
|
|
46
47
|
};
|
|
47
48
|
if (entry['tag'] !== undefined)
|
|
48
49
|
exception.tag = requireNonEmptyString(entry, 'tag', field);
|
|
49
|
-
|
|
50
|
+
if (entry['file'] !== undefined) {
|
|
51
|
+
if (exception.tag !== undefined) {
|
|
52
|
+
throw new FurnaceError(`Furnace config: "${field}" may name a "tag" or a "file", not both`);
|
|
53
|
+
}
|
|
54
|
+
const file = requireNonEmptyString(entry, 'file', field);
|
|
55
|
+
if (/[\\/]/.test(file) || file === '.' || file === '..') {
|
|
56
|
+
throw new FurnaceError(`Furnace config: "${field}.file" must be a file name in the component's directory, not a path`);
|
|
57
|
+
}
|
|
58
|
+
exception.file = file;
|
|
59
|
+
}
|
|
60
|
+
const scope = exception.tag !== undefined
|
|
61
|
+
? `tag:${exception.tag}`
|
|
62
|
+
: exception.file !== undefined
|
|
63
|
+
? `file:${exception.file}`
|
|
64
|
+
: '';
|
|
65
|
+
const identity = `${exception.component}\u0000${exception.check}\u0000${scope}`;
|
|
50
66
|
if (seen.has(identity)) {
|
|
51
|
-
const on = exception.tag !== undefined
|
|
67
|
+
const on = exception.tag !== undefined
|
|
68
|
+
? ` on <${exception.tag}>`
|
|
69
|
+
: exception.file !== undefined
|
|
70
|
+
? ` in ${exception.file}`
|
|
71
|
+
: '';
|
|
52
72
|
throw new FurnaceError(`Furnace config: "${field}" repeats the exception for ${exception.component} [${exception.check}]${on}`);
|
|
53
73
|
}
|
|
54
74
|
seen.add(identity);
|
|
@@ -305,11 +305,11 @@ export async function completeJournalRollback(ctx, journal, error, options) {
|
|
|
305
305
|
export async function forceReleaseFurnaceLocksForActiveOperations() {
|
|
306
306
|
const paths = new Set([...activeOperations.values()].map((op) => getFurnaceLockPath(op.root)));
|
|
307
307
|
for (const lockPath of paths) {
|
|
308
|
-
// Ownership-checked, like every other release path.
|
|
309
|
-
//
|
|
310
|
-
//
|
|
311
|
-
//
|
|
312
|
-
//
|
|
308
|
+
// Ownership-checked, like every other release path. A bare `rm` would
|
|
309
|
+
// delete a lock a different process had acquired in the window between
|
|
310
|
+
// our operation dying and this sweep running. That is the exact race
|
|
311
|
+
// `releaseLock`'s PID verification closes, and the sweeper has no
|
|
312
|
+
// acquisition token to check.
|
|
313
313
|
const removed = await releaseLockIfOwned(lockPath);
|
|
314
314
|
verbose(removed
|
|
315
315
|
? `Removed furnace lock at ${lockPath} during signal teardown`
|
|
@@ -2,10 +2,9 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* In-place edits to a widget's `toolkit/content/jar.mn` block.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* the lines that change: a deleted file's line goes, a renamed component's
|
|
5
|
+
* Dropping every line of the widget and re-adding the live ones would
|
|
6
|
+
* re-sort the whole block and leave the owning patch carrying reorder
|
|
7
|
+
* hunks. These helpers touch only the lines that change: a deleted file's line goes, a renamed component's
|
|
9
8
|
* lines are rewritten where they stand, and anything still missing is left
|
|
10
9
|
* for `addJarMnEntries` to insert.
|
|
11
10
|
*/
|
|
@@ -15,8 +14,8 @@ import { escapeRegex } from '../utils/regex.js';
|
|
|
15
14
|
import { JAR_MN } from './furnace-constants.js';
|
|
16
15
|
/**
|
|
17
16
|
* The file a widget jar.mn line registers: the `(widgets/<tag>/<file>)`
|
|
18
|
-
* source mapping, or the
|
|
19
|
-
*
|
|
17
|
+
* source mapping, or the `content/global/elements/<tag>.<ext>` target of a
|
|
18
|
+
* line that carries no source mapping. Undefined when the line is
|
|
20
19
|
* not one of this tag's.
|
|
21
20
|
*/
|
|
22
21
|
function registeredFile(line, tagName) {
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* In-place rename of a custom element registration in customElements.js.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
4
|
+
* Removing the old entry and adding a new one would, on a registration
|
|
5
|
+
* list the remove path cannot see, leave the old entry beside the new one,
|
|
6
|
+
* and the new entry would lack the project marker every sibling line
|
|
7
|
+
* carries (`// <MARKER>:`), so the next lint would flag it as an unmarked
|
|
8
|
+
* engine edit. Rewriting the tag and module
|
|
9
9
|
* literals inside the existing entry keeps the entry's position, layout and
|
|
10
10
|
* trailing marker comments exactly as they were.
|
|
11
11
|
*/
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* In-place rename of a custom element registration in customElements.js.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
5
|
+
* Removing the old entry and adding a new one would, on a registration
|
|
6
|
+
* list the remove path cannot see, leave the old entry beside the new one,
|
|
7
|
+
* and the new entry would lack the project marker every sibling line
|
|
8
|
+
* carries (`// <MARKER>:`), so the next lint would flag it as an unmarked
|
|
9
|
+
* engine edit. Rewriting the tag and module
|
|
10
10
|
* literals inside the existing entry keeps the entry's position, layout and
|
|
11
11
|
* trailing marker comments exactly as they were.
|
|
12
12
|
*/
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Shared validation for furnace custom element registration placement.
|
|
3
|
-
*
|
|
3
|
+
* Shared by the AST registration editor and the registration validator to
|
|
4
|
+
* avoid duplicating logic.
|
|
4
5
|
*/
|
|
5
6
|
/**
|
|
6
7
|
* Regex for valid custom element tag names. A valid name is lowercase, starts
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
// SPDX-License-Identifier: EUPL-1.2
|
|
2
2
|
/**
|
|
3
3
|
* Shared validation for furnace custom element registration placement.
|
|
4
|
-
*
|
|
4
|
+
* Shared by the AST registration editor and the registration validator to
|
|
5
|
+
* avoid duplicating logic.
|
|
5
6
|
*/
|
|
6
7
|
import { FurnaceError } from '../errors/furnace.js';
|
|
7
8
|
import { escapeRegex, stripJsComments } from '../utils/regex.js';
|
|
@@ -252,8 +252,8 @@ export async function removeJarMnEntries(engineDir, tagName) {
|
|
|
252
252
|
// line the component registered is removed regardless of the target
|
|
253
253
|
// basename. A helper `.mjs` whose name does not start with the tag (e.g.
|
|
254
254
|
// a renamed `foo-utils.mjs`) otherwise survives the remove pass and leaves
|
|
255
|
-
// a stale registration that breaks packaging. The
|
|
256
|
-
//
|
|
255
|
+
// a stale registration that breaks packaging. The target-path match is
|
|
256
|
+
// kept as an OR for lines that carry no source mapping.
|
|
257
257
|
// Word boundaries keep "moz-card" from matching "moz-card-group".
|
|
258
258
|
const sourcePattern = new RegExp(`\\(widgets/${escapeRegex(tagName)}/`);
|
|
259
259
|
const legacyTargetPattern = new RegExp(`content/global/elements/${escapeRegex(tagName)}\\.`);
|
|
@@ -34,6 +34,6 @@ export declare function validateAccessibility(componentDir: string, tagName: str
|
|
|
34
34
|
* `title=`, or `tooltiptext=` attribute when constructing DOM in JS.
|
|
35
35
|
*
|
|
36
36
|
* A file-wide `// furnace-ignore: hardcoded-text` comment suppresses all
|
|
37
|
-
* findings (matches the pre-existing escape hatch).
|
|
37
|
+
* findings in that file (matches the pre-existing escape hatch).
|
|
38
38
|
*/
|
|
39
39
|
export declare function containsHardcodedTemplateText(content: string): boolean;
|
|
@@ -11,8 +11,9 @@ import { collectLitTemplateRegions, parseTemplateAttributes } from './lit-templa
|
|
|
11
11
|
* findings do not depend on which of the component's modules it lives in.
|
|
12
12
|
* Returns undefined when the host module is missing.
|
|
13
13
|
*/
|
|
14
|
-
async function
|
|
15
|
-
const
|
|
14
|
+
async function readComposedComponentModules(componentDir, tagName) {
|
|
15
|
+
const hostName = `${tagName}.mjs`;
|
|
16
|
+
const hostPath = join(componentDir, hostName);
|
|
16
17
|
if (!(await pathExists(hostPath)))
|
|
17
18
|
return undefined;
|
|
18
19
|
const entries = await readdir(componentDir).catch(() => []);
|
|
@@ -21,10 +22,11 @@ async function readComposedComponentSource(componentDir, tagName) {
|
|
|
21
22
|
name.endsWith('.mjs') &&
|
|
22
23
|
!/\.(test|spec|stories)\./.test(name))
|
|
23
24
|
.sort();
|
|
24
|
-
const
|
|
25
|
-
for (const name of companions)
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
const modules = [{ name: hostName, content: await readText(hostPath) }];
|
|
26
|
+
for (const name of companions) {
|
|
27
|
+
modules.push({ name, content: await readText(join(componentDir, name)) });
|
|
28
|
+
}
|
|
29
|
+
return modules;
|
|
28
30
|
}
|
|
29
31
|
/**
|
|
30
32
|
* Validates accessibility patterns in a component's composed source: its
|
|
@@ -44,9 +46,10 @@ async function readComposedComponentSource(componentDir, tagName) {
|
|
|
44
46
|
* adds the icon-only `<moz-button>` naming check.
|
|
45
47
|
*/
|
|
46
48
|
export async function validateAccessibility(componentDir, tagName, customConfig, optInChecks = []) {
|
|
47
|
-
const
|
|
48
|
-
if (
|
|
49
|
+
const modules = await readComposedComponentModules(componentDir, tagName);
|
|
50
|
+
if (modules === undefined)
|
|
49
51
|
return [];
|
|
52
|
+
const content = modules.map((module) => module.content).join('\n');
|
|
50
53
|
const issues = [];
|
|
51
54
|
if (!hasAriaRole(content) && hasGenericInteractiveElement(content)) {
|
|
52
55
|
issues.push(createIssue(tagName, 'warning', 'no-aria-role', 'Generic interactive markup has no native semantics. Prefer native elements, or add role= when native markup cannot provide the semantics.'));
|
|
@@ -72,8 +75,16 @@ export async function validateAccessibility(componentDir, tagName, customConfig,
|
|
|
72
75
|
tags: syntheticTags,
|
|
73
76
|
});
|
|
74
77
|
}
|
|
75
|
-
|
|
76
|
-
|
|
78
|
+
// Judged per module, so the finding names its files and an exception can
|
|
79
|
+
// accept one module (a vendored bundle) without muting the others.
|
|
80
|
+
const textFiles = modules
|
|
81
|
+
.filter((module) => containsHardcodedTemplateText(module.content))
|
|
82
|
+
.map((module) => module.name);
|
|
83
|
+
if (textFiles.length > 0) {
|
|
84
|
+
issues.push({
|
|
85
|
+
...createIssue(tagName, 'warning', 'hardcoded-text', `Possible hardcoded string found in ${textFiles.join(', ')}. Use data-l10n-id for localization.`),
|
|
86
|
+
files: textFiles,
|
|
87
|
+
});
|
|
77
88
|
}
|
|
78
89
|
const isInteractive = hasClick || hasKeyboardHandler;
|
|
79
90
|
if (isInteractive && !hasDelegatesFocusEnabled(content)) {
|
|
@@ -337,7 +348,7 @@ function isWithinLocalizedElement(content, matchIndex) {
|
|
|
337
348
|
* `title=`, or `tooltiptext=` attribute when constructing DOM in JS.
|
|
338
349
|
*
|
|
339
350
|
* A file-wide `// furnace-ignore: hardcoded-text` comment suppresses all
|
|
340
|
-
* findings (matches the pre-existing escape hatch).
|
|
351
|
+
* findings in that file (matches the pre-existing escape hatch).
|
|
341
352
|
*/
|
|
342
353
|
export function containsHardcodedTemplateText(content) {
|
|
343
354
|
if (/furnace-ignore:\s*hardcoded-text/.test(content)) {
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
* Applies `furnace.json#validationExceptions[]` to a `furnace validate`
|
|
3
3
|
* result. An exception suppresses every finding of its check on its
|
|
4
4
|
* component, or with `tag`, a finding whose element tags it (and its
|
|
5
|
-
* sibling tag entries) cover
|
|
5
|
+
* sibling tag entries) cover, or with `file`, a finding whose module files
|
|
6
|
+
* it (and its sibling file entries) cover. One that suppresses nothing is stale: the
|
|
6
7
|
* finding was fixed (or renamed) and the review record outlived it, so it
|
|
7
8
|
* is reported as a warning naming the entry to delete, which keeps the
|
|
8
9
|
* list honest under `--max-warnings 0`.
|
|
@@ -6,19 +6,34 @@ export const EXCEPTION_ON_ERROR_CHECK = 'validation-exception-on-error';
|
|
|
6
6
|
function matches(exception, issue) {
|
|
7
7
|
return exception.component === issue.component && exception.check === issue.check;
|
|
8
8
|
}
|
|
9
|
+
/** An exception's narrowing: the finding field it reads and its value. */
|
|
10
|
+
function scopeOf(entry) {
|
|
11
|
+
if (entry.tag !== undefined)
|
|
12
|
+
return { key: 'tags', value: entry.tag };
|
|
13
|
+
if (entry.file !== undefined)
|
|
14
|
+
return { key: 'files', value: entry.file };
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
9
17
|
/**
|
|
10
|
-
* The exceptions that suppress a warning: an
|
|
11
|
-
* ones that together cover every tag the finding names.
|
|
18
|
+
* The exceptions that suppress a warning: an unscoped one alone, or scoped
|
|
19
|
+
* ones that together cover every tag (or every file) the finding names.
|
|
12
20
|
*/
|
|
13
21
|
function coveringExceptions(issue, candidates) {
|
|
14
|
-
const
|
|
15
|
-
if (
|
|
16
|
-
return [
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
+
const unscoped = candidates.find((entry) => scopeOf(entry) === undefined);
|
|
23
|
+
if (unscoped !== undefined)
|
|
24
|
+
return [unscoped];
|
|
25
|
+
for (const key of ['tags', 'files']) {
|
|
26
|
+
const named = issue[key] ?? [];
|
|
27
|
+
if (named.length === 0)
|
|
28
|
+
continue;
|
|
29
|
+
const covering = named.map((value) => candidates.find((entry) => {
|
|
30
|
+
const scope = scopeOf(entry);
|
|
31
|
+
return scope?.key === key && scope.value === value;
|
|
32
|
+
}));
|
|
33
|
+
if (covering.every((entry) => entry !== undefined))
|
|
34
|
+
return [...new Set(covering)];
|
|
35
|
+
}
|
|
36
|
+
return [];
|
|
22
37
|
}
|
|
23
38
|
/**
|
|
24
39
|
* Splits findings into counted and excepted, and reports stale exceptions.
|
|
@@ -47,11 +62,14 @@ export function applyValidationExceptions(issues, exceptions, judged) {
|
|
|
47
62
|
}
|
|
48
63
|
const covering = coveringExceptions(issue, candidates);
|
|
49
64
|
if (covering.length === 0) {
|
|
50
|
-
// A
|
|
51
|
-
// element still carries the finding, beside one it does
|
|
65
|
+
// A scoped exception that covers some of the tags (or files) is not
|
|
66
|
+
// stale: its element still carries the finding, beside one it does
|
|
67
|
+
// not cover.
|
|
52
68
|
for (const entry of candidates) {
|
|
53
|
-
|
|
69
|
+
const scope = scopeOf(entry);
|
|
70
|
+
if (scope !== undefined && issue[scope.key]?.includes(scope.value) === true) {
|
|
54
71
|
used.add(entry);
|
|
72
|
+
}
|
|
55
73
|
}
|
|
56
74
|
outcome.issues.push(issue);
|
|
57
75
|
continue;
|
|
@@ -45,9 +45,10 @@ export const GIT_ADD_CHUNK_TIMEOUT_MS = resolveTimeoutFromEnv(GIT_ADD_CHUNK_TIME
|
|
|
45
45
|
/**
|
|
46
46
|
* In-flight or settled successful `git` availability probe.
|
|
47
47
|
*
|
|
48
|
-
* The probe spawns `which git` (or `where` on Windows)
|
|
49
|
-
*
|
|
50
|
-
*
|
|
48
|
+
* The probe spawns `which git` (or `where` on Windows); running it before
|
|
49
|
+
* every command would mean dozens of extra spawns per `fireforge export`, so
|
|
50
|
+
* the result is shared by every git wrapper in this layer. Only success is
|
|
51
|
+
* cached: a rejection
|
|
51
52
|
* clears the slot so a run that installs git mid-flight (or a test that flips
|
|
52
53
|
* the probe) sees the new answer instead of a sticky refusal.
|
|
53
54
|
*/
|
|
@@ -521,7 +521,7 @@ async function generateBinaryFilePatchWithConfig(repoDir, filePath, gitConfigArg
|
|
|
521
521
|
* "absent", and this branch only runs for files untracked in HEAD). A prior
|
|
522
522
|
* stage-0 entry is re-pointed at its recorded mode and blob. Unmerged entries
|
|
523
523
|
* (stages 1 to 3) cannot be rebuilt through `--cacheinfo`. That degenerate
|
|
524
|
-
* state
|
|
524
|
+
* state falls back to the reset-to-HEAD restore rather than corrupting the
|
|
525
525
|
* conflict.
|
|
526
526
|
* @param repoDir - Repository directory
|
|
527
527
|
* @param filePath - File path (relative to repo root)
|
package/dist/src/core/git.js
CHANGED
|
@@ -46,7 +46,7 @@ const GIT_ADD_ENV = { GIT_INDEX_THREADS: '0' };
|
|
|
46
46
|
* AbortError when the signal fires, so both shapes have to be recognised.
|
|
47
47
|
*/
|
|
48
48
|
function isTimeoutError(error) {
|
|
49
|
-
// `exec`
|
|
49
|
+
// `exec` rejects with a typed ExecTimeoutError when its `timeout`
|
|
50
50
|
// option fires (the raw AbortError check below is kept for defence in
|
|
51
51
|
// depth against other abort paths).
|
|
52
52
|
if (error instanceof ExecTimeoutError)
|
|
@@ -45,12 +45,12 @@ export interface OrphanedHarnessProcess {
|
|
|
45
45
|
* True when `command` IS a harness helper: its executable is a helper
|
|
46
46
|
* binary, or an interpreter whose first argument is a helper script.
|
|
47
47
|
*
|
|
48
|
-
* Structural on purpose.
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
48
|
+
* Structural on purpose. Matching the helper names anywhere in the command
|
|
49
|
+
* line is right for the httpd (`xpcshell -g … -f …/server.js`) and wrong for
|
|
50
|
+
* everything that merely mentions such a path: an editor opened on
|
|
51
|
+
* `server.js`, a shell whose command text quotes it, a `grep` over the
|
|
52
|
+
* objdir. Report-only, that would print a misleading `kill`; under the reap
|
|
53
|
+
* posture it would terminate the operator's shell.
|
|
54
54
|
*/
|
|
55
55
|
export declare function matchesHarnessHelper(command: string): boolean;
|
|
56
56
|
/** Objdir provenance: an explicit objdir path, a `obj-…` path segment, or `_tests/`. */
|
|
@@ -55,12 +55,12 @@ function basename(token) {
|
|
|
55
55
|
* True when `command` IS a harness helper: its executable is a helper
|
|
56
56
|
* binary, or an interpreter whose first argument is a helper script.
|
|
57
57
|
*
|
|
58
|
-
* Structural on purpose.
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
58
|
+
* Structural on purpose. Matching the helper names anywhere in the command
|
|
59
|
+
* line is right for the httpd (`xpcshell -g … -f …/server.js`) and wrong for
|
|
60
|
+
* everything that merely mentions such a path: an editor opened on
|
|
61
|
+
* `server.js`, a shell whose command text quotes it, a `grep` over the
|
|
62
|
+
* objdir. Report-only, that would print a misleading `kill`; under the reap
|
|
63
|
+
* posture it would terminate the operator's shell.
|
|
64
64
|
*/
|
|
65
65
|
export function matchesHarnessHelper(command) {
|
|
66
66
|
const [exe, first] = command.trim().split(/\s+/);
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mochitest server ports for `fireforge test --jobs <n>`: placeholder
|
|
3
|
+
* listeners that push every parallel harness instance onto its own ports,
|
|
4
|
+
* and the parse of the ports each instance actually took.
|
|
5
|
+
*
|
|
6
|
+
* Desktop `mach mochitest` has no `--http-port`, `--ssl-port` or
|
|
7
|
+
* `--websocket-port`. Its `startServers` (`testing/mochitest/runtests.py`)
|
|
8
|
+
* bind-probes the HTTP server, ssltunnel and WebSocket ports (8888, 4443,
|
|
9
|
+
* 9988) and moves only a busy one to a free port; see
|
|
10
|
+
* `harness-port-fallback.ts`. Instances started at the same moment all
|
|
11
|
+
* pass that probe on a free 8888, then race to bind it: the losers die with
|
|
12
|
+
* `could not start server on port 8888` before any test runs. Holding the
|
|
13
|
+
* three ports for the whole parallel run makes every instance's probe fail,
|
|
14
|
+
* so each one falls back to an OS-assigned port of its own.
|
|
15
|
+
*/
|
|
16
|
+
/** Which mochitest server a port belongs to, as the verdict line names it. */
|
|
17
|
+
export type HarnessServerPortKey = 'http' | 'ssl' | 'ws';
|
|
18
|
+
/** Placeholder listeners held for a parallel run. */
|
|
19
|
+
export interface HarnessPortReservation {
|
|
20
|
+
/** Ports this process now holds. A port that was already busy is not listed. */
|
|
21
|
+
held: number[];
|
|
22
|
+
/** Closes every placeholder. Safe to call more than once. */
|
|
23
|
+
release(): Promise<void>;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Holds a placeholder listener on `127.0.0.1` on each mochitest server
|
|
27
|
+
* port, so the harness's busy-port probe moves every instance elsewhere.
|
|
28
|
+
* A port that cannot be bound is skipped, not refused: whatever holds it
|
|
29
|
+
* already makes the probe fail, which is all the placeholder is for (the
|
|
30
|
+
* preflight has already ruled out this checkout's own stale httpd).
|
|
31
|
+
*
|
|
32
|
+
* @param ports - Ports to hold (default: {@link HARNESS_SERVER_PORTS})
|
|
33
|
+
*/
|
|
34
|
+
export declare function reserveHarnessServerPorts(ports?: readonly number[]): Promise<HarnessPortReservation>;
|
|
35
|
+
/** The server ports one harness invocation reported, by server. */
|
|
36
|
+
export type HarnessServerPorts = Partial<Record<HarnessServerPortKey, number>>;
|
|
37
|
+
/**
|
|
38
|
+
* Records the port one output line reports, if it reports one: the
|
|
39
|
+
* fallback port of a "falling back" line, or the port itself otherwise.
|
|
40
|
+
*
|
|
41
|
+
* @param line - One line of harness output
|
|
42
|
+
* @param into - Collected ports; a later line for the same server wins
|
|
43
|
+
*/
|
|
44
|
+
export declare function recordHarnessServerPortLine(line: string, into: HarnessServerPorts): void;
|
|
45
|
+
/** Collects server ports from streamed output chunks. */
|
|
46
|
+
export interface HarnessPortTracker {
|
|
47
|
+
onChunk(stream: 'stdout' | 'stderr', chunk: string): void;
|
|
48
|
+
/** The ports seen so far, including an unterminated last line. */
|
|
49
|
+
ports(): HarnessServerPorts;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* A tracker over the full output stream. The ports are printed at harness
|
|
53
|
+
* start, so the capped capture tail a long run keeps would lose them.
|
|
54
|
+
*/
|
|
55
|
+
export declare function createHarnessPortTracker(): HarnessPortTracker;
|
|
56
|
+
/**
|
|
57
|
+
* The `http:N,ssl:N,ws:N` value of a `ports=` key, in probe order, or
|
|
58
|
+
* undefined when no port is known.
|
|
59
|
+
*/
|
|
60
|
+
export declare function formatHarnessServerPorts(ports: HarnessServerPorts | undefined): string | undefined;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* Mochitest server ports for `fireforge test --jobs <n>`: placeholder
|
|
4
|
+
* listeners that push every parallel harness instance onto its own ports,
|
|
5
|
+
* and the parse of the ports each instance actually took.
|
|
6
|
+
*
|
|
7
|
+
* Desktop `mach mochitest` has no `--http-port`, `--ssl-port` or
|
|
8
|
+
* `--websocket-port`. Its `startServers` (`testing/mochitest/runtests.py`)
|
|
9
|
+
* bind-probes the HTTP server, ssltunnel and WebSocket ports (8888, 4443,
|
|
10
|
+
* 9988) and moves only a busy one to a free port; see
|
|
11
|
+
* `harness-port-fallback.ts`. Instances started at the same moment all
|
|
12
|
+
* pass that probe on a free 8888, then race to bind it: the losers die with
|
|
13
|
+
* `could not start server on port 8888` before any test runs. Holding the
|
|
14
|
+
* three ports for the whole parallel run makes every instance's probe fail,
|
|
15
|
+
* so each one falls back to an OS-assigned port of its own.
|
|
16
|
+
*/
|
|
17
|
+
import { createServer } from 'node:net';
|
|
18
|
+
/**
|
|
19
|
+
* The ports `startServers` probes, in the order it probes them. Desktop
|
|
20
|
+
* mochitest cannot configure them, so the engine defaults are the ports.
|
|
21
|
+
*/
|
|
22
|
+
const HARNESS_SERVER_PORTS = [
|
|
23
|
+
{ key: 'http', port: 8888, label: 'HTTP test server' },
|
|
24
|
+
{ key: 'ssl', port: 4443, label: 'ssltunnel' },
|
|
25
|
+
{ key: 'ws', port: 9988, label: 'WebSocket server' },
|
|
26
|
+
];
|
|
27
|
+
/** Listens on `127.0.0.1:port`, or resolves undefined when the bind fails. */
|
|
28
|
+
async function holdPort(port) {
|
|
29
|
+
return new Promise((resolve) => {
|
|
30
|
+
const server = createServer((socket) => {
|
|
31
|
+
// Nothing should connect: the harness only probes with a bind. Anyone
|
|
32
|
+
// who does gets a closed socket instead of a hang.
|
|
33
|
+
socket.destroy();
|
|
34
|
+
});
|
|
35
|
+
server.once('error', () => {
|
|
36
|
+
resolve(undefined);
|
|
37
|
+
});
|
|
38
|
+
server.listen({ port, host: '127.0.0.1', exclusive: true }, () => {
|
|
39
|
+
server.unref();
|
|
40
|
+
resolve(server);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Holds a placeholder listener on `127.0.0.1` on each mochitest server
|
|
46
|
+
* port, so the harness's busy-port probe moves every instance elsewhere.
|
|
47
|
+
* A port that cannot be bound is skipped, not refused: whatever holds it
|
|
48
|
+
* already makes the probe fail, which is all the placeholder is for (the
|
|
49
|
+
* preflight has already ruled out this checkout's own stale httpd).
|
|
50
|
+
*
|
|
51
|
+
* @param ports - Ports to hold (default: {@link HARNESS_SERVER_PORTS})
|
|
52
|
+
*/
|
|
53
|
+
export async function reserveHarnessServerPorts(ports = HARNESS_SERVER_PORTS.map((spec) => spec.port)) {
|
|
54
|
+
const servers = (await Promise.all(ports.map(holdPort))).filter((server) => server !== undefined);
|
|
55
|
+
const held = servers.map((server) => {
|
|
56
|
+
const address = server.address();
|
|
57
|
+
return typeof address === 'object' && address !== null ? address.port : 0;
|
|
58
|
+
});
|
|
59
|
+
let released = false;
|
|
60
|
+
return {
|
|
61
|
+
held,
|
|
62
|
+
async release() {
|
|
63
|
+
if (released)
|
|
64
|
+
return;
|
|
65
|
+
released = true;
|
|
66
|
+
await Promise.all(servers.map((server) => new Promise((resolve) => {
|
|
67
|
+
server.close(() => {
|
|
68
|
+
resolve();
|
|
69
|
+
});
|
|
70
|
+
})));
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
const PORT_LINE_PATTERN = /\b(HTTP test server|ssltunnel|WebSocket server) port (\d+)(?: already in use, falling back to port (\d+))?/;
|
|
75
|
+
/**
|
|
76
|
+
* Records the port one output line reports, if it reports one: the
|
|
77
|
+
* fallback port of a "falling back" line, or the port itself otherwise.
|
|
78
|
+
*
|
|
79
|
+
* @param line - One line of harness output
|
|
80
|
+
* @param into - Collected ports; a later line for the same server wins
|
|
81
|
+
*/
|
|
82
|
+
export function recordHarnessServerPortLine(line, into) {
|
|
83
|
+
const match = PORT_LINE_PATTERN.exec(line);
|
|
84
|
+
if (match === null)
|
|
85
|
+
return;
|
|
86
|
+
const spec = HARNESS_SERVER_PORTS.find((entry) => entry.label === match[1]);
|
|
87
|
+
const port = Number.parseInt(match[3] ?? match[2] ?? '', 10);
|
|
88
|
+
if (spec !== undefined && Number.isInteger(port))
|
|
89
|
+
into[spec.key] = port;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* A tracker over the full output stream. The ports are printed at harness
|
|
93
|
+
* start, so the capped capture tail a long run keeps would lose them.
|
|
94
|
+
*/
|
|
95
|
+
export function createHarnessPortTracker() {
|
|
96
|
+
const collected = {};
|
|
97
|
+
const pending = { stdout: '', stderr: '' };
|
|
98
|
+
return {
|
|
99
|
+
onChunk(stream, chunk) {
|
|
100
|
+
const lines = `${pending[stream]}${chunk}`.split(/\r?\n/);
|
|
101
|
+
pending[stream] = lines.pop() ?? '';
|
|
102
|
+
for (const line of lines)
|
|
103
|
+
recordHarnessServerPortLine(line, collected);
|
|
104
|
+
},
|
|
105
|
+
ports() {
|
|
106
|
+
const result = { ...collected };
|
|
107
|
+
recordHarnessServerPortLine(pending.stdout, result);
|
|
108
|
+
recordHarnessServerPortLine(pending.stderr, result);
|
|
109
|
+
return result;
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* The `http:N,ssl:N,ws:N` value of a `ports=` key, in probe order, or
|
|
115
|
+
* undefined when no port is known.
|
|
116
|
+
*/
|
|
117
|
+
export function formatHarnessServerPorts(ports) {
|
|
118
|
+
if (ports === undefined)
|
|
119
|
+
return undefined;
|
|
120
|
+
const parts = HARNESS_SERVER_PORTS.flatMap(({ key }) => ports[key] === undefined ? [] : [`${key}:${ports[key]}`]);
|
|
121
|
+
return parts.length > 0 ? parts.join(',') : undefined;
|
|
122
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** Filename of
|
|
1
|
+
/** Filename of a history log inside `patches/`, which is moved out of the queue. */
|
|
2
2
|
export declare const LEGACY_HISTORY_LOG_FILENAME = ".fireforge-history.jsonl";
|
|
3
3
|
/** Default history log location, relative to the project root. */
|
|
4
4
|
export declare const DEFAULT_HISTORY_LOG_PATH: string;
|
|
@@ -29,30 +29,30 @@ export declare function historyRecordIdentity(): HistoryRecordIdentity;
|
|
|
29
29
|
*/
|
|
30
30
|
export declare function resolveHistoryLogPath(projectRoot: string): Promise<string>;
|
|
31
31
|
/**
|
|
32
|
-
* Moves a history log
|
|
32
|
+
* Moves a history log found at the `patches/` path to `target`.
|
|
33
33
|
*
|
|
34
34
|
* Records are never dropped, whatever else is happening:
|
|
35
|
-
* - The
|
|
35
|
+
* - The in-queue file is first claimed by renaming it aside, so of two
|
|
36
36
|
* commands migrating at once exactly one does the work and neither
|
|
37
37
|
* duplicates it.
|
|
38
38
|
* - When `target` does not exist yet (the first run), the claimed file is
|
|
39
39
|
* hard-linked into place, which cannot overwrite a log another command
|
|
40
40
|
* created in the meantime, and keeps a large log a constant-time move.
|
|
41
|
-
* - When both exist (
|
|
42
|
-
*
|
|
41
|
+
* - When both exist (a log reappeared at the `patches/` path after a
|
|
42
|
+
* migration, or `history.path` changed), the in-queue records are
|
|
43
43
|
* appended to the target in one write. They then follow newer records;
|
|
44
44
|
* every record carries its own `ts`.
|
|
45
45
|
*
|
|
46
46
|
* A crash between the claim and the move leaves the records in a
|
|
47
|
-
* `.migrating-*` file next to the
|
|
47
|
+
* `.migrating-*` file next to the in-queue path, never deleted.
|
|
48
48
|
*
|
|
49
|
-
* @param patchesDir - The patches directory that may hold
|
|
49
|
+
* @param patchesDir - The patches directory that may hold an in-queue log
|
|
50
50
|
* @param target - Absolute path of the history log
|
|
51
51
|
*/
|
|
52
52
|
export declare function migrateLegacyHistoryLog(patchesDir: string, target: string): Promise<void>;
|
|
53
53
|
/**
|
|
54
|
-
* Appends one serialized record to the project's history log,
|
|
55
|
-
*
|
|
54
|
+
* Appends one serialized record to the project's history log, first moving
|
|
55
|
+
* a log found inside `patches/`.
|
|
56
56
|
*
|
|
57
57
|
* @param patchesDir - The project's `patches/` directory; the project root is
|
|
58
58
|
* its parent, as every caller derives it from `getProjectPaths`
|