@hominis/fireforge 0.31.0 → 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 (35) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/dist/src/commands/export-all.js +4 -1
  3. package/dist/src/commands/export-shared.js +10 -1
  4. package/dist/src/commands/export.js +5 -1
  5. package/dist/src/commands/lint-per-patch.d.ts +2 -0
  6. package/dist/src/commands/lint-per-patch.js +206 -44
  7. package/dist/src/commands/lint.js +100 -7
  8. package/dist/src/commands/re-export-files.js +4 -1
  9. package/dist/src/commands/re-export.js +8 -1
  10. package/dist/src/commands/test-run.d.ts +10 -0
  11. package/dist/src/commands/test-run.js +13 -4
  12. package/dist/src/commands/test.js +46 -7
  13. package/dist/src/core/config-validate.js +26 -0
  14. package/dist/src/core/furnace-jsconfig.js +22 -2
  15. package/dist/src/core/git-base.d.ts +15 -0
  16. package/dist/src/core/git-base.js +32 -0
  17. package/dist/src/core/git-diff.d.ts +8 -0
  18. package/dist/src/core/git-diff.js +224 -59
  19. package/dist/src/core/git-file-ops.d.ts +39 -0
  20. package/dist/src/core/git-file-ops.js +82 -1
  21. package/dist/src/core/mach.d.ts +17 -0
  22. package/dist/src/core/mach.js +21 -0
  23. package/dist/src/core/patch-lint-checkjs.d.ts +75 -21
  24. package/dist/src/core/patch-lint-checkjs.js +213 -67
  25. package/dist/src/core/patch-lint-css.d.ts +23 -0
  26. package/dist/src/core/patch-lint-css.js +172 -0
  27. package/dist/src/core/patch-lint.d.ts +34 -11
  28. package/dist/src/core/patch-lint.js +19 -163
  29. package/dist/src/core/test-xpcshell-retry.d.ts +9 -2
  30. package/dist/src/core/test-xpcshell-retry.js +9 -4
  31. package/dist/src/core/typecheck-shim.d.ts +3 -1
  32. package/dist/src/core/typecheck-shim.js +43 -3
  33. package/dist/src/types/commands/options.d.ts +17 -0
  34. package/dist/src/types/config.d.ts +11 -2
  35. package/package.json +1 -1
@@ -1,13 +1,11 @@
1
1
  // SPDX-License-Identifier: EUPL-1.2
2
2
  import { dirname, join } from 'node:path';
3
- import { toError } from '../utils/errors.js';
4
3
  import { pathExists, readText } from '../utils/fs.js';
5
- import { verbose } from '../utils/logger.js';
6
- import { hasRawCssColors, stripJsComments } from '../utils/regex.js';
7
- import { loadFurnaceConfig } from './furnace-config.js';
4
+ import { stripJsComments } from '../utils/regex.js';
8
5
  import { containsUpstreamLicenseText, getLicenseHeader, hasAnyLicenseHeader, hasAnyLicenseHeaderAnyStyle, } from './license-headers.js';
9
6
  import { invokePatchLintCheckJs } from './patch-lint-checkjs.js';
10
7
  import { lintChromeScriptJsDocForFile } from './patch-lint-chrome-jsdoc.js';
8
+ import { lintPatchedCss } from './patch-lint-css.js';
11
9
  import { detectNewFilesInDiff, extractAddedLinesPerFile } from './patch-lint-diff.js';
12
10
  import { AGGREGATE_PATCH_FILE } from './patch-lint-diff-tag.js';
13
11
  import { hasRelativeImport } from './patch-lint-imports.js';
@@ -25,6 +23,10 @@ import { resolvePatchOwnedChromeScripts, resolvePatchOwnedSysMjs } from './patch
25
23
  // public surface from `patch-lint-reexports.ts` so callers continue to
26
24
  // import from a single module.
27
25
  export * from './patch-lint-reexports.js';
