@hominis/fireforge 0.19.3 → 0.19.5

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.
Files changed (57) hide show
  1. package/README.md +1 -3
  2. package/dist/src/commands/bootstrap-checks.js +1 -1
  3. package/dist/src/commands/doctor-check-core.d.ts +101 -0
  4. package/dist/src/commands/doctor-check-core.js +32 -0
  5. package/dist/src/commands/doctor-furnace-manifest-sync.d.ts +1 -1
  6. package/dist/src/commands/doctor-furnace-manifest-sync.js +1 -1
  7. package/dist/src/commands/doctor-furnace.d.ts +1 -1
  8. package/dist/src/commands/doctor-furnace.js +1 -1
  9. package/dist/src/commands/doctor-working-tree.d.ts +1 -1
  10. package/dist/src/commands/doctor-working-tree.js +1 -1
  11. package/dist/src/commands/doctor.d.ts +1 -107
  12. package/dist/src/commands/doctor.js +1 -29
  13. package/dist/src/commands/furnace/index.d.ts +0 -17
  14. package/dist/src/commands/furnace/index.js +0 -1
  15. package/dist/src/commands/furnace/validate.d.ts +2 -3
  16. package/dist/src/commands/patch/index.d.ts +0 -6
  17. package/dist/src/commands/patch/index.js +0 -6
  18. package/dist/src/commands/test.js +16 -8
  19. package/dist/src/commands/wire.js +34 -32
  20. package/dist/src/core/config.d.ts +2 -2
  21. package/dist/src/core/config.js +2 -2
  22. package/dist/src/core/furnace-apply-helpers.js +2 -2
  23. package/dist/src/core/furnace-config-array-utils.d.ts +11 -0
  24. package/dist/src/core/furnace-config-array-utils.js +27 -0
  25. package/dist/src/core/furnace-config-custom.js +1 -1
  26. package/dist/src/core/furnace-config-tokens.js +1 -1
  27. package/dist/src/core/furnace-config.d.ts +0 -6
  28. package/dist/src/core/furnace-config.js +2 -14
  29. package/dist/src/core/furnace-refresh.d.ts +0 -4
  30. package/dist/src/core/furnace-registration-ast.d.ts +0 -1
  31. package/dist/src/core/furnace-registration-ast.js +0 -1
  32. package/dist/src/core/furnace-registration.d.ts +2 -3
  33. package/dist/src/core/furnace-registration.js +2 -3
  34. package/dist/src/core/git-file-ops.d.ts +0 -6
  35. package/dist/src/core/git-file-ops.js +0 -8
  36. package/dist/src/core/git-status.d.ts +0 -14
  37. package/dist/src/core/git-status.js +0 -24
  38. package/dist/src/core/git.d.ts +0 -1
  39. package/dist/src/core/mach.d.ts +2 -2
  40. package/dist/src/core/marionette-port.d.ts +16 -6
  41. package/dist/src/core/marionette-port.js +32 -6
  42. package/dist/src/core/patch-apply.d.ts +2 -2
  43. package/dist/src/core/patch-apply.js +2 -2
  44. package/dist/src/core/patch-lint-reexports.d.ts +1 -4
  45. package/dist/src/core/patch-lint-reexports.js +0 -3
  46. package/dist/src/core/patch-manifest.d.ts +1 -2
  47. package/dist/src/core/patch-manifest.js +1 -1
  48. package/dist/src/core/signal-critical.d.ts +0 -11
  49. package/dist/src/core/signal-critical.js +0 -15
  50. package/dist/src/core/wire-targets.d.ts +4 -4
  51. package/dist/src/core/wire-targets.js +4 -4
  52. package/dist/src/types/commands/options.d.ts +6 -5
  53. package/dist/src/utils/logger.d.ts +0 -5
  54. package/dist/src/utils/logger.js +2 -2
  55. package/dist/src/utils/package-root.d.ts +0 -2
  56. package/dist/src/utils/package-root.js +0 -4
  57. package/package.json +1 -1
package/README.md CHANGED
@@ -640,9 +640,7 @@ The two flags can be combined — `--with-tests --xpcshell` writes both harnesse
640
640
 
641
641
  ### Mochitest stalls and `--marionette-port`
642
642
 
