@hominis/fireforge 0.37.0 → 0.39.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.
Files changed (99) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/README.md +1 -1
  3. package/dist/src/commands/build.js +13 -8
  4. package/dist/src/commands/config.js +1 -0
  5. package/dist/src/commands/discard.js +75 -50
  6. package/dist/src/commands/download.js +5 -2
  7. package/dist/src/commands/export-flow.d.ts +6 -2
  8. package/dist/src/commands/export-flow.js +17 -11
  9. package/dist/src/commands/export-placement-gate.js +4 -6
  10. package/dist/src/commands/export-placement-policy.d.ts +11 -3
  11. package/dist/src/commands/export-placement-policy.js +32 -29
  12. package/dist/src/commands/export-shared.js +17 -2
  13. package/dist/src/commands/export.js +5 -5
  14. package/dist/src/commands/furnace/index.js +23 -18
  15. package/dist/src/commands/import.js +5 -0
  16. package/dist/src/commands/lint-per-patch.js +15 -5
  17. package/dist/src/commands/lint.js +1 -1
  18. package/dist/src/commands/patch/lint-ignore.js +20 -1
  19. package/dist/src/commands/patch/move-files-create.d.ts +19 -0
  20. package/dist/src/commands/patch/move-files-create.js +116 -0
  21. package/dist/src/commands/patch/move-files-into.d.ts +47 -0
  22. package/dist/src/commands/patch/move-files-into.js +245 -0
  23. package/dist/src/commands/patch/move-files.d.ts +3 -1
  24. package/dist/src/commands/patch/move-files.js +74 -15
  25. package/dist/src/commands/patch/rename.js +4 -1
  26. package/dist/src/commands/patch/split.d.ts +11 -1
  27. package/dist/src/commands/patch/split.js +6 -5
  28. package/dist/src/commands/re-export-scan.d.ts +17 -0
  29. package/dist/src/commands/re-export-scan.js +51 -0
  30. package/dist/src/commands/re-export.js +17 -6
  31. package/dist/src/commands/reset.js +5 -0
  32. package/dist/src/commands/source.js +28 -3
  33. package/dist/src/commands/status-output.d.ts +9 -0
  34. package/dist/src/commands/status-output.js +33 -1
  35. package/dist/src/commands/status.js +17 -3
  36. package/dist/src/commands/test-diagnose.d.ts +1 -1
  37. package/dist/src/commands/test-diagnose.js +9 -3
  38. package/dist/src/commands/test-modes.d.ts +21 -0
  39. package/dist/src/commands/test-modes.js +42 -0
  40. package/dist/src/commands/test-register.js +6 -5
  41. package/dist/src/commands/test-run.d.ts +2 -0
  42. package/dist/src/commands/test-run.js +8 -2
  43. package/dist/src/commands/test-stale-gate.d.ts +44 -0
  44. package/dist/src/commands/test-stale-gate.js +81 -0
  45. package/dist/src/commands/test.js +52 -81
  46. package/dist/src/core/build-audit.d.ts +12 -0
  47. package/dist/src/core/build-audit.js +14 -0
  48. package/dist/src/core/build-baseline-types.d.ts +41 -0
  49. package/dist/src/core/build-baseline.d.ts +6 -1
  50. package/dist/src/core/build-baseline.js +48 -7
  51. package/dist/src/core/build-prepare.d.ts +9 -0
  52. package/dist/src/core/build-prepare.js +24 -0
  53. package/dist/src/core/config-paths.d.ts +1 -1
  54. package/dist/src/core/config-paths.js +1 -0
  55. package/dist/src/core/config-validate.js +14 -1
  56. package/dist/src/core/discard-baseline.d.ts +60 -0
  57. package/dist/src/core/discard-baseline.js +232 -0
  58. package/dist/src/core/engine-session-lock.d.ts +10 -1
  59. package/dist/src/core/engine-session-lock.js +28 -2
  60. package/dist/src/core/file-lock.d.ts +33 -0
  61. package/dist/src/core/file-lock.js +32 -6
  62. package/dist/src/core/firefox-archive.d.ts +2 -1
  63. package/dist/src/core/firefox-archive.js +32 -8
  64. package/dist/src/core/firefox.d.ts +8 -3
  65. package/dist/src/core/firefox.js +11 -6
  66. package/dist/src/core/furnace-validate-helpers.js +28 -1
  67. package/dist/src/core/license-headers.d.ts +24 -0
  68. package/dist/src/core/license-headers.js +103 -1
  69. package/dist/src/core/patch-export.d.ts +10 -1
  70. package/dist/src/core/patch-export.js +12 -2
  71. package/dist/src/core/patch-lint-checkjs.d.ts +6 -1
  72. package/dist/src/core/patch-lint-checkjs.js +30 -14
  73. package/dist/src/core/patch-lint-cross.d.ts +9 -1
  74. package/dist/src/core/patch-lint-cross.js +69 -9
  75. package/dist/src/core/patch-lint-mozbuild.d.ts +18 -0
  76. package/dist/src/core/patch-lint-mozbuild.js +104 -0
  77. package/dist/src/core/patch-lint.d.ts +3 -0
  78. package/dist/src/core/patch-lint.js +29 -8
  79. package/dist/src/core/patch-policy.d.ts +14 -1
  80. package/dist/src/core/patch-policy.js +13 -2
  81. package/dist/src/core/status-classify.d.ts +11 -2
  82. package/dist/src/core/status-classify.js +49 -25
  83. package/dist/src/core/test-harness-crash.d.ts +10 -0
  84. package/dist/src/core/test-harness-crash.js +16 -0
  85. package/dist/src/core/test-stale-check.d.ts +56 -1
  86. package/dist/src/core/test-stale-check.js +114 -4
  87. package/dist/src/core/token-manager.js +39 -2
  88. package/dist/src/core/token-variant.d.ts +7 -0
  89. package/dist/src/core/token-variant.js +9 -0
  90. package/dist/src/core/typecheck-shim.d.ts +13 -0
  91. package/dist/src/core/typecheck-shim.js +17 -2
  92. package/dist/src/core/typecheck.js +14 -3
  93. package/dist/src/types/commands/options.d.ts +69 -3
  94. package/dist/src/types/config.d.ts +22 -0
  95. package/dist/src/utils/options.d.ts +20 -0
  96. package/dist/src/utils/options.js +39 -0
  97. package/dist/src/utils/validation.d.ts +5 -0
  98. package/dist/src/utils/validation.js +7 -0
  99. package/package.json +3 -3
