@hominis/fireforge 0.30.1 → 0.32.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 (152) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/README.md +22 -0
  3. package/dist/src/commands/export-all.js +9 -16
  4. package/dist/src/commands/export-flow.d.ts +6 -0
  5. package/dist/src/commands/export-flow.js +6 -1
  6. package/dist/src/commands/export-placement-gate.d.ts +38 -0
  7. package/dist/src/commands/export-placement-gate.js +105 -0
  8. package/dist/src/commands/export-shared.d.ts +28 -0
  9. package/dist/src/commands/export-shared.js +46 -1
  10. package/dist/src/commands/export.js +52 -113
  11. package/dist/src/commands/furnace/chrome-doc-templates.d.ts +0 -13
  12. package/dist/src/commands/furnace/chrome-doc-templates.js +1 -1
  13. package/dist/src/commands/furnace/create-dry-run.d.ts +1 -1
  14. package/dist/src/commands/furnace/create.d.ts +1 -2
  15. package/dist/src/commands/furnace/deploy.js +36 -114
  16. package/dist/src/commands/furnace/refresh.js +52 -32
  17. package/dist/src/commands/furnace/sync.js +2 -0
  18. package/dist/src/commands/import.js +108 -73
  19. package/dist/src/commands/lint-per-patch.d.ts +3 -1
  20. package/dist/src/commands/lint-per-patch.js +265 -74
  21. package/dist/src/commands/lint.d.ts +1 -58
  22. package/dist/src/commands/lint.js +193 -88
  23. package/dist/src/commands/patch/compact.d.ts +5 -2
  24. package/dist/src/commands/patch/compact.js +85 -25
  25. package/dist/src/commands/patch/delete.js +17 -17
  26. package/dist/src/commands/patch/index.js +2 -0
  27. package/dist/src/commands/patch/lint-ignore.js +3 -16
  28. package/dist/src/commands/patch/move-files.js +2 -0
  29. package/dist/src/commands/patch/patch-context.d.ts +41 -0
  30. package/dist/src/commands/patch/patch-context.js +53 -0
  31. package/dist/src/commands/patch/rename.js +10 -15
  32. package/dist/src/commands/patch/reorder.d.ts +0 -2
  33. package/dist/src/commands/patch/reorder.js +18 -19
  34. package/dist/src/commands/patch/split-plan.d.ts +66 -0
  35. package/dist/src/commands/patch/split-plan.js +178 -0
  36. package/dist/src/commands/patch/split.d.ts +30 -0
  37. package/dist/src/commands/patch/split.js +283 -0
  38. package/dist/src/commands/patch/staged-dependency.d.ts +1 -7
  39. package/dist/src/commands/patch/staged-dependency.js +4 -17
  40. package/dist/src/commands/patch/tier.js +4 -17
  41. package/dist/src/commands/re-export-files.js +4 -1
  42. package/dist/src/commands/re-export-scan.js +8 -1
  43. package/dist/src/commands/re-export.js +8 -1
  44. package/dist/src/commands/rebase/summary.d.ts +1 -5
  45. package/dist/src/commands/rebase/summary.js +1 -1
  46. package/dist/src/commands/status-output.js +77 -68
  47. package/dist/src/commands/test-diagnose.d.ts +23 -0
  48. package/dist/src/commands/test-diagnose.js +210 -0
  49. package/dist/src/commands/test-run.d.ts +68 -0
  50. package/dist/src/commands/test-run.js +97 -0
  51. package/dist/src/commands/test.js +214 -263
  52. package/dist/src/commands/token.js +15 -1
  53. package/dist/src/commands/wire.js +109 -78
  54. package/dist/src/core/build-audit.d.ts +1 -1
  55. package/dist/src/core/build-audit.js +2 -46
  56. package/dist/src/core/build-baseline-types.d.ts +38 -0
  57. package/dist/src/core/build-baseline-types.js +10 -0
  58. package/dist/src/core/build-baseline.d.ts +1 -31
  59. package/dist/src/core/build-prepare.d.ts +1 -1
  60. package/dist/src/core/build-prepare.js +2 -45
  61. package/dist/src/core/config-paths.d.ts +0 -8
  62. package/dist/src/core/config-paths.js +4 -4
  63. package/dist/src/core/config-state.d.ts +0 -6
  64. package/dist/src/core/config-state.js +1 -1
  65. package/dist/src/core/config-validate-patch-policy.js +12 -13
  66. package/dist/src/core/config-validate.js +74 -28
  67. package/dist/src/core/engine-changes.d.ts +24 -0
  68. package/dist/src/core/engine-changes.js +64 -0
  69. package/dist/src/core/firefox-cache.d.ts +0 -5
  70. package/dist/src/core/firefox-cache.js +1 -1
  71. package/dist/src/core/firefox-download.d.ts +0 -6
  72. package/dist/src/core/firefox-download.js +1 -1
  73. package/dist/src/core/furnace-apply-helpers.d.ts +1 -8
  74. package/dist/src/core/furnace-apply-helpers.js +11 -20
  75. package/dist/src/core/furnace-apply.d.ts +1 -1
  76. package/dist/src/core/furnace-apply.js +1 -1
  77. package/dist/src/core/furnace-checksum-utils.d.ts +7 -0
  78. package/dist/src/core/furnace-checksum-utils.js +15 -0
  79. package/dist/src/core/furnace-config-validate.d.ts +31 -0
  80. package/dist/src/core/furnace-config-validate.js +133 -0
  81. package/dist/src/core/furnace-config.d.ts +4 -32
  82. package/dist/src/core/furnace-config.js +15 -111
  83. package/dist/src/core/furnace-constants.d.ts +0 -10
  84. package/dist/src/core/furnace-constants.js +2 -2
  85. package/dist/src/core/furnace-css-fragments.d.ts +79 -0
  86. package/dist/src/core/furnace-css-fragments.js +243 -0
  87. package/dist/src/core/furnace-jsconfig.d.ts +63 -0
  88. package/dist/src/core/furnace-jsconfig.js +191 -0
  89. package/dist/src/core/furnace-validate-helpers.d.ts +16 -14
  90. package/dist/src/core/furnace-validate-helpers.js +40 -1
  91. package/dist/src/core/furnace-validate-registration.js +16 -1
  92. package/dist/src/core/furnace-validate.js +54 -2
  93. package/dist/src/core/git-base.d.ts +15 -0
  94. package/dist/src/core/git-base.js +32 -0
  95. package/dist/src/core/git-diff.d.ts +8 -0
  96. package/dist/src/core/git-diff.js +224 -59
  97. package/dist/src/core/git-file-ops.d.ts +39 -12
  98. package/dist/src/core/git-file-ops.js +84 -3
  99. package/dist/src/core/lint-cache.d.ts +0 -13
  100. package/dist/src/core/lint-cache.js +5 -5
  101. package/dist/src/core/mach.d.ts +22 -1
  102. package/dist/src/core/mach.js +27 -2
  103. package/dist/src/core/manifest-register.d.ts +5 -16
  104. package/dist/src/core/manifest-register.js +3 -1
  105. package/dist/src/core/patch-lint-checkjs.d.ts +75 -21
  106. package/dist/src/core/patch-lint-checkjs.js +263 -71
  107. package/dist/src/core/patch-lint-css.d.ts +23 -0
  108. package/dist/src/core/patch-lint-css.js +172 -0
  109. package/dist/src/core/patch-lint-jsdoc.js +63 -4
  110. package/dist/src/core/patch-lint-observer.d.ts +37 -0
  111. package/dist/src/core/patch-lint-observer.js +168 -0
  112. package/dist/src/core/patch-lint.d.ts +34 -11
  113. package/dist/src/core/patch-lint.js +24 -161
  114. package/dist/src/core/patch-manifest-io.d.ts +16 -0
  115. package/dist/src/core/patch-manifest-io.js +44 -2
  116. package/dist/src/core/patch-manifest-validate.d.ts +1 -8
  117. package/dist/src/core/patch-manifest-validate.js +1 -1
  118. package/dist/src/core/patch-manifest.d.ts +1 -1
  119. package/dist/src/core/patch-manifest.js +1 -1
  120. package/dist/src/core/patch-policy.d.ts +0 -4
  121. package/dist/src/core/patch-policy.js +10 -4
  122. package/dist/src/core/register-browser-content.d.ts +1 -1
  123. package/dist/src/core/register-module.d.ts +1 -1
  124. package/dist/src/core/register-result.d.ts +21 -0
  125. package/dist/src/core/register-result.js +9 -0
  126. package/dist/src/core/register-shared-css.d.ts +1 -1
  127. package/dist/src/core/register-test-manifest.d.ts +1 -1
  128. package/dist/src/core/test-harness-crash.d.ts +61 -0
  129. package/dist/src/core/test-harness-crash.js +140 -0
  130. package/dist/src/core/test-stale-check.d.ts +1 -1
  131. package/dist/src/core/test-stale-check.js +2 -46
  132. package/dist/src/core/test-xpcshell-retry.d.ts +9 -2
  133. package/dist/src/core/test-xpcshell-retry.js +10 -3
  134. package/dist/src/core/token-dark-mode.js +14 -26
  135. package/dist/src/core/token-manager.d.ts +4 -0
  136. package/dist/src/core/token-manager.js +70 -16
  137. package/dist/src/core/typecheck-shim.d.ts +3 -22
  138. package/dist/src/core/typecheck-shim.js +69 -7
  139. package/dist/src/core/wire-utils.js +37 -44
  140. package/dist/src/types/commands/index.d.ts +1 -1
  141. package/dist/src/types/commands/options.d.ts +122 -0
  142. package/dist/src/types/config.d.ts +11 -2
  143. package/dist/src/types/furnace.d.ts +12 -1
  144. package/dist/src/utils/elapsed.d.ts +0 -2
  145. package/dist/src/utils/elapsed.js +1 -1
  146. package/dist/src/utils/fs.d.ts +0 -5
  147. package/dist/src/utils/fs.js +1 -1
  148. package/dist/src/utils/regex.d.ts +0 -6
  149. package/dist/src/utils/regex.js +3 -3
  150. package/dist/src/utils/validation.d.ts +0 -8
  151. package/dist/src/utils/validation.js +2 -2
  152. package/package.json +6 -4