643
- When you pass `--marionette-port <n>`, FireForge uses that port for the stale-listener probe and for `--doctor`, and it forwards `--setpref=marionette.port=<n>` to `mach test` so the harness binds the same port. The only exception is an explicit `--mach-arg --flavor=xpcshell` (or `--flavor=xpcshell-tests`): that harness ignores the pref, so FireForge skips auto-forward and logs a short notice instead. Toolkit widget mochitests under `toolkit/content/tests/` (for example `test_*.html` next to `browser_*.js` suites) therefore stay aligned with the probe without duplicating `--mach-arg=--setpref=marionette.port=…`.
644
-
645
- Some forks see mochitests end with **`TEST_END: TIMEOUT`** after on the order of **370 seconds** with **no harness output** — including runs where mozinfo reports `headless: false`, so the failure is not explained by SWGL headless alone. When tests wait on custom chrome (for example a fork tile shell that never sets a `_readyForTesting` gate), the hang is **engine-side**; use your fork’s test docs and `browser.toml` defaults (for Hominis-style trees, see the fork’s `AGENT_RULES.md` for `hominis.testing.*` prefs). Operationally: ensure Marionette port **2828** is free, pass **`--marionette-port`** when you use a non-default port (FireForge keeps preflight and mach consistent as above), and narrow the failure with `--doctor` or by splitting a lighter smoke test that does not depend on full chrome init.
643
+ When you pass `--marionette-port <n>`, FireForge uses that port for the **stale-listener probe** (before `mach test` runs) and for **`--doctor`**, forwards **`--setpref=marionette.port=<n>`** to `mach test` so the **browser Marionette listener** binds that port, and forwards **`--marionette=127.0.0.1:<n>`** so the **mochitest harness client** connects to the same endpoint (the client otherwise defaults to `127.0.0.1:2828`). If you already pass **`--mach-arg=--marionette=...`**, FireForge does not add a second client flag. If **`--mach-arg`** already forwards the port via **`--marionette-port=...`** or **`--setpref=marionette.port=...`**, FireForge skips duplicating the setpref (existing behaviour). The exception is an explicit **`--mach-arg --flavor=xpcshell`** (or **`--flavor=xpcshell-tests`**): that harness does not use the browser Marionette path, so FireForge skips both auto-forwarded flags and logs a short notice instead. Toolkit widget mochitests under `toolkit/content/tests/` (for example `test_*.html` next to `browser_*.js` suites) therefore stay aligned with the probe without duplicating **`--mach-arg=--setpref=marionette.port=…`**.
646
644
 
647
645
  ### Stale-build preflight on `fireforge test`
648
646
 
@@ -1,6 +1,6 @@
1
1
  // SPDX-License-Identifier: EUPL-1.2
2
2
  import { execFile } from 'node:child_process';
3
- import { failure, warning } from './doctor.js';
3
+ import { failure, warning } from './doctor-check-core.js';
4
4
  /**
5
5
  * Scans bootstrap output for known failure patterns and returns structured
6
6
  * issue tags. A Python traceback paired with an HTTP 403 is collapsed into
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Doctor check types and lightweight result builders shared between
3
+ * `doctor.ts` and sibling check modules. Kept separate so registries can import
4
+ * these symbols without creating cycles through `doctor.ts`.
5
+ */
6
+ import type { DoctorCheck, DoctorOptions } from '../types/commands/index.js';
7
+ import type { FireForgeConfig, FireForgeState, ProjectPaths } from '../types/config.js';
8
+ import type { FurnaceConfig } from '../types/furnace.js';
9
+ /**
10
+ * Shared state available to every doctor check during a single run.
11
+ *
12
+ * The context is populated lazily by the doctor runner. Individual checks
13
+ * can record side-observations (e.g. the parsed `fireforge.json`) into the
14
+ * context for later checks to consume without re-parsing.
15
+ */
16
+ export interface DoctorCheckContext {
17
+ projectRoot: string;
18
+ paths: ProjectPaths;
19
+ state: FireForgeState;
20
+ options: DoctorOptions;
21
+ /**
22
+ * Whether the engine/ directory exists on disk. Populated before checks
23
+ * run so downstream checks can skip git/mach inspections cheaply.
24
+ */
25
+ engineExists: boolean;
26
+ /**
27
+ * The loaded project config, set by the "fireforge.json is valid" check
28
+ * when it succeeds. Undefined before that check runs and whenever loading
29
+ * failed.
30
+ */
31
+ config: FireForgeConfig | undefined;
32
+ /**
33
+ * Whether `furnace.json` exists on disk. Populated before checks run so
34
+ * the furnace group can skipIf cheaply when the subsystem is not in use.
35
+ */
36
+ furnaceConfigExists: boolean;
37
+ /**
38
+ * The parsed furnace config, set by the "Furnace configuration" check
39
+ * when it succeeds. Later furnace checks read from this so they do not
40
+ * re-parse the file; undefined when the config could not be loaded.
41
+ */
42
+ furnaceConfig: FurnaceConfig | undefined;
43
+ }
44
+ /**
45
+ * Result a check may return. A single object is the common case; an array
46
+ * lets a single check emit multiple related rows (e.g. the engine branch
47
+ * check which may report on branch + detached state together).
48
+ */
49
+ export type CheckResult = DoctorCheck | DoctorCheck[];
50
+ /**
51
+ * Declarative definition of a single doctor check.
52
+ *
53
+ * Every check opts into the shared execution/reporting pipeline by
54
+ * implementing only its inspection logic in `run`. Cross-cutting concerns
55
+ * (result aggregation, summary, exit codes) live in the runner instead of
56
+ * being duplicated at each call site.
57
+ */
58
+ export interface DoctorCheckDefinition {
59
+ /**
60
+ * Human-readable name surfaced in the check report (e.g. "Git installed").
61
+ * Not required to be unique, but tests assert on it.
62
+ */
63
+ name: string;
64
+ /**
65
+ * When `true`, the check is silently skipped. Used for checks that only
66
+ * apply when the engine is present, or only when specific state flags
67
+ * are set. Skipped checks contribute nothing to the final report.
68
+ */
69
+ skipIf?: (ctx: DoctorCheckContext) => boolean;
70
+ /**
71
+ * Names of checks that must appear earlier in the registry and run before
72
+ * this check. Enforced at startup via validateCheckDependencies so
73
+ * accidental reorders surface immediately instead of producing subtle
74
+ * context-population bugs at runtime.
75
+ */
76
+ dependsOn?: readonly string[];
77
+ /**
78
+ * Runs the inspection. Throwing is shorthand for "this check failed with
79
+ * severity 'error'" — the runner converts the exception message into a
80
+ * DoctorCheck. Returning a DoctorCheck (or array) lets the check control
81
+ * severity, warnings, and fix hints directly.
82
+ */
83
+ run: (ctx: DoctorCheckContext) => CheckResult | Promise<CheckResult>;
84
+ /**
85
+ * Optional recovery hint attached to the auto-generated failure result
86
+ * when `run` throws. Ignored when `run` returns a DoctorCheck explicitly.
87
+ */
88
+ fix?: string;
89
+ }
90
+ /**
91
+ * Builds a DoctorCheck object representing a successful "OK" check.
92
+ */
93
+ export declare function ok(name: string): DoctorCheck;
94
+ /**
95
+ * Builds a DoctorCheck object representing a warning result.
96
+ */
97
+ export declare function warning(name: string, message: string, fix?: string): DoctorCheck;
98
+ /**
99
+ * Builds a DoctorCheck object representing a failure result.
100
+ */
101
+ export declare function failure(name: string, message: string, fix?: string): DoctorCheck;
@@ -0,0 +1,32 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ /**
3
+ * Doctor check types and lightweight result builders shared between
4
+ * `doctor.ts` and sibling check modules. Kept separate so registries can import
5
+ * these symbols without creating cycles through `doctor.ts`.
6
+ */
7
+ /**
8
+ * Builds a DoctorCheck object representing a successful "OK" check.
9
+ */
10
+ export function ok(name) {
11
+ return { name, passed: true, severity: 'ok', message: 'OK' };
12
+ }
13
+ /**
14
+ * Builds a DoctorCheck object representing a warning result.
15
+ */
16
+ export function warning(name, message, fix) {
17
+ return {
18
+ name,
19
+ passed: true,
20
+ severity: 'warning',
21
+ warning: true,
22
+ message,
23
+ ...(fix ? { fix } : {}),
24
+ };
25
+ }
26
+ /**
27
+ * Builds a DoctorCheck object representing a failure result.
28
+ */
29
+ export function failure(name, message, fix) {
30
+ return { name, passed: false, severity: 'error', message, ...(fix ? { fix } : {}) };
31
+ }
32
+ //# sourceMappingURL=doctor-check-core.js.map
@@ -14,5 +14,5 @@
14
14
  * Lives in a sibling module to keep `doctor-furnace.ts` under the