@@ -107,6 +107,15 @@ function findVariantBlockInsertionPoint(lines) {
107
107
  }
108
108
  return lines.length;
109
109
  }
110
+ /**
111
+ * True when a top-level `:root<variant>` block exists in `lines`. Used by
112
+ * the override path to write theme-attribute blocks only when the scaffold
113
+ * already declares them — `insertVariantDeclaration` would otherwise create
114
+ * the block, which is only wanted for the explicit `--variant` flow.
115
+ */
116
+ export function variantBlockExists(lines, variant) {
117
+ return findVariantBlock(lines, variant) !== null;
118
+ }
110
119
  /** True when the `:root<variant>` block already declares `tokenName`. */
111
120
  export function variantBlockHasToken(lines, variant, tokenName) {
112
121
  const block = findVariantBlock(lines, variant);
@@ -24,6 +24,19 @@ export declare const SHIM_FILENAME = "__fireforge_firefox_globals.d.ts";
24
24
  * reason the operator couldn't infer from the docs.
25
25
  */
26
26
  export declare const SUPPRESSED_DIAGNOSTIC_CODES: ReadonlySet<number>;
27
+ /**
28
+ * Undefined-free-identifier codes, split out of
29
+ * {@link SUPPRESSED_DIAGNOSTIC_CODES} (FORGE F12). Unconditional
30
+ * suppression let a module reference a name with no import or declaration
31
+ * anywhere and still typecheck with 0 errors — the failure then surfaced
32
+ * as a runtime `ReferenceError`. Both flows now report these at a
33
+ * configurable severity (default `'warning'`: visible without breaking
34
+ * gates; genuine shim gaps are silenced by adding the global to
35
+ * `extraShim`, or per-run via the `'off'` setting).
36
+ */
37
+ export declare const UNDEFINED_IDENTIFIER_CODES: ReadonlySet<number>;
38
+ /** Hint appended to reported undefined-identifier diagnostics. */
39
+ export declare const UNDEFINED_IDENTIFIER_HINT: string;
27
40
  /**
28
41
  * Result of {@link composeShimSource}: the source body to feed into
29
42
  * the TS host plus a flag indicating whether the user-supplied extra
@@ -146,11 +146,26 @@ export const SUPPRESSED_DIAGNOSTIC_CODES = new Set([
146
146
  2306, // File '{0}' is not a module.
147
147
  2305, // Module '{0}' has no exported member '{1}'.
148
148
  2792, // Cannot find module '{0}'. Did you mean to set the 'moduleResolution' option...
149
- 2304, // Cannot find name '{0}'. (for globals we missed in the shim)
150
- 2552, // Cannot find name '{0}'. Did you mean '{1}'?
151
149
  2580, // Cannot find name '{0}'. Do you need to install type definitions...
152
150
  7016, // Could not find a declaration file for module '{0}'.
153
151
  ]);
152
+ /**
153
+ * Undefined-free-identifier codes, split out of
154
+ * {@link SUPPRESSED_DIAGNOSTIC_CODES} (FORGE F12). Unconditional
155
+ * suppression let a module reference a name with no import or declaration
156
+ * anywhere and still typecheck with 0 errors — the failure then surfaced
157
+ * as a runtime `ReferenceError`. Both flows now report these at a
158
+ * configurable severity (default `'warning'`: visible without breaking
159
+ * gates; genuine shim gaps are silenced by adding the global to
160
+ * `extraShim`, or per-run via the `'off'` setting).
161
+ */
162
+ export const UNDEFINED_IDENTIFIER_CODES = new Set([
163
+ 2304, // Cannot find name '{0}'.
164
+ 2552, // Cannot find name '{0}'. Did you mean '{1}'?
165
+ ]);
166
+ /** Hint appended to reported undefined-identifier diagnostics. */
167
+ export const UNDEFINED_IDENTIFIER_HINT = '(undefined identifier — import or declare it, add the global to the extra shim, ' +
168
+ 'or tune the "undefinedIdentifiers" setting)';
154
169
  /** Matches a lone triple-slash `/// <reference path="…" />` directive line. */
155
170
  const TRIPLE_SLASH_REFERENCE = /^\s*\/\/\/\s*<reference\s+path\s*=\s*["']([^"']+)["']\s*\/?>\s*$/;
156
171
  /**
@@ -21,7 +21,7 @@
21
21
  import { dirname, isAbsolute, relative, resolve } from 'node:path';
22
22
  import { pathExists } from '../utils/fs.js';
23
23
  import { verbose } from '../utils/logger.js';
24
- import { composeShimSource, SHIM_FILENAME, SUPPRESSED_DIAGNOSTIC_CODES } from './typecheck-shim.js';
24
+ import { composeShimSource, SHIM_FILENAME, SUPPRESSED_DIAGNOSTIC_CODES, UNDEFINED_IDENTIFIER_CODES, UNDEFINED_IDENTIFIER_HINT, } from './typecheck-shim.js';
25
25
  /**
26
26
  * Sentinel string surfaced as a `TypecheckIssue.message` when the
27
27
  * project's jsconfig sets `checkJs: false`. Exported so tests can
@@ -131,7 +131,7 @@ export async function runTypecheck(projectRoot, cfg) {
131
131
  });
132
132
  continue;
133
133
  }
134
- results.push(await runTypecheckForProject(ts, projectRoot, projectPath, shimSource));
134
+ results.push(await runTypecheckForProject(ts, projectRoot, projectPath, shimSource, cfg.undefinedIdentifiers ?? 'warning'));
135
135
  }
136
136
  return results;
137
137
  }
@@ -149,7 +149,7 @@ function resolveProjectExtraShim(cfg, projectPath) {
149
149
  return cfg.extraShim;
150
150
  }
151
151
  /** Runs typecheck for a single jsconfig path, isolating its failures. */
152
- async function runTypecheckForProject(ts, projectRoot, projectPath, shimSource) {
152
+ async function runTypecheckForProject(ts, projectRoot, projectPath, shimSource, undefinedIdentifiers = 'warning') {
153
153
  const absConfig = resolve(projectRoot, projectPath);
154
154
  if (!(await pathExists(absConfig))) {
155
155
  return {
@@ -268,6 +268,17 @@ async function runTypecheckForProject(ts, projectRoot, projectPath, shimSource)
268
268
  // can't act on them and they would clutter CI logs.
269
269
  if (diag.file?.fileName === shimPath)
270
270
  continue;
271
+ if (UNDEFINED_IDENTIFIER_CODES.has(diag.code)) {
272
+ if (undefinedIdentifiers === 'off')
273
+ continue;
274
+ const issue = diagnosticToIssue(ts, diag, absConfig, projectPath);
275
+ issues.push({
276
+ ...issue,
277
+ category: undefinedIdentifiers,
278
+ message: `${issue.message} ${UNDEFINED_IDENTIFIER_HINT}`,
279
+ });
280
+ continue;
281
+ }
271
282
  issues.push(diagnosticToIssue(ts, diag, absConfig, projectPath));
272
283
  }
273
284
  verbose(`typecheck: ${projectPath} — analyzed ${String(parsed.fileNames.length)} file(s), found ${String(issues.length)} issue(s)`);
@@ -43,6 +43,10 @@ export interface SourceSetOptions {
43
43
  sha256?: string;
44
44
  /** Clear any existing pinned SHA-256 */
45
45
  clearSha256?: boolean;
46
+ /** Optional release-candidate build directory (e.g. "build2") */
47
+ candidate?: string;
48
+ /** Clear any existing release-candidate build directory */
49
+ clearCandidate?: boolean;
46
50
  }
47
51
  /**
48
52
  * Options for the build command.
@@ -62,6 +66,12 @@ export interface BuildOptions {
62
66
  * for any mismatch the rewriter cannot prove safe.
63
67
  */
64
68
  rewriteMozinfo?: boolean;
69
+ /**
70
+ * Parsed `--wait-lock [seconds]` value (`true` for the bare flag, meaning
71
+ * 60). Consumed at the CLI layer to bound the engine-session-lock wait;
72
+ * the command implementation ignores it.
73
+ */
74
+ waitLock?: number | boolean;
65
75
  }
66
76
  /**
67
77
  * Options for the export command.
@@ -112,6 +122,12 @@ export interface ExportOptions {
112
122
  * another patch, because the resulting queue fails `verify` immediately.
113
123
  */
114
124
  allowOverlap?: boolean;
125
+ /**
126
+ * Parsed `--wait-lock [seconds]` value (`true` for the bare flag, meaning
127
+ * 60). Consumed at the CLI layer to bound the engine-session-lock wait;
128
+ * the command implementation ignores it.
129
+ */
130
+ waitLock?: number | boolean;
115
131
  /**
116
132
  * Force a tier override on the new patch's `PatchMetadata.tier`. Only
117
133
  * `"branding"` is currently recognised — Commander rejects other values
@@ -144,6 +160,12 @@ export interface ResetOptions {
144
160
  * Options for the discard command.
145
161
  */
146
162
  export interface DiscardOptions {
163
+ /**
164
+ * Restore to pristine upstream (HEAD) instead of the patch-applied
165
+ * baseline; deletes patch-created files (the pre-0.39.0 semantics —
166
+ * FORGE F1).
167
+ */
168
+ toUpstream?: boolean;
147
169
  /** Show what would be discarded without doing it */
148
170
  dryRun?: boolean;
149
171
  /** Skip confirmation prompt */
@@ -251,6 +273,12 @@ export interface ReExportOptions {
251
273
  * patch to also suppress X").
252
274
  */
253
275
  lintIgnore?: string[];
276
+ /**
277
+ * Parsed `--wait-lock [seconds]` value (`true` for the bare flag, meaning
278
+ * 60). Consumed at the CLI layer to bound the engine-session-lock wait;
279
+ * the command implementation ignores it.
280
+ */
281
+ waitLock?: number | boolean;
254
282
  }
255
283
  /**
256
284
  * Options for the `fireforge patch tier` subcommand. Sets or clears the
@@ -350,13 +378,31 @@ export interface PatchSplitOptions {
350
378
  skipLint?: boolean;
351
379
  }
352
380
  /**
353
- * Options for the preview-only `fireforge patch move-files` subcommand.
354
- * It validates an ownership transfer and prints the explicit
355
- * `re-export --files` commands needed to perform it.
381
+ * Options for the `fireforge patch move-files` subcommand. Without
382
+ * `--create` it is preview-only: it validates an ownership transfer and
383
+ * prints the explicit `re-export --files` commands needed to perform it.
384
+ * With `--create --order <n>` the target patch is created at that order
385
+ * and the files move into it as one split-style transaction.
356
386
  */
357
387
  export interface PatchMoveFilesOptions {
358
388
  /** File paths relative to engine/ to move from the source patch to the target patch. */
359
389
  file?: string[];
390
+ /** Create the target patch (transactional bootstrap of a split). Requires `order`. */
391
+ create?: boolean;
392
+ /** Exact sparse order for the created patch. Only valid with `create`. */
393
+ order?: number;
394
+ /** Category for the created patch; defaults to the source patch's. */
395
+ category?: string;
396
+ /** Description for the created patch. */
397
+ description?: string;
398
+ /** Preview the create+move without writing. */
399
+ dryRun?: boolean;
400
+ /** Skip interactive confirmation (required for non-TTY). */
401
+ yes?: boolean;
402
+ /** Bypass projected-lint refusals. */
403
+ forceUnsafe?: boolean;
404
+ /** Skip per-patch lint of the projected bodies. */
405
+ skipLint?: boolean;
360
406
  }
361
407
  /**
362
408
  * Options for the rebase command.
@@ -457,6 +503,14 @@ export interface TestOptions {
457
503
  killStaleMarionette?: boolean;
458
504
  /** Permit tests against packageable engine edits newer than the last successful build. */
459
505
  allowStaleBuild?: boolean;
506
+ /**
507
+ * Permit tests despite `components.conf` changes that only a full
508
+ * `fireforge build` compiles into the StaticComponents table — the
509
+ * packaged child process will resolve the OLD table. For operators who
510
+ * rebuilt out-of-band and accept the risk; distinct from
511
+ * `allowStaleBuild`, which only accepts stale packaged content.
512
+ */
513
+ allowStaleComponents?: boolean;
460
514
  /** Run the configured short harness canary. `true` means use `fireforge.json`'s test.canaryPath. */
461
515
  canary?: string | boolean;
462
516
  /**
@@ -487,6 +541,12 @@ export interface TestOptions {
487
541
  * checkers that consume a sample artifact after the run.
488
542
  */
489
543
  perfSamples?: string;
544
+ /**
545
+ * Parsed `--wait-lock [seconds]` value (`true` for the bare flag, meaning
546
+ * 60). Consumed at the CLI layer to bound the engine-session-lock wait;
547
+ * the command implementation ignores it.
548
+ */
549
+ waitLock?: number | boolean;
490
550
  }
491
551
  /**
492
552
  * Options for the furnace apply command.
@@ -741,6 +801,12 @@ export interface StatusOptions {
741
801
  * patch as an ownership conflict.
742
802
  */
743
803
  ownership?: boolean;
804
+ /**
805
+ * Print the recorded test-packaging coverage of the last build baseline
806
+ * (scope, timestamp, recording invocation) and exit. Read-only — the
807
+ * counterpart to the out-of-coverage test refusal (FORGE F11).
808
+ */
809
+ testCoverage?: boolean;
744
810
  /** Output machine-readable JSON instead of human-readable text. */
745
811
  json?: boolean;
746
812
  }
@@ -12,6 +12,13 @@ export interface FirefoxConfig {
12
12
  product: FirefoxProduct;
13
13
  /** Optional pinned SHA-256 for the resolved source archive */
14
14
  sha256?: string;
15
+ /**
16
+ * Optional release-candidate build directory (e.g. "build2"). When set,
17
+ * the source archive resolves under
18
+ * `pub/<product>/candidates/<version>-candidates/<candidate>/` instead of
19
+ * `pub/<product>/releases/<version>/`, for pre-release verification.
20
+ */
21
+ candidate?: string;
15
22
  }
16
23
  /**
17
24
  * Supported project license SPDX identifiers.
@@ -174,6 +181,14 @@ export interface TypecheckConfig {
174
181
  * mismatch). A narrowed project sets `null` here to stay clean.
175
182
  */
176
183
  projectOverrides?: Record<string, string | null>;
184
+ /**
185
+ * How to report undefined free identifiers (TS2304/TS2552). These were
186
+ * historically suppressed as shim-gap noise, which also hid genuine
187
+ * missing imports until they crashed at runtime (FORGE F12). Default
188
+ * `'warning'`: visible without failing the gate. `'error'` makes them
189
+ * blocking; `'off'` restores the historical suppression.
190
+ */
191
+ undefinedIdentifiers?: PatchLintSeverityGate;
177
192
  }
178
193
  /**
179
194
  * Wire command configuration.
@@ -255,6 +270,13 @@ export interface PatchLintConfig {
255
270
  * 'off'.
256
271
  */
257
272
  chromeScriptJsDoc?: PatchLintSeverityGate;
273
+ /**
274
+ * How the checkJs pass reports undefined free identifiers
275
+ * (TS2304/TS2552). Same semantics as `typecheck.undefinedIdentifiers`
276
+ * (FORGE F12); the two flows share the suppression policy so a patch
277
+ * cannot pass one and fail the other. Default: 'warning'.
278
+ */
279
+ undefinedIdentifiers?: PatchLintSeverityGate;
258
280
  }