26
+ // The CSS rule bodies live in `patch-lint-css.ts` (same per-file-budget
27
+ // split as the other rule families); re-export the imported binding so
28
+ // callers and tests keep importing `lintPatchedCss` from this module.
29
+ export { lintPatchedCss };
28
30
  // ---------------------------------------------------------------------------
29
31
  // Helpers
30
32
  // ---------------------------------------------------------------------------
@@ -205,158 +207,6 @@ export function commentStyleForFile(file) {
205
207
  return 'js';
206
208
  return null;
207
209
  }
208
- /**
209
- * Loads the furnace token-prefix lint inputs gracefully — returns
210
- * undefined (skipping the token-prefix check) when furnace.json cannot
211
- * be loaded or no tokenPrefix is configured.
212
- */
213
- async function loadCssTokenContext(repoDir) {
214
- try {
215
- const root = join(repoDir, '..');
216
- const furnaceConfig = await loadFurnaceConfig(root);
217
- if (furnaceConfig.tokenPrefix) {
218
- return {
219
- tokenPrefix: furnaceConfig.tokenPrefix,
220
- tokenAllowlist: new Set(furnaceConfig.tokenAllowlist ?? []),
221
- runtimeVariables: new Set(furnaceConfig.runtimeVariables ?? []),
222
- };
223
- }
224
- }
225
- catch (error) {
226
- verbose(`Skipping furnace token-prefix lint hints because furnace.json could not be loaded: ${toError(error).message}`);
227
- }
228
- return undefined;
229
- }
230
- /**
231
- * Raw-color check for one patched CSS file, scoped to introduced lines
232
- * when diff context is available. Pushes onto `issues`.
233
- */
234
- function checkRawColorValues(file, rawCss, addedLinesByFile, config, issues) {
235
- // Check only introduced raw color values when diff context is available.
236
- // Skip files on the raw-color allowlist (exact path or basename match) and
237
- // auto-exempt files under `browser/branding/` — those are the fork's
238
- // visual identity assets (app-about dialogs, installer pages, branded
239
- // CSS copied from Firefox's `unofficial` template) and belong to the
240
- // design-decision layer the design-token system does not govern.
241
- // Without this auto-exemption, every first-time setup's copied CSS
242
- // failed `raw-color-value` with no actionable fix other than manually
243
- // listing each path in `rawColorAllowlist`.
244
- const allowlist = config?.patchLint?.rawColorAllowlist;
245
- const isAllowlisted = allowlist?.some((entry) => file === entry || file.endsWith('/' + entry));
246
- const isBranding = file.startsWith('browser/branding/');
247
- if (!isAllowlisted && !isBranding) {
248
- // Strip lines with inline fireforge-ignore: raw-color-value suppression.
249
- // Check against rawCss (before comment stripping) so the CSS comment marker is still present.
250
- const sourceForSuppression = addedLinesByFile
251
- ? (addedLinesByFile.get(file) ?? []).join('\n')
252
- : rawCss;
253
- const suppressedContent = sourceForSuppression
254
- .split('\n')
255
- .filter((line) => !line.includes('fireforge-ignore: raw-color-value'))
256
- .join('\n')
257
- .replace(/\/\*[\s\S]*?\*\//g, '');
258
- if (hasRawCssColors(suppressedContent)) {
259
- issues.push({
260
- file,
261
- check: 'raw-color-value',
262
- message: 'Raw color value found. Use CSS custom properties (var(--...)) for design token consistency.',
263
- severity: 'error',
264
- });
265
- }
266
- }
267
- }
268
- /**
269
- * Token-prefix check for one patched CSS file: flags `var(--x)` references
270
- * that match neither the configured prefix, the allowlist, the runtime
271
- * variables, nor a same-file declaration. Pushes onto `issues`.
272
- */
273
- function checkTokenPrefixViolations(file, cssContent, addedLinesByFile, tokenContext, issues) {
274
- // Check for non-tokenized custom properties. A variable that is both
275
- // declared and consumed inside the same file is auto-exempted as a
276
- // runtime state channel (see furnace.json → runtimeVariables).
277
- //
278
- // When diff context is available, scope the `var(...)` scan to
279
- // added/modified lines only. `cssContent` (full-file) is still the
280
- // source of `localDeclarations` so vars declared anywhere in the file
281
- // are recognised as same-file refs regardless of where the consuming
282
- // `var(...)` appears. Before this scoping change, a small edit to a
283
- // Furnace override of a stock component (e.g. moz-card) produced a
284
- // `token-prefix-violation` for every stock `var(--moz-card-*)` the
285
- // upstream file already carried, because the scanner saw the full
286
- // applied file and flagged each inherited reference as if the fork
287
- // had introduced it.
288
- if (tokenContext) {
289
- const declarationPattern = /(?:^|[{;,\s])(--[\w-]+)\s*:/g;
290
- const localDeclarations = new Set();
291
- let declMatch;
292
- while ((declMatch = declarationPattern.exec(cssContent)) !== null) {
293
- const name = declMatch[1];
294
- if (name)
295
- localDeclarations.add(name);
296
- }
297
- const prefixScanSource = addedLinesByFile
298
- ? (addedLinesByFile.get(file) ?? []).join('\n').replace(/\/\*[\s\S]*?\*\//g, '')
299
- : cssContent;
300
- if (prefixScanSource.length > 0) {
301
- const varPattern = /var\(\s*(--[\w-]+)/g;
302
- const flaggedProps = new Set();
303
- let match;
304
- while ((match = varPattern.exec(prefixScanSource)) !== null) {
305
- const prop = match[1];
306
- if (!prop)
307
- continue;
308
- if (prop.startsWith(tokenContext.tokenPrefix))
309
- continue;
310
- if (tokenContext.tokenAllowlist.has(prop))
311
- continue;
312
- if (tokenContext.runtimeVariables.has(prop))
313
- continue;
314
- if (localDeclarations.has(prop))
315
- continue;
316
- // De-duplicate per (file, prop) pair so the same introduced var
317
- // used five times in the added hunk doesn't produce five
318
- // identical issue entries.
319
- if (flaggedProps.has(prop))
320
- continue;
321
- flaggedProps.add(prop);
322
- issues.push({
323
- file,
324
- check: 'token-prefix-violation',
325
- message: `CSS references var(${prop}) which does not match the required token prefix "${tokenContext.tokenPrefix}". Use a design token, add to tokenAllowlist, or (for runtime state channels) list the variable in runtimeVariables.`,
326
- severity: 'error',
327
- });
328
- }
329
- }
330
- }
331
- }
332
- /**
333
- * Lints patched CSS files for introduced raw color values and non-tokenized
334
- * custom properties.
335
- *
336
- * @param repoDir - Absolute path to the engine (repository) directory
337
- * @param affectedFiles - File paths (relative to repoDir) affected by the patch
338
- * @param diffContent - Optional unified diff used to scope raw color checks to introduced lines
339
- * @returns Array of lint issues found
340
- */
341
- export async function lintPatchedCss(repoDir, affectedFiles, diffContent, config) {
342
- const cssFiles = affectedFiles.filter((f) => f.endsWith('.css'));
343
- if (cssFiles.length === 0)
344
- return [];
345
- const tokenContext = await loadCssTokenContext(repoDir);
346
- const issues = [];
347
- const addedLinesByFile = diffContent ? extractAddedLinesPerFile(diffContent) : undefined;
348
- for (const file of cssFiles) {
349
- const filePath = join(repoDir, file);
350
- if (!(await pathExists(filePath)))
351
- continue;
352
- const rawCss = await readText(filePath);
353
- // Strip block comments before scanning
354
- const cssContent = rawCss.replace(/\/\*[\s\S]*?\*\//g, '');
355
- checkRawColorValues(file, rawCss, addedLinesByFile, config, issues);
356
- checkTokenPrefixViolations(file, cssContent, addedLinesByFile, tokenContext, issues);
357
- }
358
- return issues;
359
- }
360
210
  // ---------------------------------------------------------------------------
361
211
  // License header lint
362
212
  // ---------------------------------------------------------------------------
@@ -695,9 +545,6 @@ export async function lintModifiedFileHeaders(repoDir, affectedFiles, newFiles)
695
545
  }
696
546
  return issues;
697
547
  }
698
- // ---------------------------------------------------------------------------
699
- // Orchestrator
700
- // ---------------------------------------------------------------------------
701
548
  /**
702
549
  * Runs all patch lint checks and returns combined issues.
703
550
  *
@@ -717,9 +564,11 @@ export async function lintModifiedFileHeaders(repoDir, affectedFiles, newFiles)
717
564
  * per-patch manifest context (re-export, per-patch lint) should
718
565
  * pass this; aggregate-mode callers without a specific patch
719
566
  * context skip it and fall through to auto-detection.
567
+ * @param options - Optional behaviour switches; see
568
+ * {@link LintExportedPatchOptions}.
720
569
  * @returns Array of all lint issues found
721
570
  */
722
- export async function lintExportedPatch(repoDir, affectedFiles, diffContent, config, patchQueueCtx, ignoreChecks, patchTier) {
571
+ export async function lintExportedPatch(repoDir, affectedFiles, diffContent, config, patchQueueCtx, ignoreChecks, patchTier, options) {
723
572
  const newFiles = detectNewFilesInDiff(diffContent);
724
573
  const { textLines: lineCount } = countNonBinaryDiffLines(diffContent);
725
574
  const patchOwnedFiles = resolvePatchOwnedSysMjs(newFiles, patchQueueCtx);
@@ -731,7 +580,9 @@ export async function lintExportedPatch(repoDir, affectedFiles, diffContent, con
731
580
  lintModifiedFileHeaders(repoDir, affectedFiles, newFiles),
732
581
  ]);
733
582
  const modCommentIssues = lintModificationComments(diffContent, config);
734
- const sizeIssues = lintPatchSize(affectedFiles, lineCount, patchTier);
583
+ const sizeIssues = options?.skipPatchSize
584
+ ? []
585
+ : lintPatchSize(affectedFiles, lineCount, patchTier);
735
586
  const issues = [
736
587
  ...sizeIssues,
737
588
  ...cssIssues,
@@ -740,8 +591,13 @@ export async function lintExportedPatch(repoDir, affectedFiles, diffContent, con
740
591
  ...jsIssues,
741
592
  ...modCommentIssues,
742
593
  ];
743
- if (config.patchLint?.checkJs) {
744
- issues.push(...(await invokePatchLintCheckJs(repoDir, patchOwnedFiles, config.patchLint, dirname(repoDir))));
594
+ if (options?.precomputedCheckJs) {
595
+ // Per-patch lint built one queue-wide program and already attributed
596
+ // this patch's findings — append them instead of rebuilding the program.
597
+ issues.push(...options.precomputedCheckJs);
598
+ }
599
+ else if (config.patchLint?.checkJs) {
600
+ issues.push(...(await invokePatchLintCheckJs(repoDir, patchOwnedFiles, config.patchLint, dirname(repoDir), options?.checkJsReportScope)));
745
601
  }
746
602
  // Filter out ignored checks last so every rule still runs (keeps the
747
603
  // implementation uniform) but suppressed rules do not surface. We do not
@@ -3,5 +3,12 @@ export interface XpcshellRetryClassification {
3
3
  xpcshell: readonly string[];
4
4
  nonXpcshell: readonly string[];
5
5
  }
6
- /** Removes a stale xpcshell install symlink and retries the focused mach test once. */
7
- export declare function retryAfterXpcshellSymlinkRepair(engineDir: string, objDir: string | undefined, result: MachCommandResult, classification: XpcshellRetryClassification, normalizedPaths: string[], extraArgs: string[], env?: Record<string, string>): Promise<MachCommandResult>;
6
+ /** Dispatches a (possibly suite-specific) mach test run, mirroring `testWithOutput`. */
7
+ export type TestDispatch = (engineDir: string, testPaths: string[], args: string[], env?: Record<string, string>) => Promise<MachCommandResult>;
8
+ /**
9
+ * Removes a stale xpcshell install symlink and retries the focused mach test
10
+ * once. The retry uses the same `dispatch` (suite-specific or generic) the
11
+ * caller is already running on, so an xpcshell-suite run repairs and re-runs
12
+ * via `mach xpcshell-test` rather than falling back to the generic command.
13
+ */
14
+ export declare function retryAfterXpcshellSymlinkRepair(engineDir: string, objDir: string | undefined, result: MachCommandResult, classification: XpcshellRetryClassification, normalizedPaths: string[], extraArgs: string[], env?: Record<string, string>, dispatch?: TestDispatch): Promise<MachCommandResult>;
@@ -1,16 +1,21 @@
1
1
  // SPDX-License-Identifier: EUPL-1.2
2
2
  import { testWithOutput } from './mach.js';
3
3
  import { tryRepairStaleXpcshellTestSymlink } from './test-stale-symlink.js';
4
- /** Removes a stale xpcshell install symlink and retries the focused mach test once. */
5
- export async function retryAfterXpcshellSymlinkRepair(engineDir, objDir, result, classification, normalizedPaths, extraArgs, env) {
4
+ /**
5
+ * Removes a stale xpcshell install symlink and retries the focused mach test
6
+ * once. The retry uses the same `dispatch` (suite-specific or generic) the
7
+ * caller is already running on, so an xpcshell-suite run repairs and re-runs
8
+ * via `mach xpcshell-test` rather than falling back to the generic command.
9
+ */
10
+ export async function retryAfterXpcshellSymlinkRepair(engineDir, objDir, result, classification, normalizedPaths, extraArgs, env, dispatch = testWithOutput) {
6
11
  if (result.exitCode !== 0 &&
7
12
  classification.xpcshell.length > 0 &&
8
13
  classification.nonXpcshell.length === 0) {
9
14
  const repaired = await tryRepairStaleXpcshellTestSymlink(engineDir, objDir, `${result.stdout}\n${result.stderr}`);
10
15
  if (repaired) {
11
16
  return env
12
- ? testWithOutput(engineDir, normalizedPaths, extraArgs, env)
13
- : testWithOutput(engineDir, normalizedPaths, extraArgs);
17
+ ? dispatch(engineDir, normalizedPaths, extraArgs, env)
18
+ : dispatch(engineDir, normalizedPaths, extraArgs);
14
19
  }
15
20
  }
16
21
  return result;
@@ -40,7 +40,9 @@ export interface ComposedShim {
40
40
  * direction is intentional (declarations later in concat order
41
41
  * augment earlier ones), so a project that wants to refine `Services`
42
42
  * with a more specific type can do so by declaring it in the extra
43
- * shim.
43
+ * shim. Any triple-slash `/// <reference path="…">` directives inside the
44
+ * extra shim are inlined (resolved against the extra shim's own directory)
45
+ * so they are not silently dropped at the synthetic shim path.
44
46
  *
45
47
  * Missing extra-shim files raise a clear error rather than failing
46
48
  * silently with a confusing "type not found" downstream — this is the
@@ -10,7 +10,7 @@
10
10
  * still fail `fireforge typecheck`, or vice versa, for reasons the
11
11
  * operator could not infer from the rule names.
12
12
  */
13
- import { resolve } from 'node:path';
13
+ import { dirname, resolve } from 'node:path';
14
14
  import { pathExists, readText } from '../utils/fs.js';
15
15
  /** Filename used for the synthetic Firefox-globals shim source file. */
16
16
  export const SHIM_FILENAME = '__fireforge_firefox_globals.d.ts';
@@ -110,6 +110,43 @@ export const SUPPRESSED_DIAGNOSTIC_CODES = new Set([
110
110
  2580, // Cannot find name '{0}'. Do you need to install type definitions...
111
111
  7016, // Could not find a declaration file for module '{0}'.
112
112
  ]);
113
+ /** Matches a lone triple-slash `/// <reference path="…" />` directive line. */
114
+ const TRIPLE_SLASH_REFERENCE = /^\s*\/\/\/\s*<reference\s+path\s*=\s*["']([^"']+)["']\s*\/?>\s*$/;
115
+ /**
116
+ * Inlines triple-slash `/// <reference path="…">` directives in shim source.
117
+ *
118
+ * Both shim consumers feed the text to the compiler at a *synthetic* path
119
+ * (an in-memory source file, not the extra shim's real location), so TS
120
+ * resolves a relative `/// <reference>` against that synthetic directory and
121
+ * silently drops it. Inlining the referenced file's contents (recursively,
122
+ * resolved against the *referencing* file's directory, deduped by absolute
123
+ * path) makes the directives self-contained so their declarations survive.
124
+ *
125
+ * @param source - Shim source possibly containing reference directives
126
+ * @param baseDir - Directory the directives' relative paths resolve against
127
+ * @param seen - Absolute paths already inlined (cycle / duplicate guard)
128
+ */
129
+ async function inlineTripleSlashReferences(source, baseDir, seen) {
130
+ const out = [];
131
+ for (const line of source.split('\n')) {
132
+ const match = TRIPLE_SLASH_REFERENCE.exec(line);
133
+ if (!match?.[1]) {
134
+ out.push(line);
135
+ continue;
136
+ }
137
+ const absolute = resolve(baseDir, match[1]);
138
+ if (seen.has(absolute))
139
+ continue;
140
+ seen.add(absolute);
141
+ if (!(await pathExists(absolute))) {
142
+ out.push(`// (fireforge: unresolved /// <reference path="${match[1]}">)`);
143
+ continue;
144
+ }
145
+ const referenced = await readText(absolute);
146
+ out.push(await inlineTripleSlashReferences(referenced, dirname(absolute), seen));
147
+ }
148
+ return out.join('\n');
149
+ }
113
150
  /**
114
151
  * Composes the synthetic shim source by concatenating the built-in
115
152
  * Firefox globals shim with the contents of an optional user-supplied
@@ -117,7 +154,9 @@ export const SUPPRESSED_DIAGNOSTIC_CODES = new Set([
117
154
  * direction is intentional (declarations later in concat order
118
155
  * augment earlier ones), so a project that wants to refine `Services`
119
156
  * with a more specific type can do so by declaring it in the extra
120
- * shim.
157
+ * shim. Any triple-slash `/// <reference path="…">` directives inside the
158
+ * extra shim are inlined (resolved against the extra shim's own directory)
159
+ * so they are not silently dropped at the synthetic shim path.
121
160
  *
122
161
  * Missing extra-shim files raise a clear error rather than failing
123
162
  * silently with a confusing "type not found" downstream — this is the
@@ -137,8 +176,9 @@ export async function composeShimSource(projectRoot, extraShimPath) {
137
176
  'Check the path in fireforge.json or create the file.');
138
177
  }
139
178
  const extra = await readText(absoluteShim);
179
+ const inlinedExtra = await inlineTripleSlashReferences(extra, dirname(absoluteShim), new Set([absoluteShim]));
140
180
  return {
141
- source: `${FIREFOX_GLOBALS_SHIM}\n// ── extraShim: ${extraShimPath} ──\n${extra}`,
181
+ source: `${FIREFOX_GLOBALS_SHIM}\n// ── extraShim: ${extraShimPath} ──\n${inlinedExtra}`,
142
182
  extraShimAppended: true,
143
183
  };
144
184
  }
@@ -428,6 +428,15 @@ export interface TestOptions {
428
428
  * command layer.
429
429
  */
430
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;
431
440
  /**
432
441
  * Commander negation flag for `--no-shard`. When false, multiple test
433
442
  * paths run in one combined mach invocation; by default they shard into
@@ -774,6 +783,14 @@ export interface LintCommandOptions {
774
783
  * scope contracts are different.
775
784
  */
776
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[];
777
794
  /**
778
795
  * Maximum warning count tolerated before lint exits non-zero. Mirrors
779
796
  * ESLint's `--max-warnings` shape for release gates that want advisory
@@ -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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hominis/fireforge",
3
- "version": "0.31.0",
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",