15
15
  * per-file LOC budget.
16
16
  */
17
- import type { DoctorCheckDefinition } from './doctor.js';
17
+ import type { DoctorCheckDefinition } from './doctor-check-core.js';
18
18
  export declare const furnaceManifestSyncCheck: DoctorCheckDefinition;
@@ -20,7 +20,7 @@ import { join } from 'node:path';
20
20
  import { getFurnacePaths, loadFurnaceConfig, writeFurnaceConfig } from '../core/furnace-config.js';
21
21
  import { toError } from '../utils/errors.js';
22
22
  import { pathExists, readJson } from '../utils/fs.js';
23
- import { failure, ok, warning } from './doctor.js';
23
+ import { failure, ok, warning } from './doctor-check-core.js';
24
24
  async function listComponentDirs(dir) {
25
25
  if (!(await pathExists(dir)))
26
26
  return [];
@@ -1,4 +1,4 @@
1
- import type { DoctorCheckDefinition } from './doctor.js';
1
+ import type { DoctorCheckDefinition } from './doctor-check-core.js';
2
2
  /**
3
3
  * The ordered furnace check group. Exported as an array so `doctor.ts`
4
4
  * can splice it into the main registry at the right position. The order
@@ -9,7 +9,7 @@ import { getFurnaceLockPath, runFurnaceMutation } from '../core/furnace-operatio
9
9
  import { validateAllComponents } from '../core/furnace-validate.js';
10
10
  import { toError } from '../utils/errors.js';
11
11
  import { pathExists } from '../utils/fs.js';
12
- import { failure, ok, warning } from './doctor.js';
12
+ import { failure, ok, warning } from './doctor-check-core.js';
13
13
  import { furnaceManifestSyncCheck } from './doctor-furnace-manifest-sync.js';
14
14
  const ENGINE_REPAIRABLE_OPERATIONS = [
15
15
  'preview-teardown',
@@ -10,7 +10,7 @@
10
10
  * budget; see the call site in `runEngineGitChecks`.
11
11
  */
12
12
  import type { DoctorCheck } from '../types/commands/index.js';
13
- import type { DoctorCheckContext } from './doctor.js';
13
+ import type { DoctorCheckContext } from './doctor-check-core.js';
14
14
  /**
15
15
  * Inspects the engine working tree and returns a single
16
16
  * `DoctorCheck`. Ownership-aware: patch-backed / branding / furnace
@@ -13,7 +13,7 @@
13
13
  import { collectFurnaceManagedPrefixes } from '../core/furnace-config.js';
14
14
  import { expandUntrackedDirectoryEntries, getWorkingTreeStatus } from '../core/git-status.js';
15
15
  import { classifyFiles } from '../core/status-classify.js';
16
- import { ok, warning } from './doctor.js';
16
+ import { ok, warning } from './doctor-check-core.js';
17
17
  function summarizeWorkingTreeChangeCount(changeCount) {
18
18
  return `Engine working tree has ${changeCount} local change${changeCount === 1 ? '' : 's'}. Some FireForge commands assume a clean baseline and may behave differently until these are exported, discarded, or committed.`;
19
19
  }
@@ -2,113 +2,7 @@ import { Command } from 'commander';
2
2
  import { ExitCode } from '../errors/codes.js';
3
3
  import type { CommandContext } from '../types/cli.js';
4
4
  import type { DoctorCheck, DoctorOptions } from '../types/commands/index.js';
5
- import type { FireForgeConfig, FireForgeState, ProjectPaths } from '../types/config.js';
6
- import type { FurnaceConfig } from '../types/furnace.js';
7
- /**
8
- * Shared state available to every doctor check during a single run.
9
- *
10
- * The context is populated lazily by the doctor runner. Individual checks
11
- * can record side-observations (e.g. the parsed `fireforge.json`) into the
12
- * context for later checks to consume without re-parsing.
13
- *
14
- * Exported so sibling modules (e.g. `doctor-furnace.ts`) can declare
15
- * `DoctorCheckDefinition` entries against the same shared context.
16
- */
17
- export interface DoctorCheckContext {
18
- projectRoot: string;
19
- paths: ProjectPaths;
20
- state: FireForgeState;
21
- options: DoctorOptions;
22
- /**
23
- * Whether the engine/ directory exists on disk. Populated before checks
24
- * run so downstream checks can skip git/mach inspections cheaply.
25
- */
26
- engineExists: boolean;
27
- /**
28
- * The loaded project config, set by the "fireforge.json is valid" check
29
- * when it succeeds. Undefined before that check runs and whenever loading
30
- * failed.
31
- */
32
- config: FireForgeConfig | undefined;
33
- /**
34
- * Whether `furnace.json` exists on disk. Populated before checks run so
35
- * the furnace group can skipIf cheaply when the subsystem is not in use.
36
- * A missing furnace.json is not an error — plenty of projects never touch
37
- * the subsystem — so the doctor stays silent rather than failing.
38
- */
39
- furnaceConfigExists: boolean;
40
- /**
41
- * The parsed furnace config, set by the "Furnace configuration" check
42
- * when it succeeds. Later furnace checks read from this so they do not
43
- * re-parse the file; undefined when the config could not be loaded.
44
- */
45
- furnaceConfig: FurnaceConfig | undefined;
46
- }
47
- /**
48
- * Result a check may return. A single object is the common case; an array
49
- * lets a single check emit multiple related rows (e.g. the engine branch
50
- * check which may report on branch + detached state together).
51
- */
52
- export type CheckResult = DoctorCheck | DoctorCheck[];
53
- /**
54
- * Declarative definition of a single doctor check.
55
- *
56
- * Every check opts into the shared execution/reporting pipeline by
57
- * implementing only its inspection logic in `run`. Cross-cutting concerns
58
- * (result aggregation, summary, exit codes) live in the runner instead of
59
- * being duplicated at each call site.
60
- *
61
- * Exported so sibling modules (e.g. `doctor-furnace.ts`) can declare
62
- * new checks without re-deriving the shape.
63
- */
64
- export interface DoctorCheckDefinition {
65
- /**
66
- * Human-readable name surfaced in the check report (e.g. "Git installed").
67
- * Not required to be unique, but tests assert on it.
68
- */
69
- name: string;
70
- /**
71
- * When `true`, the check is silently skipped. Used for checks that only
72
- * apply when the engine is present, or only when specific state flags
73
- * are set. Skipped checks contribute nothing to the final report.
74
- */
75
- skipIf?: (ctx: DoctorCheckContext) => boolean;
76
- /**
77
- * Names of checks that must appear earlier in the registry and run before
78
- * this check. Enforced at startup via {@link validateCheckDependencies} so
79
- * accidental reorders surface immediately instead of producing subtle
80
- * context-population bugs at runtime.
81
- */
82
- dependsOn?: readonly string[];
83
- /**
84
- * Runs the inspection. Throwing is shorthand for "this check failed with
85
- * severity 'error'" — the runner converts the exception message into a
86
- * DoctorCheck. Returning a DoctorCheck (or array) lets the check control
87
- * severity, warnings, and fix hints directly.
88
- */
89
- run: (ctx: DoctorCheckContext) => CheckResult | Promise<CheckResult>;
90
- /**
91
- * Optional recovery hint attached to the auto-generated failure result
92
- * when `run` throws. Ignored when `run` returns a DoctorCheck explicitly.
93
- */
94
- fix?: string;
95
- }
96
- /**
97
- * Builds a DoctorCheck object representing a successful "OK" check.
98
- * Exported for sibling check modules that declare `DoctorCheckDefinition`
99
- * entries out-of-file (e.g. `doctor-furnace.ts`).
100
- */
101
- export declare function ok(name: string): DoctorCheck;
102
- /**
103
- * Builds a DoctorCheck object representing a warning result.
104
- * Exported for sibling check modules — see {@link ok}.
105
- */
106
- export declare function warning(name: string, message: string, fix?: string): DoctorCheck;
107
- /**
108
- * Builds a DoctorCheck object representing a failure result.
109
- * Exported for sibling check modules — see {@link ok}.
110
- */
111
- export declare function failure(name: string, message: string, fix?: string): DoctorCheck;
5
+ import type { DoctorCheckDefinition } from './doctor-check-core.js';
112
6
  /**
113
7
  * Validates that every check's `dependsOn` entries appear earlier in the
114
8
  * registry. Called once at module load time so a broken reorder surfaces
@@ -10,37 +10,9 @@ import { toError } from '../utils/errors.js';
10
10
  import { pathExists } from '../utils/fs.js';
11
11
  import { error, info, intro, outro, success, warn } from '../utils/logger.js';
12
12
  import { findExecutable } from '../utils/process.js';
13
+ import { failure, ok, warning } from './doctor-check-core.js';
13
14
  import { FURNACE_DOCTOR_CHECKS } from './doctor-furnace.js';
14
15
  import { inspectEngineWorkingTree } from './doctor-working-tree.js';
15
- /**
16
- * Builds a DoctorCheck object representing a successful "OK" check.
17
- * Exported for sibling check modules that declare `DoctorCheckDefinition`
18
- * entries out-of-file (e.g. `doctor-furnace.ts`).
19
- */
20
- export function ok(name) {
21
- return { name, passed: true, severity: 'ok', message: 'OK' };
22
- }
23
- /**
24
- * Builds a DoctorCheck object representing a warning result.
25
- * Exported for sibling check modules — see {@link ok}.
26
- */
27
- export function warning(name, message, fix) {
28
- return {
29
- name,
30
- passed: true,
31
- severity: 'warning',
32
- warning: true,
33
- message,
34
- ...(fix ? { fix } : {}),
35
- };
36
- }
37
- /**
38
- * Builds a DoctorCheck object representing a failure result.
39
- * Exported for sibling check modules — see {@link ok}.
40
- */
41
- export function failure(name, message, fix) {
42
- return { name, passed: false, severity: 'error', message, ...(fix ? { fix } : {}) };
43
- }
44
16
  /**
45
17
  * Runs a single check definition, converting thrown errors into
46
18
  * DoctorCheck failure rows. Always returns an array so the caller can
@@ -1,21 +1,4 @@
1
1
  import { Command } from 'commander';
2
2
  import type { CommandContext } from '../../types/cli.js';
3
- import { furnaceApplyCommand } from './apply.js';
4
- import { furnaceChromeDocCreateCommand } from './chrome-doc.js';
5
- import { furnaceCreateCommand } from './create.js';
6
- import { furnaceDeployCommand } from './deploy.js';
7
- import { furnaceDiffCommand } from './diff.js';
8
- import { furnaceInitCommand } from './init.js';
9
- import { furnaceListCommand } from './list.js';
10
- import { furnaceBatchOverrideCommand, furnaceOverrideCommand } from './override.js';
11
- import { furnacePreviewCommand } from './preview.js';
12
- import { furnaceRefreshCommand } from './refresh.js';
13
- import { furnaceRemoveCommand } from './remove.js';
14
- import { furnaceRenameCommand } from './rename.js';
15
- import { furnaceScanCommand } from './scan.js';
16
- import { furnaceStatusCommand } from './status.js';
17
- import { furnaceSyncCommand } from './sync.js';
18
- import { furnaceValidateCommand } from './validate.js';
19
- export { furnaceApplyCommand, furnaceBatchOverrideCommand, furnaceChromeDocCreateCommand, furnaceCreateCommand, furnaceDeployCommand, furnaceDiffCommand, furnaceInitCommand, furnaceListCommand, furnaceOverrideCommand, furnacePreviewCommand, furnaceRefreshCommand, furnaceRemoveCommand, furnaceRenameCommand, furnaceScanCommand, furnaceStatusCommand, furnaceSyncCommand, furnaceValidateCommand, };
20
3
  /** Registers the furnace command on the CLI program. */
21
4
  export declare function registerFurnace(program: Command, context: CommandContext): void;
@@ -17,7 +17,6 @@ import { furnaceScanCommand } from './scan.js';
17
17
  import { furnaceStatusCommand } from './status.js';
18
18
  import { furnaceSyncCommand } from './sync.js';
19
19
  import { furnaceValidateCommand } from './validate.js';
20
- export { furnaceApplyCommand, furnaceBatchOverrideCommand, furnaceChromeDocCreateCommand, furnaceCreateCommand, furnaceDeployCommand, furnaceDiffCommand, furnaceInitCommand, furnaceListCommand, furnaceOverrideCommand, furnacePreviewCommand, furnaceRefreshCommand, furnaceRemoveCommand, furnaceRenameCommand, furnaceScanCommand, furnaceStatusCommand, furnaceSyncCommand, furnaceValidateCommand, };
21
20
  /**
22
21
  * Registers Furnace commands for querying component state: status, scan,
23
22
  * and action commands like apply, deploy, and create.
@@ -1,9 +1,8 @@
1
+ import type { FurnaceValidateOptions } from '../../types/commands/index.js';
1
2
  /**
2
3
  * Runs the furnace validate command to perform static analysis on components.
3
4
  * @param projectRoot - Root directory of the project
4
5
  * @param name - Optional component name to validate (validates all if omitted)
5
6
  * @param options - Optional command options (e.g. --fix)
6
7
  */
7
- export declare function furnaceValidateCommand(projectRoot: string, name?: string, options?: {
8
- fix?: boolean;
9
- }): Promise<void>;
8
+ export declare function furnaceValidateCommand(projectRoot: string, name?: string, options?: FurnaceValidateOptions): Promise<void>;
@@ -7,12 +7,6 @@
7
7
  */
8
8
  import { Command } from 'commander';
9
9
  import type { CommandContext } from '../../types/cli.js';
10
- export { patchCompactCommand } from './compact.js';
11
- export { patchDeleteCommand } from './delete.js';
12
- export { patchLintIgnoreCommand } from './lint-ignore.js';
13
- export { patchRenameCommand } from './rename.js';
14
- export { patchReorderCommand } from './reorder.js';
15
- export { patchTierCommand } from './tier.js';
16
10
  /**
17
11
  * Registers the `patch` subcommand parent and its verbs on the CLI.
18
12
  *
@@ -12,12 +12,6 @@ import { registerPatchLintIgnore } from './lint-ignore.js';
12
12
  import { registerPatchRename } from './rename.js';
13
13
  import { registerPatchReorder } from './reorder.js';
14
14
  import { registerPatchTier } from './tier.js';
15
- export { patchCompactCommand } from './compact.js';
16
- export { patchDeleteCommand } from './delete.js';
17
- export { patchLintIgnoreCommand } from './lint-ignore.js';
18
- export { patchRenameCommand } from './rename.js';
19
- export { patchReorderCommand } from './reorder.js';
20
- export { patchTierCommand } from './tier.js';
21
15
  /**
22
16
  * Registers the `patch` subcommand parent and its verbs on the CLI.
23
17
  *
@@ -3,7 +3,7 @@ import { join } from 'node:path';
3
3
  import { prepareBuildEnvironment } from '../core/build-prepare.js';
4
4
  import { getProjectPaths, loadConfig } from '../core/config.js';
5
5
  import { buildArtifactMismatchMessage, buildUI, hasBuildArtifacts, hasRunnableBundle, testWithOutput, } from '../core/mach.js';
6
- import { assertMarionettePortAvailable, extractForwardedMarionettePort, shouldAutoForwardMarionettePortToMach, } from '../core/marionette-port.js';
6
+ import { assertMarionettePortAvailable, extractForwardedMarionettePort, forwardedMachArgsIncludeMarionetteClient, shouldAutoForwardMarionettePortToMach, } from '../core/marionette-port.js';
7
7
  import { formatMarionettePreflightLine, reportMarionettePreflight, runMarionettePreflight, } from '../core/marionette-preflight.js';
8
8
  import { checkStaleBuildForTest, formatStaleBuildWarning } from '../core/test-stale-check.js';
9
9
  import { operatorAlreadySetAppPath, resolveXpcshellAppdirArg, } from '../core/xpcshell-appdir.js';
@@ -320,17 +320,21 @@ export async function testCommand(projectRoot, testPaths, options = {}) {
320
320
  extraArgs.push(...options.machArg);
321
321
  }
322
322
  // Auto-forward the Marionette port to mach when `--marionette-port` is
323
- // set. We use `--setpref=marionette.port=<n>` because the marionette
324
- // listener reads that pref before binding (browser-chrome / mochitest
325
- // path); xpcshell never reads it, so the pref is a no-op there.
323
+ // set. `--setpref=marionette.port=<n>` configures where the browser
324
+ // listener binds; `--marionette=127.0.0.1:<n>` tells the mochitest harness
325
+ // client to connect there (default client is 127.0.0.1:2828). xpcshell
326
+ // ignores both for browser Marionette.
326
327
  //
327
- // Skip forwarding when the operator already supplied an equivalent arg
328
- // via `--mach-arg` — duplicates would be confusing without changing
328
+ // Skip setpref forwarding when the operator already supplied an equivalent
329
+ // arg via `--mach-arg` — duplicates would be confusing without changing
329
330
  // semantics. Skip when mach args explicitly request `--flavor=xpcshell`
330
331
  // (or `xpcshell-tests`): the preflight still honours `--marionette-port`,
331
332
  // but mach does not use the marionette.port pref on that harness. Any
332
333
  // other arg shape still forwards so toolkit widget paths and mixed suites
333
334
  // stay aligned with the probe without duplicate `--mach-arg` flags.
335
+ //
336
+ // Skip auto `--marionette=...` when `--mach-arg` already includes a client
337
+ // `--marionette=...` (or two-token `--marionette host:port`).
334
338
  if (options.marionettePort !== undefined) {
335
339
  const operatorAlreadyForwarded = forwardedPort !== undefined;
336
340
  const machArgs = options.machArg ?? [];
@@ -341,7 +345,11 @@ export async function testCommand(projectRoot, testPaths, options = {}) {
341
345
  extraArgs.push(`--setpref=marionette.port=${options.marionettePort}`);
342
346
  }
343
347
  else {
344
- info(`--marionette-port=${options.marionettePort} applied to the preflight probe, but --flavor=xpcshell is set — mach is not auto-configured with --setpref=marionette.port (xpcshell ignores that pref). Pass --mach-arg --setpref=marionette.port=${options.marionettePort} explicitly if you still need mach to see the port.`);
348
+ info(`--marionette-port=${options.marionettePort} applied to the preflight probe, but --flavor=xpcshell is set — mach is not auto-configured with --setpref=marionette.port or --marionette (xpcshell ignores the browser Marionette path). Pass --mach-arg --setpref=marionette.port=${options.marionettePort} explicitly if you still need mach to see the port.`);
349
+ }
350
+ if (shouldAutoForwardMarionettePortToMach(machArgs) &&
351
+ !forwardedMachArgsIncludeMarionetteClient(machArgs)) {
352
+ extraArgs.push(`--marionette=127.0.0.1:${options.marionettePort}`);
345
353
  }
346
354
  }
347
355
  // xpcshell appdir auto-injection — see src/core/xpcshell-appdir.ts for the
@@ -409,7 +417,7 @@ export function registerTest(program, { getProjectRoot, withErrorHandling }) {
409
417
  acc.push(value);
410
418
  return acc;
411
419
  }, [])
412
- .option('--marionette-port <port>', 'Override the Marionette control port (default 2828) for the stale-browser probe, the --doctor preflight, and (unless --mach-arg includes --flavor=xpcshell) the auto-forwarded --setpref=marionette.port=<n> passed to mach. Use this when a stale process holds 2828 or a CI runner reserves a different port.', (raw) => {
420
+ .option('--marionette-port <port>', 'Override the Marionette control port (default 2828) for the stale-browser probe, the --doctor preflight, and (unless --mach-arg includes --flavor=xpcshell) auto-forwarded mach args: --setpref=marionette.port=<n> (browser listener) and --marionette=127.0.0.1:<n> (mochitest client). Omits the client flag when --mach-arg already sets --marionette. Use when 2828 is busy or CI assigns another port.', (raw) => {
413
421
  const n = Number.parseInt(raw, 10);
414
422
  if (!Number.isFinite(n) || n < 1 || n > 65535) {
415
423
  throw new GeneralError(`--marionette-port must be an integer in 1..65535 (got "${raw}")`);
@@ -111,6 +111,38 @@ async function assertDomTargetIsWireable(projectRoot, domFilePath, domTargetPath
111
111
  '`--target <path>`.', 'target');
112
112
  }
113
113
  }
114
+ async function resolveWireSubscriptDir(projectRoot, options) {
115
+ let subscriptDir = DEFAULT_BROWSER_SUBSCRIPT_DIR;
116
+ try {
117
+ const config = await loadConfig(projectRoot);
118
+ if (config.wire?.subscriptDir) {
119
+ subscriptDir = config.wire.subscriptDir;
120
+ }
121
+ }
122
+ catch (error) {
123
+ warn(`Using default wire.subscriptDir because fireforge.json could not be loaded: ${toError(error).message}`);
124
+ }
125
+ if (options.subscriptDir) {
126
+ if (!isContainedRelativePath(options.subscriptDir)) {
127
+ throw new InvalidArgumentError(`Subscript directory must stay within engine/: ${options.subscriptDir}`, 'subscriptDir');
128
+ }
129
+ subscriptDir = options.subscriptDir;
130
+ }
131
+ return subscriptDir;
132
+ }
133
+ async function ensureSubscriptSourceExists(projectRoot, subscriptDir, name, dryRun) {
134
+ const paths = getProjectPaths(projectRoot);
135
+ const subscriptPath = join(paths.engine, subscriptDir, `${name}.js`);
136
+ if (!(await pathExists(subscriptPath))) {
137
+ if (dryRun) {
138
+ info(`Note: ${subscriptDir}/${name}.js does not exist yet — the real wire command will require it before writing. Create the file before re-running without --dry-run.`);
139
+ }
140
+ else {
141
+ throw new InvalidArgumentError(`Subscript file not found: ${subscriptDir}/${name}.js\n` +
142
+ 'Create the file in engine/ before wiring.', 'name');
143
+ }
144
+ }
145
+ }
114
146
  /**
115
147
  * Wires a chrome subscript into the browser.
116
148
  *
@@ -144,23 +176,7 @@ export async function wireCommand(projectRoot, name, options = {}) {
144
176
  validateWireExpression(options.destroy, 'destroy expression');
145
177
  }
146
178
  consumeParserFallbackEvents();
147
- // Resolve subscript directory: CLI flag > fireforge.json > default
148
- let subscriptDir = DEFAULT_BROWSER_SUBSCRIPT_DIR;
149
- try {
150
- const config = await loadConfig(projectRoot);
151
- if (config.wire?.subscriptDir) {
152
- subscriptDir = config.wire.subscriptDir;
153
- }
154
- }
155
- catch (error) {
156
- warn(`Using default wire.subscriptDir because fireforge.json could not be loaded: ${toError(error).message}`);
157
- }
158
- if (options.subscriptDir) {
159
- if (!isContainedRelativePath(options.subscriptDir)) {
160
- throw new InvalidArgumentError(`Subscript directory must stay within engine/: ${options.subscriptDir}`, 'subscriptDir');
161
- }
162
- subscriptDir = options.subscriptDir;
163
- }
179
+ const subscriptDir = await resolveWireSubscriptDir(projectRoot, options);
164
180
  // Validate DOM fragment file exists and compute path relative to engine root.
165
181
  //
166
182
  // Accepts three shapes:
@@ -235,21 +251,7 @@ export async function wireCommand(projectRoot, name, options = {}) {
235
251
  // plausible plan and the non-dry-run invocation then errored. The
236
252
  // info line surfaces the mismatch in preview mode so the operator
237
253
  // can act on the warning before re-running without --dry-run.
238
- if (!options.dryRun) {
239
- const paths = getProjectPaths(projectRoot);
240
- const subscriptPath = join(paths.engine, subscriptDir, `${name}.js`);
241
- if (!(await pathExists(subscriptPath))) {
242
- throw new InvalidArgumentError(`Subscript file not found: ${subscriptDir}/${name}.js\n` +
243
- 'Create the file in engine/ before wiring.', 'name');
244
- }
245
- }
246
- else {
247
- const paths = getProjectPaths(projectRoot);
248
- const subscriptPath = join(paths.engine, subscriptDir, `${name}.js`);
249
- if (!(await pathExists(subscriptPath))) {
250
- info(`Note: ${subscriptDir}/${name}.js does not exist yet — the real wire command will require it before writing. Create the file before re-running without --dry-run.`);
251
- }
252
- }
254
+ await ensureSubscriptSourceExists(projectRoot, subscriptDir, name, options.dryRun ?? false);
253
255
  if (options.dryRun) {
254
256
  printWireDryRun(getProjectPaths(projectRoot).engine, name, subscriptDir, domFilePath, domTargetPath, options);
255
257
  return;
@@ -9,8 +9,8 @@
9
9
  */
10
10
  import type { FireForgeConfig } from '../types/config.js';
11
11
  export { mutateConfig } from './config-mutate.js';
12
- export { CONFIG_FILENAME, CONFIGS_DIR, ENGINE_DIR, FIREFORGE_DIR, getProjectPaths, PATCHES_DIR, SRC_DIR, STATE_FILENAME, SUPPORTED_CONFIG_PATHS, SUPPORTED_CONFIG_ROOT_KEYS, } from './config-paths.js';
13
- export { loadState, saveState, updateState, validateFireForgeState } from './config-state.js';
12
+ export { CONFIG_FILENAME, FIREFORGE_DIR, getProjectPaths, STATE_FILENAME, SUPPORTED_CONFIG_PATHS, SUPPORTED_CONFIG_ROOT_KEYS, } from './config-paths.js';
13
+ export { loadState, saveState, updateState } from './config-state.js';
14
14
  export { validateConfig } from './config-validate.js';
15
15
  /**
16
16
  * Checks if a fireforge.json exists in the given directory.
@@ -17,8 +17,8 @@ import { validateConfig } from './config-validate.js';
17
17
  import { createSiblingLockPath, withFileLock } from './file-lock.js';
18
18
  // ---- re-exports ----
19
19
  export { mutateConfig } from './config-mutate.js';
20
- export { CONFIG_FILENAME, CONFIGS_DIR, ENGINE_DIR, FIREFORGE_DIR, getProjectPaths, PATCHES_DIR, SRC_DIR, STATE_FILENAME, SUPPORTED_CONFIG_PATHS, SUPPORTED_CONFIG_ROOT_KEYS, } from './config-paths.js';
21
- export { loadState, saveState, updateState, validateFireForgeState } from './config-state.js';
20
+ export { CONFIG_FILENAME, FIREFORGE_DIR, getProjectPaths, STATE_FILENAME, SUPPORTED_CONFIG_PATHS, SUPPORTED_CONFIG_ROOT_KEYS, } from './config-paths.js';
21
+ export { loadState, saveState, updateState } from './config-state.js';
22
22
  export { validateConfig } from './config-validate.js';
23
23
  // ---- config I/O (stays here because it bridges paths + validation) ----
24
24
  /**
@@ -8,7 +8,7 @@ import { copyFile, ensureDir, pathExists, readText, removeFile } from '../utils/
8
8
  import { verbose } from '../utils/logger.js';
9
9
  import { applyCustomFtlFile, describeLocaleFtlJarMnRegistration, removeCustomFtlJarMnEntry, } from './furnace-apply-ftl.js';
10
10
  import { CUSTOM_ELEMENTS_JS, JAR_MN } from './furnace-constants.js';
11
- import { addCustomElementRegistration, addJarMnEntries, validateCustomElementRegistration, validateJarMnEntries, } from './furnace-registration.js';
11
+ import { addCustomElementRegistration, addJarMnEntries, validateCustomElementRegistration, validateJarMnInsertionForFiles, } from './furnace-registration.js';
12
12
  import { recordCreatedDir, snapshotFile } from './furnace-rollback.js';
13
13
  import { checkRegistrationConsistency } from './furnace-validate-registration.js';
14
14
  import { isGitRepository } from './git.js';
@@ -352,7 +352,7 @@ async function buildCustomDryRunActions(name, componentDir, engineDir, config, t
352
352
  .map((entry) => entry.name);
353
353
  if (copiedFileNames.length > 0) {
354
354
  try {
355
- await validateJarMnEntries(engineDir, name, copiedFileNames);
355
+ await validateJarMnInsertionForFiles(engineDir, name, copiedFileNames);
356
356
  }
357
357
  catch (error) {
358
358
  stepErrors.push({