259
281
  /**
260
282
  * Build mode for mach.
@@ -6,6 +6,7 @@
6
6
  export declare function pickDefined<T extends Record<string, unknown>>(obj: T): {
7
7
  [K in keyof T]+?: Exclude<T[K], undefined>;
8
8
  };
9
+ import type { Command } from 'commander';
9
10
  /**
10
11
  * Wraps an option-argument parser so its failures surface through
11
12
  * commander's own invalid-argument channel.
@@ -21,3 +22,22 @@ export declare function pickDefined<T extends Record<string, unknown>>(obj: T):
21
22
  * helper (or throw commander's InvalidArgumentError directly).
22
23
  */
23
24
  export declare function commanderArgParser<T>(parse: (raw: string) => T): (raw: string) => T;
25
+ /**
26
+ * Resolves the parsed `--wait-lock [seconds]` option value into a wait budget
27
+ * in seconds for `withEngineSessionLock`:
28
+ * - absent (`undefined`) → `undefined` (legacy ~1 s fail-fast),
29
+ * - bare flag (`true`) → {@link DEFAULT_WAIT_LOCK_SECONDS},
30
+ * - explicit value → integer validated into 1..3600.
31
+ *
32
+ * Accepts the raw string too, so it doubles as the option's arg parser (wrap
33
+ * with {@link commanderArgParser} so failures surface through commander's
34
+ * invalid-argument channel).
35
+ */
36
+ export declare function resolveWaitLockSeconds(value: string | number | boolean | undefined): number | undefined;
37
+ /**
38
+ * Registers the shared `--wait-lock [seconds]` flag on an engine-mutating
39
+ * command. The parsed option value is `true` for the bare flag or a validated
40
+ * integer; feed it through {@link resolveWaitLockSeconds} at the call site to
41
+ * obtain the `waitLockSeconds` for `withEngineSessionLock`.
42
+ */
43
+ export declare function addWaitLockOption(command: Command): Command;
@@ -14,6 +14,7 @@ export function pickDefined(obj) {
14
14
  return result;
15
15
  }