@@ -60,9 +60,7 @@ export function coerceToCall(expression) {
60
60
  */
61
61
  export function countBraceDepth(line, inBlockComment) {
62
62
  let depth = 0;
63
- let inSingle = false;
64
- let inDouble = false;
65
- let inTemplate = false;
63
+ let quote = null;
66
64
  let inLine = false;
67
65
  let inBlock = inBlockComment;
68
66
  for (let i = 0; i < line.length; i++) {
@@ -77,23 +75,13 @@ export function countBraceDepth(line, inBlockComment) {
77
75
  }
78
76
  continue;
79
77
  }
80
- if (ch === '\\' && (inSingle || inDouble || inTemplate)) {
78
+ if (ch === '\\' && quote !== null) {
81
79
  i++;
82
80
  continue;
83
81
  }
84
- if (inSingle) {
85
- if (ch === "'")
86
- inSingle = false;
87
- continue;
88
- }
89
- if (inDouble) {
90
- if (ch === '"')
91
- inDouble = false;
92
- continue;
93
- }
94
- if (inTemplate) {
95
- if (ch === '`')
96
- inTemplate = false;
82
+ if (quote !== null) {
83
+ if (ch === quote)
84
+ quote = null;
97
85
  continue;
98
86
  }
99
87
  if (ch === '/' && next === '/') {
@@ -105,35 +93,12 @@ export function countBraceDepth(line, inBlockComment) {
105
93
  i++;
106
94
  continue;
107
95
  }
108
- // Heuristic for regex literals: if / follows an operator or keyword boundary,
109
- // treat it as a regex start and skip to the closing /
110
- if (ch === '/' && next !== undefined) {
111
- const prev = i > 0 ? line[i - 1] : undefined;
112
- if (prev === undefined || /[=(:,!|&?;~^{[\n+\-*%<>]/.test(prev)) {
113
- // Skip to closing /
114
- i++;
115
- while (i < line.length) {
116
- if (line[i] === '\\') {
117
- i++; // skip escaped character
118
- }
119
- else if (line[i] === '/') {
120
- break;
121
- }
122
- i++;
123
- }
124
- continue;
125
- }
126
- }
127
- if (ch === "'") {
128
- inSingle = true;
129
- continue;
130
- }
131
- if (ch === '"') {
132
- inDouble = true;
96
+ if (ch === '/' && next !== undefined && isRegexLiteralStart(line, i)) {
97
+ i = scanToRegexLiteralEnd(line, i);
133
98
  continue;
134
99
  }
135
- if (ch === '`') {
136
- inTemplate = true;
100
+ if (ch === "'" || ch === '"' || ch === '`') {
101
+ quote = ch;
137
102
  continue;
138
103
  }
139
104
  if (ch === '{')
@@ -143,6 +108,34 @@ export function countBraceDepth(line, inBlockComment) {
143
108
  }
144
109
  return { depth, inBlockComment: inBlock };
145
110
  }
111
+ /**
112
+ * Regex-literal opener heuristic for the fallback scanner: a `/` is
113
+ * treated as starting a regex when it follows an operator or
114
+ * keyword-boundary character (or starts the line). See the misfire notes
115
+ * on {@link countBraceDepth}.
116
+ */
117
+ function isRegexLiteralStart(line, i) {
118
+ const prev = i > 0 ? line[i - 1] : undefined;
119
+ return prev === undefined || /[=(:,!|&?;~^{[\n+\-*%<>]/.test(prev);
120
+ }
121
+ /**
122
+ * Scans from the opening `/` of a regex literal to its closing `/`
123
+ * (honouring escapes), returning the index of the closing slash — or the
124
+ * end of the line when the literal never closes.
125
+ */
126
+ function scanToRegexLiteralEnd(line, start) {
127
+ let i = start + 1;
128
+ while (i < line.length) {
129
+ if (line[i] === '\\') {
130
+ i++; // skip escaped character
131
+ }
132
+ else if (line[i] === '/') {
133
+ break;
134
+ }
135
+ i++;
136
+ }
137
+ return i;
138
+ }
146
139
  /**
147
140
  * Extracts the class/object name from an expression like "MyComponent.init()".
148
141
  */
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Re-exports all command-related types from focused sub-modules.
3
3
  */
4
- export type { BuildOptions, DiscardOptions, DoctorOptions, DownloadOptions, ExportOptions, FurnaceApplyOptions, FurnaceCreateOptions, FurnaceDeployOptions, FurnaceOverrideOptions, FurnacePreviewOptions, FurnaceRefreshOptions, FurnaceRemoveOptions, FurnaceSyncOptions, FurnaceValidateOptions, GlobalOptions, ImportOptions, PackageOptions, PatchCompactOptions, PatchDeleteOptions, PatchLintIgnoreOptions, PatchMoveFilesOptions, PatchRenameOptions, PatchReorderOptions, PatchStagedDependencyOptions, PatchTierOptions, RebaseOptions, ReExportOptions, RegisterOptions, ResetOptions, RunOptions, SetupOptions, SourceSetOptions, StatusOptions, TestOptions, TokenAddOptions, WireOptions, } from './options.js';
4
+ export type { BuildOptions, DiscardOptions, DoctorOptions, DownloadOptions, ExportOptions, FurnaceApplyOptions, FurnaceCreateOptions, FurnaceDeployOptions, FurnaceOverrideOptions, FurnacePreviewOptions, FurnaceRefreshOptions, FurnaceRemoveOptions, FurnaceSyncOptions, FurnaceValidateOptions, GlobalOptions, ImportOptions, LintCommandOptions, PackageOptions, PatchCompactOptions, PatchDeleteOptions, PatchLintIgnoreOptions, PatchMoveFilesOptions, PatchRenameOptions, PatchReorderOptions, PatchSplitOptions, PatchStagedDependencyOptions, PatchTierOptions, RebaseOptions, ReExportOptions, RegisterOptions, ResetOptions, RunOptions, SetupOptions, SourceSetOptions, StatusOptions, TestOptions, TokenAddOptions, WireOptions, } from './options.js';
5
5
  export type { ImportSummary, PatchCategory, PatchesManifest, PatchInfo, PatchLintIssue, PatchMetadata, PatchResult, PatchStagedDependencies, PatchStagedForwardImport, } from './patches.js';
6
6
  export type { DoctorCheck, ProjectStatus, TokenCoverageFileEntry, TokenCoverageReport, } from './project.js';
@@ -304,6 +304,33 @@ export interface PatchStagedDependencyOptions {
304
304
  * It validates an ownership transfer and prints the explicit
305
305
  * `re-export --files` commands needed to perform it.
306
306
  */
307
+ /**
308
+ * Options for the `patch split` command.
309
+ */
310
+ export interface PatchSplitOptions {
311
+ /** Files to move out of the source patch (engine-relative). */
312
+ files: string[];
313
+ /** Name for the new patch (used in its filename slug). */
314
+ name: string;
315
+ /** Category for the new patch; defaults to the source patch's category. */
316
+ category?: string;
317
+ /** Description for the new patch. */
318
+ description?: string;
319
+ /** Exact sparse order for the new patch (mutually exclusive with before/after). */
320
+ order?: number;
321
+ /** Place the new patch before this patch identifier. */
322
+ before?: string;
323
+ /** Place the new patch after this patch identifier (default: the source). */
324
+ after?: string;
325
+ /** Preview without writing. */
326
+ dryRun?: boolean;
327
+ /** Skip interactive confirmation (required for non-TTY). */
328
+ yes?: boolean;
329
+ /** Bypass projected-lint refusals. */
330
+ forceUnsafe?: boolean;
331
+ /** Skip per-patch lint of the projected bodies. */
332
+ skipLint?: boolean;
333
+ }
307
334
  export interface PatchMoveFilesOptions {
308
335
  /** File paths relative to engine/ to move from the source patch to the target patch. */
309
336
  file?: string[];
@@ -394,6 +421,34 @@ export interface TestOptions {
394
421
  * stale process holds the default port or CI uses another port.
395
422
  */
396
423
  marionettePort?: number;
424
+ /**
425
+ * Retry budget for recognized harness crashes (resource-monitor
426
+ * tracebacks, pre-test no-output hangs, post-green shutdown re-entry).
427
+ * 0 disables retries. Defaults to {@link DEFAULT_HARNESS_RETRIES} at the
428
+ * command layer.
429
+ */
430
+ harnessRetries?: number;
431
+ /**
432
+ * Force dispatch through the generic `mach test` command instead of the
433
+ * suite-specific `mach xpcshell-test` / `mach mochitest` commands a
434
+ * single-suite run auto-selects. Escape hatch for the rare case where a
435
+ * suite-specific command misbehaves; on a healthy host the generic command
436
+ * is equivalent. The default (auto suite dispatch) skips the mozlog
437
+ * resource monitor that crashes `mach test` on a broken host (E1).
438
+ */
439
+ genericMachTest?: boolean;
440
+ /**
441
+ * Commander negation flag for `--no-shard`. When false, multiple test
442
+ * paths run in one combined mach invocation; by default they shard into
443
+ * sequential single-file harness runs with an aggregate report.
444
+ */
445
+ shard?: boolean;
446
+ /**
447
+ * Project-relative (or absolute) artifact path published to the harness
448
+ * run as `<BINARYNAME>_PERF_SAMPLE_JSON`, for downstream perf-budget
449
+ * checkers that consume a sample artifact after the run.
450
+ */
451
+ perfSamples?: string;
397
452
  }
398
453
  /**
399
454
  * Options for the furnace apply command.
@@ -648,6 +703,8 @@ export interface TokenAddOptions {
648
703
  description?: string;
649
704
  darkValue?: string;
650
705
  dryRun?: boolean;
706
+ /** Declare the category banner in the tokens CSS when it does not exist yet. */
707
+ createCategory?: boolean;
651
708
  }
652
709
  /**
653
710
  * Options for the doctor command.
@@ -681,3 +738,68 @@ export interface GlobalOptions {
681
738
  /** Enable verbose/debug output */
682
739
  verbose?: boolean;
683
740
  }
741
+ /** Options controlling how the lint command filters and tags its output. */
742
+ export interface LintCommandOptions {
743
+ /**
744
+ * When set, tag each issue as `introduced` or `cumulative` based on
745
+ * whether its file changed since this git revision (e.g. `HEAD`, a
746
+ * branch name, or a SHA). Issues are not filtered — the full set still
747
+ * prints — but a diff-scoped summary makes it trivial to see which
748
+ * errors the current task introduced.
749
+ */
750
+ since?: string;
751
+ /**
752
+ * When set together with {@link since}, scope the exit code to issues
753
+ * tagged `introduced`. Cumulative pre-existing errors still print (so
754
+ * the operator can still see the full queue state) but do not fail
755
+ * lint. Motivating case: a branch whose diff is clean but whose repo
756
+ * already carries unrelated `raw-color` / license-header errors from
757
+ * older patches. Without this flag, CI treats the clean branch as
758
+ * failing; with it, a branch "breaks the build" only when its own diff
759
+ * introduced a new error.
760
+ *
761
+ * Requires {@link since}: without a revision to diff against there is
762
+ * no distinction between introduced and cumulative, so the flag is
763
+ * rejected up-front rather than silently ignored.
764
+ */
765
+ onlyIntroduced?: boolean;
766
+ /**
767
+ * Lint each patch in the queue as its own isolated diff, rather than
768
+ * the aggregate `git diff HEAD` across all applied patches.
769
+ *
770
+ * Motivating case: running `fireforge lint` (no args) on a repo where
771
+ * `fireforge import` or `fireforge rebase` has just applied the full
772
+ * patch queue produces an aggregate diff (every patch's changes
773
+ * summed). The patch-size advisory rules (`large-patch-lines`,
774
+ * `large-patch-files`) then fire against the sum — e.g. "Patch is
775
+ * 37529 lines" on a queue of 22 individually-fine patches — which
776
+ * reads as a task-specific regression when it is really an artefact
777
+ * of the aggregation. `--per-patch` rescopes the diff to each patch's
778
+ * own `filesAffected`, honours the patch's own `lintIgnore`, and runs
779
+ * the cross-patch rules once over the whole queue so queue-level
780
+ * findings (duplicate creations, forward imports) still surface.
781
+ *
782
+ * Mutually exclusive with passing explicit file paths — the two
783
+ * scope contracts are different.
784
+ */
785
+ perPatch?: boolean;
786
+ /**
787
+ * Restrict `--per-patch` to a named subset of the queue (by filename,
788
+ * filename ± `.patch`, or manifest `name`). Lets a change that touches a
789
+ * handful of patches run the per-patch gate over just those instead of
790
+ * the full ~90-patch queue. Only valid with {@link perPatch}; queue-level
791
+ * findings (policy, cross-patch) are scoped to files the subset touches.
792
+ */
793
+ patches?: string[];
794
+ /**
795
+ * Maximum warning count tolerated before lint exits non-zero. Mirrors
796
+ * ESLint's `--max-warnings` shape for release gates that want advisory
797
+ * findings to become blocking without changing default CLI behavior.
798
+ */
799
+ maxWarnings?: number;
800
+ /**
801
+ * Bypass per-patch lint cache reads and writes. Accepted in aggregate mode
802
+ * for CLI consistency, but only `--per-patch` currently uses the cache.
803
+ */
804
+ noCache?: boolean;
805
+ }
@@ -145,8 +145,15 @@ export type PatchLintSeverityGate = 'off' | 'warning' | 'error';
145
145
  /**
146
146
  * Allowlisted TypeScript `compilerOptions` overrides for the patch
147
147
  * `checkJs` pass when {@link PatchLintConfig.checkJsStrict} is true.
148
- * Merged after the strict preset; only boolean flags — no `paths`,
149
- * `rootDir`, or other options that would fight the synthetic program.
148
+ * Merged after the strict preset.
149
+ *
150
+ * Boolean flags tighten the strict preset. The optional `paths` mapping
151
+ * (each pattern may carry a single `*`) lets patch-owned modules be typed
152
+ * from their real sources — e.g. `"resource:///modules/foo/*": ["./*"]` —
153
+ * resolved host-side against the engine directory, so no `baseUrl` is set
154
+ * (TS5090-safe) and no hand-generated ambient stub shim is needed. Other
155
+ * options (`rootDir`, etc.) stay disallowed: they would fight the
156
+ * synthetic program.
150
157
  */
151
158
  export interface PatchLintCheckJsCompilerOptions {
152
159
  strictNullChecks?: boolean;
@@ -157,6 +164,8 @@ export interface PatchLintCheckJsCompilerOptions {
157
164
  strictPropertyInitialization?: boolean;
158
165
  noUnusedLocals?: boolean;
159
166
  noUnusedParameters?: boolean;
167
+ /** Module-resolution `paths` mapping (pattern → targets, engine-relative). */
168
+ paths?: Record<string, string[]>;
160
169
  }
161
170
  /**
162
171
  * Configuration for patch lint rules.
@@ -140,6 +140,15 @@ export interface FurnaceConfig {
140
140
  * Always includes `toolkit/content/widgets` by default.
141
141
  */
142
142
  scanPaths?: string[];
143
+ /**
144
+ * Project-relative path to a consumer-owned jsconfig/tsconfig whose
145
+ * `compilerOptions.paths` entries for deployed component modules
146
+ * (`chrome://global/content/elements/<file>.mjs`) Furnace keeps in sync
147
+ * on deploy. Only entries under that chrome prefix that map into
148
+ * `components/custom/` are managed; everything else is preserved.
149
+ * Unset disables jsconfig maintenance.
150
+ */
151
+ typecheckJsconfig?: string;
143
152
  /** Stock components tracked for preview */
144
153
  stock: string[];
145
154
  /** Override components */
@@ -234,7 +243,7 @@ export interface ApplyResult {
234
243
  */
235
244
  export interface DryRunAction {
236
245
  component: string;
237
- action: 'copy' | 'register-ce' | 'register-jar' | 'copy-ftl' | 'undeploy-remove' | 'undeploy-restore' | 'unregister-ce' | 'unregister-jar';
246
+ action: 'copy' | 'expand-fragments' | 'register-ce' | 'register-jar' | 'copy-ftl' | 'undeploy-remove' | 'undeploy-restore' | 'unregister-ce' | 'unregister-jar';
238
247
  source?: string;
239
248
  target?: string;
240
249
  description: string;
@@ -277,3 +286,5 @@ export interface ValidationIssue {
277
286
  /** Human-readable description of the issue */
278
287
  message: string;
279
288
  }
289
+ /** Resolved test-harness selection for a `furnace create` run. */
290
+ export type ResolvedTestStyle = 'mochikit' | 'browser-chrome' | 'xpcshell' | 'none';
@@ -1,4 +1,2 @@
1
- /** Formats elapsed milliseconds for progress messages. */
2
- export declare function formatElapsed(ms: number): string;
3
1
  /** Returns formatted elapsed time since a start timestamp from Date.now(). */
4
2
  export declare function elapsedSince(startedAt: number): string;
@@ -1,6 +1,6 @@
1
1
  // SPDX-License-Identifier: EUPL-1.2
2
2
  /** Formats elapsed milliseconds for progress messages. */
3
- export function formatElapsed(ms) {
3
+ function formatElapsed(ms) {
4
4
  const totalSeconds = Math.max(0, Math.round(ms / 1000));
5
5
  const minutes = Math.floor(totalSeconds / 60);
6
6
  const seconds = totalSeconds % 60;
@@ -13,11 +13,6 @@ export declare function pathExistsStrict(path: string): Promise<boolean>;
13
13
  * @param path - Directory path to ensure
14
14
  */
15
15
  export declare function ensureDir(path: string): Promise<void>;
16
- /**
17
- * Ensures the parent directory of a file exists.
18
- * @param filePath - Path to a file
19
- */
20
- export declare function ensureParentDir(filePath: string): Promise<void>;
21
16
  /**
22
17
  * Removes a directory recursively.
23
18
  * @param path - Directory path to remove
@@ -52,7 +52,7 @@ export async function ensureDir(path) {
52
52
  * Ensures the parent directory of a file exists.
53
53
  * @param filePath - Path to a file
54
54
  */
55
- export async function ensureParentDir(filePath) {
55
+ async function ensureParentDir(filePath) {
56
56
  const parent = dirname(filePath);
57
57
  await ensureDir(parent);
58
58
  }
@@ -2,12 +2,6 @@
2
2
  * Escapes special regex characters in a string.
3
3
  */
4
4
  export declare function escapeRegex(str: string): string;
5
- /** Matches hex color values like #fff, #ff00ff, #ff00ff80 (longest-first alternation) */
6
- export declare const CSS_HEX_COLOR: RegExp;
7
- /** Matches rgb() or rgba() function calls */
8
- export declare const CSS_RGB_COLOR: RegExp;
9
- /** Matches hsl() or hsla() function calls */
10
- export declare const CSS_HSL_COLOR: RegExp;
11
5
  /**
12
6
  * Returns true if the content contains any raw CSS color values (hex, rgb, hsl).
13
7
  */
@@ -6,11 +6,11 @@ export function escapeRegex(str) {
6
6
  return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
7
7
  }
8
8
  /** Matches hex color values like #fff, #ff00ff, #ff00ff80 (longest-first alternation) */
9
- export const CSS_HEX_COLOR = /#(?:[0-9a-fA-F]{8}|[0-9a-fA-F]{6}|[0-9a-fA-F]{3,4})\b/;
9
+ const CSS_HEX_COLOR = /#(?:[0-9a-fA-F]{8}|[0-9a-fA-F]{6}|[0-9a-fA-F]{3,4})\b/;
10
10
  /** Matches rgb() or rgba() function calls */
11
- export const CSS_RGB_COLOR = /\brgba?\s*\(/;
11
+ const CSS_RGB_COLOR = /\brgba?\s*\(/;
12
12
  /** Matches hsl() or hsla() function calls */
13
- export const CSS_HSL_COLOR = /\bhsla?\s*\(/;
13
+ const CSS_HSL_COLOR = /\bhsla?\s*\(/;
14
14
  // Global variants for counting (cached to avoid re-creation per call)
15
15
  const CSS_HEX_COLOR_G = new RegExp(CSS_HEX_COLOR.source, 'g');
16
16
  const CSS_RGB_COLOR_G = new RegExp(CSS_RGB_COLOR.source, 'g');
@@ -90,14 +90,6 @@ export declare const PATCH_CATEGORIES: readonly ["branding", "ui", "privacy", "s
90
90
  * Validates a patch category string.
91
91
  */
92
92
  export declare function isValidPatchCategory(category: string): category is (typeof PATCH_CATEGORIES)[number];
93
- /**
94
- * Checks whether a Firefox version string has an ESR suffix.
95
- */
96
- export declare function isEsrVersion(version: string): boolean;
97
- /**
98
- * Checks whether a Firefox version string is a beta version (e.g. "147.0b1").
99
- */
100
- export declare function isBetaVersion(version: string): boolean;
101
93
  /**
102
94
  * Infers the Firefox product type from a version string.
103
95
  * Returns undefined if no clear inference can be made.
@@ -134,13 +134,13 @@ export function isValidPatchCategory(category) {
134
134
  /**
135
135
  * Checks whether a Firefox version string has an ESR suffix.
136
136
  */
137
- export function isEsrVersion(version) {
137
+ function isEsrVersion(version) {
138
138
  return /esr$/i.test(version);
139
139
  }
140
140
  /**
141
141
  * Checks whether a Firefox version string is a beta version (e.g. "147.0b1").
142
142
  */
143
- export function isBetaVersion(version) {
143
+ function isBetaVersion(version) {
144
144
  return /b\d+$/.test(version);
145
145
  }
146
146
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hominis/fireforge",
3
- "version": "0.30.1",
3
+ "version": "0.32.0",
4
4
  "description": "FireForge — a build tool for customizing Firefox",
5
5
  "type": "module",
6
6
  "main": "./dist/src/index.js",
@@ -24,6 +24,8 @@
24
24
  "lint:ci": "eslint src/ bin/ scripts/ eslint.config.js vitest.config.ts --max-warnings 0",
25
25
  "lint:fix": "eslint src/ bin/ scripts/ eslint.config.js vitest.config.ts --fix",
26
26
  "typecheck": "tsc --noEmit -p tsconfig.json",
27
+ "deadcode:check": "knip",
28
+ "cycles:check": "dpdm --no-output --no-tree --no-warning --exit-code circular:1 src/index.ts bin/fireforge.ts",
27
29
  "test": "vitest run",
28
30
  "test:package": "npm run pack:verify",
29
31
  "test:watch": "vitest",
@@ -36,7 +38,7 @@
36
38
  "format:check": "prettier --check --ignore-unknown \"src/**/*.ts\" \"bin/**/*.ts\" \"scripts/**/*.mjs\" \"package.json\" \"eslint.config.js\" \"vitest.config.ts\" \"tsconfig.json\" \"tsconfig.build.json\" \".lintstagedrc.json\" \".prettierignore\" \".prettierrc\" \".gitignore\" \"README.md\" \"CHANGELOG.md\"",
37
39
  "prepare": "node -e \"import('./scripts/prepare.mjs').catch((error) => { if (error && typeof error === 'object' && 'code' in error && error.code === 'ERR_MODULE_NOT_FOUND') process.exit(0); throw error; })\"",
38
40
  "prepack": "npm run build",
39
- "release:check": "npm run format:check && npm run whitespace:check && npm run lint:ci && npm run typecheck && npm run test:coverage && npm run pack:verify && npm run pack:dry-run",
41
+ "release:check": "npm run format:check && npm run whitespace:check && npm run lint:ci && npm run typecheck && npm run deadcode:check && npm run cycles:check && npm run test:coverage && npm run pack:verify && npm run pack:dry-run",
40
42
  "prepublishOnly": "npm run release:check"
41
43
  },
42
44
  "files": [
@@ -62,15 +64,15 @@
62
64
  "@eslint/js": "^10.0.0",
63
65
  "@types/estree": "^1.0.8",
64
66
  "@types/node": "^25.5.2",
65
- "@typescript-eslint/eslint-plugin": "^8.0.0",
66
- "@typescript-eslint/parser": "^8.0.0",
67
67
  "@vitest/coverage-v8": "^4.1.2",
68
+ "dpdm": "4.2.0",
68
69
  "eslint": "^10.0.0",
69
70
  "eslint-config-prettier": "^10.1.8",
70
71
  "eslint-plugin-jsdoc": "^63.0.0",
71
72
  "eslint-plugin-simple-import-sort": "^13.0.0",
72
73
  "fast-check": "^4.6.0",
73
74
  "husky": "^9.1.7",
75
+ "knip": "6.16.1",
74
76
  "lint-staged": "^17.0.4",
75
77
  "prettier": "^3.7.4",
76
78
  "tsx": "^4.7.0",