16
16
  import { InvalidArgumentError as CommanderInvalidArgumentError } from 'commander';
17
+ import { GeneralError } from '../errors/base.js';
17
18
  /**
18
19
  * Wraps an option-argument parser so its failures surface through
19
20
  * commander's own invalid-argument channel.
@@ -38,3 +39,41 @@ export function commanderArgParser(parse) {
38
39
  }
39
40
  };
40
41
  }
42
+ /** Wait budget applied when `--wait-lock` is passed without a value. */
43
+ const DEFAULT_WAIT_LOCK_SECONDS = 60;
44
+ /**
45
+ * Resolves the parsed `--wait-lock [seconds]` option value into a wait budget
46
+ * in seconds for `withEngineSessionLock`:
47
+ * - absent (`undefined`) → `undefined` (legacy ~1 s fail-fast),
48
+ * - bare flag (`true`) → {@link DEFAULT_WAIT_LOCK_SECONDS},
49
+ * - explicit value → integer validated into 1..3600.
50
+ *
51
+ * Accepts the raw string too, so it doubles as the option's arg parser (wrap
52
+ * with {@link commanderArgParser} so failures surface through commander's
53
+ * invalid-argument channel).
54
+ */
55
+ export function resolveWaitLockSeconds(value) {
56
+ if (value === undefined || value === false) {
57
+ return undefined;
58
+ }
59
+ if (value === true) {
60
+ return DEFAULT_WAIT_LOCK_SECONDS;
61
+ }
62
+ if (typeof value === 'number') {
63
+ return value;
64
+ }
65
+ const n = Number.parseInt(value, 10);
66
+ if (!Number.isFinite(n) || n < 1 || n > 3600) {
67
+ throw new GeneralError(`--wait-lock must be an integer in 1..3600 (got "${value}")`);
68
+ }
69
+ return n;
70
+ }
71
+ /**
72
+ * Registers the shared `--wait-lock [seconds]` flag on an engine-mutating
73
+ * command. The parsed option value is `true` for the bare flag or a validated
74
+ * integer; feed it through {@link resolveWaitLockSeconds} at the call site to
75
+ * obtain the `waitLockSeconds` for `withEngineSessionLock`.
76
+ */
77
+ export function addWaitLockOption(command) {
78
+ return command.option('--wait-lock [seconds]', 'Wait up to this many seconds (default 60) for another FireForge engine-mutating command to release the engine lock, instead of failing immediately', commanderArgParser((raw) => resolveWaitLockSeconds(raw)));
79
+ }
@@ -57,6 +57,11 @@ export declare function isArray(value: unknown): value is unknown[];
57
57
  * Accepts formats like "140.9.0", "140.9.1", "140.9.0esr", "147.0b1"
58
58
  */
59
59
  export declare function isValidFirefoxVersion(version: string): boolean;
60
+ /**
61
+ * Validates a release-candidate build directory name.
62
+ * Accepts formats like "build1", "build2", "build12" (no leading zero).
63
+ */
64
+ export declare function isValidFirefoxCandidate(candidate: string): boolean;
60
65
  /**
61
66
  * Validates a Firefox product string.
62
67
  * Accepts: firefox, firefox-esr, firefox-beta, firefox-devedition
@@ -87,6 +87,13 @@ export function isValidFirefoxVersion(version) {
87
87
  // Beta: 147.0b1, 147.0b2
88
88
  return /^[1-9]\d{0,2}\.\d+(?:b[1-9]\d*|\.\d+(?:esr)?|esr)?$/.test(version);
89
89
  }
90
+ /**
91
+ * Validates a release-candidate build directory name.
92
+ * Accepts formats like "build1", "build2", "build12" (no leading zero).
93
+ */
94
+ export function isValidFirefoxCandidate(candidate) {
95
+ return /^build[1-9]\d{0,3}$/.test(candidate);
96
+ }
90
97
  /**
91
98
  * Validates a Firefox product string.
92
99
  * Accepts: firefox, firefox-esr, firefox-beta, firefox-devedition
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hominis/fireforge",
3
- "version": "0.37.0",
3
+ "version": "0.39.0",
4
4
  "description": "FireForge — a build tool for customizing Firefox",
5
5
  "type": "module",
6
6
  "main": "./dist/src/index.js",
@@ -73,9 +73,9 @@
73
73
  "eslint-plugin-simple-import-sort": "^13.0.0",
74
74
  "fast-check": "^4.6.0",
75
75
  "husky": "^9.1.7",
76
- "knip": "6.25.0",
76
+ "knip": "6.26.0",
77
77
  "lint-staged": "^17.0.4",
78
- "prettier": "3.9.4",
78
+ "prettier": "3.9.5",
79
79
  "tsx": "^4.7.0",
80
80
  "typescript": "~6.0.0",
81
81
  "typescript-eslint": "^8.0.0",