@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.
- package/CHANGELOG.md +36 -0
- package/README.md +22 -0
- package/dist/src/commands/export-all.js +9 -16
- package/dist/src/commands/export-flow.d.ts +6 -0
- package/dist/src/commands/export-flow.js +6 -1
- package/dist/src/commands/export-placement-gate.d.ts +38 -0
- package/dist/src/commands/export-placement-gate.js +105 -0
- package/dist/src/commands/export-shared.d.ts +28 -0
- package/dist/src/commands/export-shared.js +46 -1
- package/dist/src/commands/export.js +52 -113
- package/dist/src/commands/furnace/chrome-doc-templates.d.ts +0 -13
- package/dist/src/commands/furnace/chrome-doc-templates.js +1 -1
- package/dist/src/commands/furnace/create-dry-run.d.ts +1 -1
- package/dist/src/commands/furnace/create.d.ts +1 -2
- package/dist/src/commands/furnace/deploy.js +36 -114
- package/dist/src/commands/furnace/refresh.js +52 -32
- package/dist/src/commands/furnace/sync.js +2 -0
- package/dist/src/commands/import.js +108 -73
- package/dist/src/commands/lint-per-patch.d.ts +3 -1
- package/dist/src/commands/lint-per-patch.js +265 -74
- package/dist/src/commands/lint.d.ts +1 -58
- package/dist/src/commands/lint.js +193 -88
- package/dist/src/commands/patch/compact.d.ts +5 -2
- package/dist/src/commands/patch/compact.js +85 -25
- package/dist/src/commands/patch/delete.js +17 -17
- package/dist/src/commands/patch/index.js +2 -0
- package/dist/src/commands/patch/lint-ignore.js +3 -16
- package/dist/src/commands/patch/move-files.js +2 -0
- package/dist/src/commands/patch/patch-context.d.ts +41 -0
- package/dist/src/commands/patch/patch-context.js +53 -0
- package/dist/src/commands/patch/rename.js +10 -15
- package/dist/src/commands/patch/reorder.d.ts +0 -2
- package/dist/src/commands/patch/reorder.js +18 -19
- package/dist/src/commands/patch/split-plan.d.ts +66 -0
- package/dist/src/commands/patch/split-plan.js +178 -0
- package/dist/src/commands/patch/split.d.ts +30 -0
- package/dist/src/commands/patch/split.js +283 -0
- package/dist/src/commands/patch/staged-dependency.d.ts +1 -7
- package/dist/src/commands/patch/staged-dependency.js +4 -17
- package/dist/src/commands/patch/tier.js +4 -17
- package/dist/src/commands/re-export-files.js +4 -1
- package/dist/src/commands/re-export-scan.js +8 -1
- package/dist/src/commands/re-export.js +8 -1
- package/dist/src/commands/rebase/summary.d.ts +1 -5
- package/dist/src/commands/rebase/summary.js +1 -1
- package/dist/src/commands/status-output.js +77 -68
- package/dist/src/commands/test-diagnose.d.ts +23 -0
- package/dist/src/commands/test-diagnose.js +210 -0
- package/dist/src/commands/test-run.d.ts +68 -0
- package/dist/src/commands/test-run.js +97 -0
- package/dist/src/commands/test.js +214 -263
- package/dist/src/commands/token.js +15 -1
- package/dist/src/commands/wire.js +109 -78
- package/dist/src/core/build-audit.d.ts +1 -1
- package/dist/src/core/build-audit.js +2 -46
- package/dist/src/core/build-baseline-types.d.ts +38 -0
- package/dist/src/core/build-baseline-types.js +10 -0
- package/dist/src/core/build-baseline.d.ts +1 -31
- package/dist/src/core/build-prepare.d.ts +1 -1
- package/dist/src/core/build-prepare.js +2 -45
- package/dist/src/core/config-paths.d.ts +0 -8
- package/dist/src/core/config-paths.js +4 -4
- package/dist/src/core/config-state.d.ts +0 -6
- package/dist/src/core/config-state.js +1 -1
- package/dist/src/core/config-validate-patch-policy.js +12 -13
- package/dist/src/core/config-validate.js +74 -28
- package/dist/src/core/engine-changes.d.ts +24 -0
- package/dist/src/core/engine-changes.js +64 -0
- package/dist/src/core/firefox-cache.d.ts +0 -5
- package/dist/src/core/firefox-cache.js +1 -1
- package/dist/src/core/firefox-download.d.ts +0 -6
- package/dist/src/core/firefox-download.js +1 -1
- package/dist/src/core/furnace-apply-helpers.d.ts +1 -8
- package/dist/src/core/furnace-apply-helpers.js +11 -20
- package/dist/src/core/furnace-apply.d.ts +1 -1
- package/dist/src/core/furnace-apply.js +1 -1
- package/dist/src/core/furnace-checksum-utils.d.ts +7 -0
- package/dist/src/core/furnace-checksum-utils.js +15 -0
- package/dist/src/core/furnace-config-validate.d.ts +31 -0
- package/dist/src/core/furnace-config-validate.js +133 -0
- package/dist/src/core/furnace-config.d.ts +4 -32
- package/dist/src/core/furnace-config.js +15 -111
- package/dist/src/core/furnace-constants.d.ts +0 -10
- package/dist/src/core/furnace-constants.js +2 -2
- package/dist/src/core/furnace-css-fragments.d.ts +79 -0
- package/dist/src/core/furnace-css-fragments.js +243 -0
- package/dist/src/core/furnace-jsconfig.d.ts +63 -0
- package/dist/src/core/furnace-jsconfig.js +191 -0
- package/dist/src/core/furnace-validate-helpers.d.ts +16 -14
- package/dist/src/core/furnace-validate-helpers.js +40 -1
- package/dist/src/core/furnace-validate-registration.js +16 -1
- package/dist/src/core/furnace-validate.js +54 -2
- package/dist/src/core/git-base.d.ts +15 -0
- package/dist/src/core/git-base.js +32 -0
- package/dist/src/core/git-diff.d.ts +8 -0
- package/dist/src/core/git-diff.js +224 -59
- package/dist/src/core/git-file-ops.d.ts +39 -12
- package/dist/src/core/git-file-ops.js +84 -3
- package/dist/src/core/lint-cache.d.ts +0 -13
- package/dist/src/core/lint-cache.js +5 -5
- package/dist/src/core/mach.d.ts +22 -1
- package/dist/src/core/mach.js +27 -2
- package/dist/src/core/manifest-register.d.ts +5 -16
- package/dist/src/core/manifest-register.js +3 -1
- package/dist/src/core/patch-lint-checkjs.d.ts +75 -21
- package/dist/src/core/patch-lint-checkjs.js +263 -71
- package/dist/src/core/patch-lint-css.d.ts +23 -0
- package/dist/src/core/patch-lint-css.js +172 -0
- package/dist/src/core/patch-lint-jsdoc.js +63 -4
- package/dist/src/core/patch-lint-observer.d.ts +37 -0
- package/dist/src/core/patch-lint-observer.js +168 -0
- package/dist/src/core/patch-lint.d.ts +34 -11
- package/dist/src/core/patch-lint.js +24 -161
- package/dist/src/core/patch-manifest-io.d.ts +16 -0
- package/dist/src/core/patch-manifest-io.js +44 -2
- package/dist/src/core/patch-manifest-validate.d.ts +1 -8
- package/dist/src/core/patch-manifest-validate.js +1 -1
- package/dist/src/core/patch-manifest.d.ts +1 -1
- package/dist/src/core/patch-manifest.js +1 -1
- package/dist/src/core/patch-policy.d.ts +0 -4
- package/dist/src/core/patch-policy.js +10 -4
- package/dist/src/core/register-browser-content.d.ts +1 -1
- package/dist/src/core/register-module.d.ts +1 -1
- package/dist/src/core/register-result.d.ts +21 -0
- package/dist/src/core/register-result.js +9 -0
- package/dist/src/core/register-shared-css.d.ts +1 -1
- package/dist/src/core/register-test-manifest.d.ts +1 -1
- package/dist/src/core/test-harness-crash.d.ts +61 -0
- package/dist/src/core/test-harness-crash.js +140 -0
- package/dist/src/core/test-stale-check.d.ts +1 -1
- package/dist/src/core/test-stale-check.js +2 -46
- package/dist/src/core/test-xpcshell-retry.d.ts +9 -2
- package/dist/src/core/test-xpcshell-retry.js +10 -3
- package/dist/src/core/token-dark-mode.js +14 -26
- package/dist/src/core/token-manager.d.ts +4 -0
- package/dist/src/core/token-manager.js +70 -16
- package/dist/src/core/typecheck-shim.d.ts +3 -22
- package/dist/src/core/typecheck-shim.js +69 -7
- package/dist/src/core/wire-utils.js +37 -44
- package/dist/src/types/commands/index.d.ts +1 -1
- package/dist/src/types/commands/options.d.ts +122 -0
- package/dist/src/types/config.d.ts +11 -2
- package/dist/src/types/furnace.d.ts +12 -1
- package/dist/src/utils/elapsed.d.ts +0 -2
- package/dist/src/utils/elapsed.js +1 -1
- package/dist/src/utils/fs.d.ts +0 -5
- package/dist/src/utils/fs.js +1 -1
- package/dist/src/utils/regex.d.ts +0 -6
- package/dist/src/utils/regex.js +3 -3
- package/dist/src/utils/validation.d.ts +0 -8
- package/dist/src/utils/validation.js +2 -2
- package/package.json +6 -4
|
@@ -1,17 +1,16 @@
|
|
|
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 {
|
|
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';
|
|
14
12
|
import { validateExportJsDoc } from './patch-lint-jsdoc.js';
|
|
13
|
+
import { lintObserverTopics } from './patch-lint-observer.js';
|
|
15
14
|
import { resolvePatchOwnedChromeScripts, resolvePatchOwnedSysMjs } from './patch-lint-ownership.js';
|
|
16
15
|
// ---------------------------------------------------------------------------
|
|
17
16
|
// Cross-patch lint re-exports
|
|
@@ -24,6 +23,10 @@ import { resolvePatchOwnedChromeScripts, resolvePatchOwnedSysMjs } from './patch
|
|
|
24
23
|
// public surface from `patch-lint-reexports.ts` so callers continue to
|
|
25
24
|
// import from a single module.
|
|
26
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 };
|
|
27
30
|
// ---------------------------------------------------------------------------
|
|
28
31
|
// Helpers
|
|
29
32
|
// ---------------------------------------------------------------------------
|
|
@@ -205,139 +208,6 @@ export function commentStyleForFile(file) {
|
|
|
205
208
|
return null;
|
|
206
209
|
}
|
|
207
210
|
// ---------------------------------------------------------------------------
|
|
208
|
-
// CSS lint
|
|
209
|
-
// ---------------------------------------------------------------------------
|
|
210
|
-
/**
|
|
211
|
-
* Lints patched CSS files for introduced raw color values and non-tokenized
|
|
212
|
-
* custom properties.
|
|
213
|
-
*
|
|
214
|
-
* @param repoDir - Absolute path to the engine (repository) directory
|
|
215
|
-
* @param affectedFiles - File paths (relative to repoDir) affected by the patch
|
|
216
|
-
* @param diffContent - Optional unified diff used to scope raw color checks to introduced lines
|
|
217
|
-
* @returns Array of lint issues found
|
|
218
|
-
*/
|
|
219
|
-
export async function lintPatchedCss(repoDir, affectedFiles, diffContent, config) {
|
|
220
|
-
const cssFiles = affectedFiles.filter((f) => f.endsWith('.css'));
|
|
221
|
-
if (cssFiles.length === 0)
|
|
222
|
-
return [];
|
|
223
|
-
// Load furnace config gracefully — skip token-prefix check if unavailable
|
|
224
|
-
let tokenPrefix;
|
|
225
|
-
let tokenAllowlist;
|
|
226
|
-
let runtimeVariables;
|
|
227
|
-
try {
|
|
228
|
-
const root = join(repoDir, '..');
|
|
229
|
-
const furnaceConfig = await loadFurnaceConfig(root);
|
|
230
|
-
if (furnaceConfig.tokenPrefix) {
|
|
231
|
-
tokenPrefix = furnaceConfig.tokenPrefix;
|
|
232
|
-
tokenAllowlist = new Set(furnaceConfig.tokenAllowlist ?? []);
|
|
233
|
-
runtimeVariables = new Set(furnaceConfig.runtimeVariables ?? []);
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
catch (error) {
|
|
237
|
-
verbose(`Skipping furnace token-prefix lint hints because furnace.json could not be loaded: ${toError(error).message}`);
|
|
238
|
-
}
|
|
239
|
-
const issues = [];
|
|
240
|
-
const addedLinesByFile = diffContent ? extractAddedLinesPerFile(diffContent) : undefined;
|
|
241
|
-
for (const file of cssFiles) {
|
|
242
|
-
const filePath = join(repoDir, file);
|
|
243
|
-
if (!(await pathExists(filePath)))
|
|
244
|
-
continue;
|
|
245
|
-
const rawCss = await readText(filePath);
|
|
246
|
-
// Strip block comments before scanning
|
|
247
|
-
const cssContent = rawCss.replace(/\/\*[\s\S]*?\*\//g, '');
|
|
248
|
-
// Check only introduced raw color values when diff context is available.
|
|
249
|
-
// Skip files on the raw-color allowlist (exact path or basename match) and
|
|
250
|
-
// auto-exempt files under `browser/branding/` — those are the fork's
|
|
251
|
-
// visual identity assets (app-about dialogs, installer pages, branded
|
|
252
|
-
// CSS copied from Firefox's `unofficial` template) and belong to the
|
|
253
|
-
// design-decision layer the design-token system does not govern.
|
|
254
|
-
// Without this auto-exemption, every first-time setup's copied CSS
|
|
255
|
-
// failed `raw-color-value` with no actionable fix other than manually
|
|
256
|
-
// listing each path in `rawColorAllowlist`.
|
|
257
|
-
const allowlist = config?.patchLint?.rawColorAllowlist;
|
|
258
|
-
const isAllowlisted = allowlist?.some((entry) => file === entry || file.endsWith('/' + entry));
|
|
259
|
-
const isBranding = file.startsWith('browser/branding/');
|
|
260
|
-
if (!isAllowlisted && !isBranding) {
|
|
261
|
-
// Strip lines with inline fireforge-ignore: raw-color-value suppression.
|
|
262
|
-
// Check against rawCss (before comment stripping) so the CSS comment marker is still present.
|
|
263
|
-
const sourceForSuppression = addedLinesByFile
|
|
264
|
-
? (addedLinesByFile.get(file) ?? []).join('\n')
|
|
265
|
-
: rawCss;
|
|
266
|
-
const suppressedContent = sourceForSuppression
|
|
267
|
-
.split('\n')
|
|
268
|
-
.filter((line) => !line.includes('fireforge-ignore: raw-color-value'))
|
|
269
|
-
.join('\n')
|
|
270
|
-
.replace(/\/\*[\s\S]*?\*\//g, '');
|
|
271
|
-
if (hasRawCssColors(suppressedContent)) {
|
|
272
|
-
issues.push({
|
|
273
|
-
file,
|
|
274
|
-
check: 'raw-color-value',
|
|
275
|
-
message: 'Raw color value found. Use CSS custom properties (var(--...)) for design token consistency.',
|
|
276
|
-
severity: 'error',
|
|
277
|
-
});
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
// Check for non-tokenized custom properties. A variable that is both
|
|
281
|
-
// declared and consumed inside the same file is auto-exempted as a
|
|
282
|
-
// runtime state channel (see furnace.json → runtimeVariables).
|
|
283
|
-
//
|
|
284
|
-
// When diff context is available, scope the `var(...)` scan to
|
|
285
|
-
// added/modified lines only. `cssContent` (full-file) is still the
|
|
286
|
-
// source of `localDeclarations` so vars declared anywhere in the file
|
|
287
|
-
// are recognised as same-file refs regardless of where the consuming
|
|
288
|
-
// `var(...)` appears. Before this scoping change, a small edit to a
|
|
289
|
-
// Furnace override of a stock component (e.g. moz-card) produced a
|
|
290
|
-
// `token-prefix-violation` for every stock `var(--moz-card-*)` the
|
|
291
|
-
// upstream file already carried, because the scanner saw the full
|
|
292
|
-
// applied file and flagged each inherited reference as if the fork
|
|
293
|
-
// had introduced it.
|
|
294
|
-
if (tokenPrefix) {
|
|
295
|
-
const declarationPattern = /(?:^|[{;,\s])(--[\w-]+)\s*:/g;
|
|
296
|
-
const localDeclarations = new Set();
|
|
297
|
-
let declMatch;
|
|
298
|
-
while ((declMatch = declarationPattern.exec(cssContent)) !== null) {
|
|
299
|
-
const name = declMatch[1];
|
|
300
|
-
if (name)
|
|
301
|
-
localDeclarations.add(name);
|
|
302
|
-
}
|
|
303
|
-
const prefixScanSource = addedLinesByFile
|
|
304
|
-
? (addedLinesByFile.get(file) ?? []).join('\n').replace(/\/\*[\s\S]*?\*\//g, '')
|
|
305
|
-
: cssContent;
|
|
306
|
-
if (prefixScanSource.length > 0) {
|
|
307
|
-
const varPattern = /var\(\s*(--[\w-]+)/g;
|
|
308
|
-
const flaggedProps = new Set();
|
|
309
|
-
let match;
|
|
310
|
-
while ((match = varPattern.exec(prefixScanSource)) !== null) {
|
|
311
|
-
const prop = match[1];
|
|
312
|
-
if (!prop)
|
|
313
|
-
continue;
|
|
314
|
-
if (prop.startsWith(tokenPrefix))
|
|
315
|
-
continue;
|
|
316
|
-
if (tokenAllowlist?.has(prop))
|
|
317
|
-
continue;
|
|
318
|
-
if (runtimeVariables?.has(prop))
|
|
319
|
-
continue;
|
|
320
|
-
if (localDeclarations.has(prop))
|
|
321
|
-
continue;
|
|
322
|
-
// De-duplicate per (file, prop) pair so the same introduced var
|
|
323
|
-
// used five times in the added hunk doesn't produce five
|
|
324
|
-
// identical issue entries.
|
|
325
|
-
if (flaggedProps.has(prop))
|
|
326
|
-
continue;
|
|
327
|
-
flaggedProps.add(prop);
|
|
328
|
-
issues.push({
|
|
329
|
-
file,
|
|
330
|
-
check: 'token-prefix-violation',
|
|
331
|
-
message: `CSS references var(${prop}) which does not match the required token prefix "${tokenPrefix}". Use a design token, add to tokenAllowlist, or (for runtime state channels) list the variable in runtimeVariables.`,
|
|
332
|
-
severity: 'error',
|
|
333
|
-
});
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
return issues;
|
|
339
|
-
}
|
|
340
|
-
// ---------------------------------------------------------------------------
|
|
341
211
|
// License header lint
|
|
342
212
|
// ---------------------------------------------------------------------------
|
|
343
213
|
/**
|
|
@@ -503,23 +373,10 @@ export async function lintPatchedJs(repoDir, affectedFiles, newFiles, config, pa
|
|
|
503
373
|
});
|
|
504
374
|
}
|
|
505
375
|
}
|
|
506
|
-
// 4. Observer topic naming
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
const topic = topicMatch[1];
|
|
511
|
-
if (!topic)
|
|
512
|
-
continue;
|
|
513
|
-
// Only flag topics that contain the binaryName but don't follow convention
|
|
514
|
-
if (topic.toLowerCase().includes(binaryName) && !/^[\w]+-[a-z]+-[a-z]+/.test(topic)) {
|
|
515
|
-
issues.push({
|
|
516
|
-
file,
|
|
517
|
-
check: 'observer-topic-naming',
|
|
518
|
-
message: `Observer topic "${topic}" should follow "${binaryName}-<noun>-<verb>" naming convention.`,
|
|
519
|
-
severity: 'warning',
|
|
520
|
-
});
|
|
521
|
-
}
|
|
522
|
-
}
|
|
376
|
+
// 4. Observer topic naming. Rule body lives in `patch-lint-observer.ts`:
|
|
377
|
+
// argument-position-aware, multi-line-safe, and allowlists Firefox-owned
|
|
378
|
+
// topics so simulated upstream notifications are not flagged.
|
|
379
|
+
issues.push(...lintObserverTopics(strippedContent, file, binaryName));
|
|
523
380
|
}
|
|
524
381
|
return issues;
|
|
525
382
|
}
|
|
@@ -688,9 +545,6 @@ export async function lintModifiedFileHeaders(repoDir, affectedFiles, newFiles)
|
|
|
688
545
|
}
|
|
689
546
|
return issues;
|
|
690
547
|
}
|
|
691
|
-
// ---------------------------------------------------------------------------
|
|
692
|
-
// Orchestrator
|
|
693
|
-
// ---------------------------------------------------------------------------
|
|
694
548
|
/**
|
|
695
549
|
* Runs all patch lint checks and returns combined issues.
|
|
696
550
|
*
|
|
@@ -710,9 +564,11 @@ export async function lintModifiedFileHeaders(repoDir, affectedFiles, newFiles)
|
|
|
710
564
|
* per-patch manifest context (re-export, per-patch lint) should
|
|
711
565
|
* pass this; aggregate-mode callers without a specific patch
|
|
712
566
|
* context skip it and fall through to auto-detection.
|
|
567
|
+
* @param options - Optional behaviour switches; see
|
|
568
|
+
* {@link LintExportedPatchOptions}.
|
|
713
569
|
* @returns Array of all lint issues found
|
|
714
570
|
*/
|
|
715
|
-
export async function lintExportedPatch(repoDir, affectedFiles, diffContent, config, patchQueueCtx, ignoreChecks, patchTier) {
|
|
571
|
+
export async function lintExportedPatch(repoDir, affectedFiles, diffContent, config, patchQueueCtx, ignoreChecks, patchTier, options) {
|
|
716
572
|
const newFiles = detectNewFilesInDiff(diffContent);
|
|
717
573
|
const { textLines: lineCount } = countNonBinaryDiffLines(diffContent);
|
|
718
574
|
const patchOwnedFiles = resolvePatchOwnedSysMjs(newFiles, patchQueueCtx);
|
|
@@ -724,7 +580,9 @@ export async function lintExportedPatch(repoDir, affectedFiles, diffContent, con
|
|
|
724
580
|
lintModifiedFileHeaders(repoDir, affectedFiles, newFiles),
|
|
725
581
|
]);
|
|
726
582
|
const modCommentIssues = lintModificationComments(diffContent, config);
|
|
727
|
-
const sizeIssues =
|
|
583
|
+
const sizeIssues = options?.skipPatchSize
|
|
584
|
+
? []
|
|
585
|
+
: lintPatchSize(affectedFiles, lineCount, patchTier);
|
|
728
586
|
const issues = [
|
|
729
587
|
...sizeIssues,
|
|
730
588
|
...cssIssues,
|
|
@@ -733,8 +591,13 @@ export async function lintExportedPatch(repoDir, affectedFiles, diffContent, con
|
|
|
733
591
|
...jsIssues,
|
|
734
592
|
...modCommentIssues,
|
|
735
593
|
];
|
|
736
|
-
if (
|
|
737
|
-
|
|
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)));
|
|
738
601
|
}
|
|
739
602
|
// Filter out ignored checks last so every rule still runs (keeps the
|
|
740
603
|
// implementation uniform) but suppressed rules do not surface. We do not
|
|
@@ -85,6 +85,22 @@ export interface PatchRenameEntry {
|
|
|
85
85
|
/** New numeric order — must match the prefix of `newFilename`. */
|
|
86
86
|
newOrder: number;
|
|
87
87
|
}
|
|
88
|
+
/**
|
|
89
|
+
* Rewrites `stagedDependencies.forwardImports[].owner` references on one
|
|
90
|
+
* patch through a rename lookup. Owners embed exact patch filenames, so any
|
|
91
|
+
* renumber (compact, reorder, placement export, rename) that does not remap
|
|
92
|
+
* them leaves dangling references that surface as false forward-import
|
|
93
|
+
* errors on the next lint.
|
|
94
|
+
*
|
|
95
|
+
* Pure and allocation-conservative: returns the input object unchanged when
|
|
96
|
+
* no owner matches the lookup, so callers can map whole manifests cheaply.
|
|
97
|
+
*
|
|
98
|
+
* @param patch - Manifest row to rewrite
|
|
99
|
+
* @param renameLookup - Maps an old patch filename to its new filename, or
|
|
100
|
+
* undefined when the filename is not being renamed
|
|
101
|
+
* @returns The same row, or a copy with remapped owners
|
|
102
|
+
*/
|
|
103
|
+
export declare function rewriteStagedDependencyOwners(patch: PatchMetadata, renameLookup: (oldFilename: string) => string | undefined): PatchMetadata;
|
|
88
104
|
/**
|
|
89
105
|
* Renames patch files on disk and rewrites the corresponding manifest rows
|
|
90
106
|
* atomically-ish: file renames use a two-phase staging strategy (rename each
|
|
@@ -167,6 +167,44 @@ export async function removePatchFromManifest(patchesDir, filename) {
|
|
|
167
167
|
await savePatchesManifest(patchesDir, manifest);
|
|
168
168
|
return true;
|
|
169
169
|
}
|
|
170
|
+
/**
|
|
171
|
+
* Rewrites `stagedDependencies.forwardImports[].owner` references on one
|
|
172
|
+
* patch through a rename lookup. Owners embed exact patch filenames, so any
|
|
173
|
+
* renumber (compact, reorder, placement export, rename) that does not remap
|
|
174
|
+
* them leaves dangling references that surface as false forward-import
|
|
175
|
+
* errors on the next lint.
|
|
176
|
+
*
|
|
177
|
+
* Pure and allocation-conservative: returns the input object unchanged when
|
|
178
|
+
* no owner matches the lookup, so callers can map whole manifests cheaply.
|
|
179
|
+
*
|
|
180
|
+
* @param patch - Manifest row to rewrite
|
|
181
|
+
* @param renameLookup - Maps an old patch filename to its new filename, or
|
|
182
|
+
* undefined when the filename is not being renamed
|
|
183
|
+
* @returns The same row, or a copy with remapped owners
|
|
184
|
+
*/
|
|
185
|
+
export function rewriteStagedDependencyOwners(patch, renameLookup) {
|
|
186
|
+
const forwardImports = patch.stagedDependencies?.forwardImports;
|
|
187
|
+
if (!forwardImports || forwardImports.length === 0)
|
|
188
|
+
return patch;
|
|
189
|
+
const rewritten = forwardImports.map((fi) => {
|
|
190
|
+
if (!fi.owner)
|
|
191
|
+
return fi;
|
|
192
|
+
const newOwner = renameLookup(fi.owner);
|
|
193
|
+
if (newOwner === undefined || newOwner === fi.owner)
|
|
194
|
+
return fi;
|
|
195
|
+
return { ...fi, owner: newOwner };
|
|
196
|
+
});
|
|
197
|
+
const changed = rewritten.some((fi, index) => fi !== forwardImports[index]);
|
|
198
|
+
if (!changed)
|
|
199
|
+
return patch;
|
|
200
|
+
return {
|
|
201
|
+
...patch,
|
|
202
|
+
stagedDependencies: {
|
|
203
|
+
...patch.stagedDependencies,
|
|
204
|
+
forwardImports: rewritten,
|
|
205
|
+
},
|
|
206
|
+
};
|
|
207
|
+
}
|
|
170
208
|
/**
|
|
171
209
|
* Renames patch files on disk and rewrites the corresponding manifest rows
|
|
172
210
|
* atomically-ish: file renames use a two-phase staging strategy (rename each
|
|
@@ -300,12 +338,16 @@ export async function renumberPatchesInManifest(patchesDir, renameMap) {
|
|
|
300
338
|
for (const [oldFilename, entry] of renameMap) {
|
|
301
339
|
filenameUpdates.set(oldFilename, entry);
|
|
302
340
|
}
|
|
341
|
+
// Owner references live on *other* patches than the renamed ones, so every
|
|
342
|
+
// row is passed through the staged-dependency rewrite, not just renamed rows.
|
|
343
|
+
const ownerLookup = (oldFilename) => filenameUpdates.get(oldFilename)?.newFilename;
|
|
303
344
|
const updatedPatches = manifest.patches.map((p) => {
|
|
304
345
|
const update = filenameUpdates.get(p.filename);
|
|
346
|
+
const withOwners = rewriteStagedDependencyOwners(p, ownerLookup);
|
|
305
347
|
if (!update)
|
|
306
|
-
return
|
|
348
|
+
return withOwners;
|
|
307
349
|
return {
|
|
308
|
-
...
|
|
350
|
+
...withOwners,
|
|
309
351
|
filename: update.newFilename,
|
|
310
352
|
order: update.newOrder,
|
|
311
353
|
};
|
|
@@ -1,14 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Schema validation for patches.json manifest data.
|
|
3
3
|
*/
|
|
4
|
-
import type { PatchCategory, PatchesManifest
|
|
5
|
-
/**
|
|
6
|
-
* Validates a single patch metadata entry from raw data.
|
|
7
|
-
* @param data - Raw data to validate
|
|
8
|
-
* @param index - Array index for error messages
|
|
9
|
-
* @returns Validated PatchMetadata
|
|
10
|
-
*/
|
|
11
|
-
export declare function validatePatchMetadata(data: unknown, index: number): PatchMetadata;
|
|
4
|
+
import type { PatchCategory, PatchesManifest } from '../types/commands/index.js';
|
|
12
5
|
/** Validates raw patches.json data and returns the typed manifest shape. */
|
|
13
6
|
export declare function validatePatchesManifest(data: unknown): PatchesManifest;
|
|
14
7
|
/**
|
|
@@ -39,7 +39,7 @@ function parseStagedDependencies(data, label) {
|
|
|
39
39
|
* @param index - Array index for error messages
|
|
40
40
|
* @returns Validated PatchMetadata
|
|
41
41
|
*/
|
|
42
|
-
|
|
42
|
+
function validatePatchMetadata(data, index) {
|
|
43
43
|
const rec = parseObject(data, `patches[${index}]`);
|
|
44
44
|
const filename = rec.string('filename');
|
|
45
45
|
const name = rec.string('name');
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* is an implementation detail.
|
|
6
6
|
*/
|
|
7
7
|
export { rebuildPatchesManifest, validatePatchesManifestConsistency, } from './patch-manifest-consistency.js';
|
|
8
|
-
export { addPatchToManifest, loadPatchesManifest, mutatePatchRowsInManifest, PATCHES_MANIFEST, type PatchManifestRowMutation, type PatchManifestRowMutationResult, type PatchRenameEntry, removePatchFileAndManifest, renumberPatchesInManifest, savePatchesManifest, } from './patch-manifest-io.js';
|
|
8
|
+
export { addPatchToManifest, loadPatchesManifest, mutatePatchRowsInManifest, PATCHES_MANIFEST, type PatchManifestRowMutation, type PatchManifestRowMutationResult, type PatchRenameEntry, removePatchFileAndManifest, renumberPatchesInManifest, rewriteStagedDependencyOwners, savePatchesManifest, } from './patch-manifest-io.js';
|
|
9
9
|
export { checkVersionCompatibility, findPatchesAffectingFile, getClaimedFiles, stampPatchVersions, validatePatchIntegrity, } from './patch-manifest-query.js';
|
|
10
10
|
export { resolvePatchIdentifier } from './patch-manifest-resolve.js';
|
|
11
11
|
export { validatePatchesManifest } from './patch-manifest-validate.js';
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* is an implementation detail.
|
|
7
7
|
*/
|
|
8
8
|
export { rebuildPatchesManifest, validatePatchesManifestConsistency, } from './patch-manifest-consistency.js';
|
|
9
|
-
export { addPatchToManifest, loadPatchesManifest, mutatePatchRowsInManifest, PATCHES_MANIFEST, removePatchFileAndManifest, renumberPatchesInManifest, savePatchesManifest, } from './patch-manifest-io.js';
|
|
9
|
+
export { addPatchToManifest, loadPatchesManifest, mutatePatchRowsInManifest, PATCHES_MANIFEST, removePatchFileAndManifest, renumberPatchesInManifest, rewriteStagedDependencyOwners, savePatchesManifest, } from './patch-manifest-io.js';
|
|
10
10
|
export { checkVersionCompatibility, findPatchesAffectingFile, getClaimedFiles, stampPatchVersions, validatePatchIntegrity, } from './patch-manifest-query.js';
|
|
11
11
|
export { resolvePatchIdentifier } from './patch-manifest-resolve.js';
|
|
12
12
|
export { validatePatchesManifest } from './patch-manifest-validate.js';
|
|
@@ -8,8 +8,6 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import type { PatchesManifest, PatchMetadata } from '../types/commands/index.js';
|
|
10
10
|
import type { FireForgeConfig } from '../types/config.js';
|
|
11
|
-
/** Default patch filename contract used when a policy omits `filenamePattern`. */
|
|
12
|
-
export declare const DEFAULT_PATCH_POLICY_FILENAME_PATTERN = "^(?<order>\\d{3})-(?<category>[a-z][a-z0-9-]*)-(?<slug>[a-z0-9-]+)\\.patch$";
|
|
13
11
|
/** Stable issue codes returned by patch policy evaluation. */
|
|
14
12
|
export type PatchPolicyIssueCode = 'filename-pattern' | 'filename-captures' | 'filename-metadata-mismatch' | 'order-collision' | 'category-range' | 'reserved-range' | 'reserved-documentation' | 'reserved-files' | 'description-required' | 'numeric-gap';
|
|
15
13
|
/** A single patch policy validation finding. */
|
|
@@ -26,8 +24,6 @@ export interface PatchPolicyEnforcementInput {
|
|
|
26
24
|
command: string;
|
|
27
25
|
forceUnsafe?: boolean;
|
|
28
26
|
}
|
|
29
|
-
/** Returns true when the loaded config includes an opt-in patch policy. */
|
|
30
|
-
export declare function hasPatchPolicy(config: FireForgeConfig): boolean;
|
|
31
27
|
/** Returns valid categories for prompts and CLI validation under the config. */
|
|
32
28
|
export declare function getPatchPolicyCategories(config: FireForgeConfig): string[];
|
|
33
29
|
/** Checks whether a category is accepted by legacy defaults or the policy ranges. */
|
|
@@ -10,8 +10,9 @@
|
|
|
10
10
|
import { InvalidArgumentError } from '../errors/base.js';
|
|
11
11
|
import { warn } from '../utils/logger.js';
|
|
12
12
|
import { PATCH_CATEGORIES } from '../utils/validation.js';
|
|
13
|
+
import { rewriteStagedDependencyOwners } from './patch-manifest-io.js';
|
|
13
14
|
/** Default patch filename contract used when a policy omits `filenamePattern`. */
|
|
14
|
-
|
|
15
|
+
const DEFAULT_PATCH_POLICY_FILENAME_PATTERN = '^(?<order>\\d{3})-(?<category>[a-z][a-z0-9-]*)-(?<slug>[a-z0-9-]+)\\.patch$';
|
|
15
16
|
function policy(config) {
|
|
16
17
|
return config.patchPolicy;
|
|
17
18
|
}
|
|
@@ -22,7 +23,7 @@ function issueSeverity(config) {
|
|
|
22
23
|
return mutationMode(config) === 'warn' ? 'warning' : 'error';
|
|
23
24
|
}
|
|
24
25
|
/** Returns true when the loaded config includes an opt-in patch policy. */
|
|
25
|
-
|
|
26
|
+
function hasPatchPolicy(config) {
|
|
26
27
|
return policy(config) !== undefined;
|
|
27
28
|
}
|
|
28
29
|
/** Returns valid categories for prompts and CLI validation under the config. */
|
|
@@ -305,12 +306,17 @@ export function buildProjectedManifest(current, patches) {
|
|
|
305
306
|
}
|
|
306
307
|
/** Applies a filename/order rename projection to a manifest without mutating it. */
|
|
307
308
|
export function applyRenameMapToManifest(manifest, renameMap) {
|
|
309
|
+
const ownerLookup = (oldFilename) => renameMap.get(oldFilename)?.newFilename;
|
|
308
310
|
return buildProjectedManifest(manifest, manifest.patches.map((patch) => {
|
|
311
|
+
// Staged-dependency owners reference other patches' filenames, so the
|
|
312
|
+
// projection rewrites them on every row to mirror what
|
|
313
|
+
// renumberPatchesInManifest persists.
|
|
314
|
+
const withOwners = rewriteStagedDependencyOwners(patch, ownerLookup);
|
|
309
315
|
const rename = renameMap.get(patch.filename);
|
|
310
316
|
if (!rename)
|
|
311
|
-
return
|
|
317
|
+
return withOwners;
|
|
312
318
|
return {
|
|
313
|
-
...
|
|
319
|
+
...withOwners,
|
|
314
320
|
filename: rename.newFilename,
|
|
315
321
|
order: rename.newOrder,
|
|
316
322
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Module registration in browser/modules/{binaryName}/moz.build.
|
|
3
3
|
*/
|
|
4
|
-
import type { RegisterResult } from './
|
|
4
|
+
import type { RegisterResult } from './register-result.js';
|
|
5
5
|
/**
|
|
6
6
|
* Registers a module in browser/modules/{binaryName}/moz.build.
|
|
7
7
|
*
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared result shape for manifest registration operations, split out of
|
|
3
|
+
* the `manifest-register.ts` barrel so the `register-*` leaf modules can
|
|
4
|
+
* import it without importing the barrel that re-exports them — that
|
|
5
|
+
* type-only back-edge made the registration dependency graph cyclic.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Result of a manifest registration operation.
|
|
9
|
+
*/
|
|
10
|
+
export interface RegisterResult {
|
|
11
|
+
/** The manifest file that was modified */
|
|
12
|
+
manifest: string;
|
|
13
|
+
/** The entry that was inserted */
|
|
14
|
+
entry: string;
|
|
15
|
+
/** The entry after which the new entry was inserted (for user display) */
|
|
16
|
+
previousEntry?: string | undefined;
|
|
17
|
+
/** Whether the entry already existed (skipped) */
|
|
18
|
+
skipped: boolean;
|
|
19
|
+
/** Whether --after target was not found and fell back to alphabetical */
|
|
20
|
+
afterFallback?: boolean | undefined;
|
|
21
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* Shared result shape for manifest registration operations, split out of
|
|
4
|
+
* the `manifest-register.ts` barrel so the `register-*` leaf modules can
|
|
5
|
+
* import it without importing the barrel that re-exports them — that
|
|
6
|
+
* type-only back-edge made the registration dependency graph cyclic.
|
|
7
|
+
*/
|
|
8
|
+
export {};
|
|
9
|
+
//# sourceMappingURL=register-result.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* CSS registration in browser/themes/shared/jar.inc.mn.
|
|
3
3
|
*/
|
|
4
|
-
import type { RegisterResult } from './
|
|
4
|
+
import type { RegisterResult } from './register-result.js';
|
|
5
5
|
/**
|
|
6
6
|
* Measures the column at which the `(source)` parenthesis opens in
|
|
7
7
|
* adjacent `skin/classic/browser/<x>.css (...)` entries inside an
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Test manifest registration in browser/base/moz.build.
|
|
3
3
|
*/
|
|
4
|
-
import type { RegisterResult } from './
|
|
4
|
+
import type { RegisterResult } from './register-result.js';
|
|
5
5
|
/**
|
|
6
6
|
* Registers a test manifest (browser.toml) in browser/base/moz.build.
|
|
7
7
|
*
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Harness-crash classification for `fireforge test` (field reports C1/C2).
|
|
3
|
+
*
|
|
4
|
+
* The wrapped mach harness exhibits flaky non-test failures that an exit
|
|
5
|
+
* code (or a "did it print a summary" grep) cannot distinguish from real
|
|
6
|
+
* test results:
|
|
7
|
+
*
|
|
8
|
+
* - startup crashes from the mozlog resource monitor on macOS
|
|
9
|
+
* (`AttributeError: 'SystemResourceMonitor' object has no attribute
|
|
10
|
+
* 'poll_interval'`, `host_statistics64(HOST_VM_INFO64) syscall failed`,
|
|
11
|
+
* `(ipc/mig) array not large enough` — a psutil/macOS mismatch) which
|
|
12
|
+
* abort the run before any test executes;
|
|
13
|
+
* - hangs after browser startup that die at the no-output timeout yet
|
|
14
|
+
* still emit a `Passed: 0` summary;
|
|
15
|
+
* - post-green shutdown re-entry, where a fully green run stalls on
|
|
16
|
+
* "must wait for focus" and records "Application shut down (without
|
|
17
|
+
* crashing) in the middle of a test!" as the only unexpected failure.
|
|
18
|
+
*
|
|
19
|
+
* Classification therefore keys on `TEST-START` presence — summary lines
|
|
20
|
+
* never count as proof that tests ran — and recognizes the crash shapes
|
|
21
|
+
* above so the command layer can retry them with a bounded budget instead
|
|
22
|
+
* of reporting phantom test failures (or phantom passes).
|
|
23
|
+
*/
|
|
24
|
+
/** How a completed harness run should be interpreted. */
|
|
25
|
+
export type HarnessRunClassification = 'tests-ran-ok' | 'test-failures' | 'harness-crash' | 'no-tests';
|
|
26
|
+
/** A recognized harness-crash shape with its evidence line. */
|
|
27
|
+
export interface HarnessCrashSignature {
|
|
28
|
+
reason: string;
|
|
29
|
+
line: string;
|
|
30
|
+
}
|
|
31
|
+
/** Result of {@link classifyHarnessRun}. */
|
|
32
|
+
export interface HarnessRunVerdict {
|
|
33
|
+
kind: HarnessRunClassification;
|
|
34
|
+
signature?: HarnessCrashSignature;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Detects the known harness-crash shapes in captured mach output.
|
|
38
|
+
* Returns undefined for anything that looks like a genuine test result.
|
|
39
|
+
*/
|
|
40
|
+
export declare function detectHarnessCrashSignature(output: string): HarnessCrashSignature | undefined;
|
|
41
|
+
/**
|
|
42
|
+
* Classifies a completed harness run. The decision tree, in order:
|
|
43
|
+
*
|
|
44
|
+
* 1. A recognized crash signature wins regardless of exit code (the
|
|
45
|
+
* shutdown re-entry shape exits non-zero on an otherwise green run;
|
|
46
|
+
* the hang shape can even exit zero with a `Passed: 0` summary).
|
|
47
|
+
* 2. No `TEST-START` with explicit paths requested means no test ran —
|
|
48
|
+
* `no-tests`, even when the exit code is zero. Summary lines are not
|
|
49
|
+
* trusted as evidence of execution.
|
|
50
|
+
* 3. Exit code zero with tests started is a pass; anything else is a
|
|
51
|
+
* test failure for the regular diagnosis chain.
|
|
52
|
+
*/
|
|
53
|
+
export declare function classifyHarnessRun(exitCode: number, output: string, requestedPaths: readonly string[]): HarnessRunVerdict;
|
|
54
|
+
/** Builds the operator-facing failure message after retries are exhausted. */
|
|
55
|
+
export declare function buildHarnessCrashMessage(signature: HarnessCrashSignature, attempts: number): string;
|
|
56
|
+
/**
|
|
57
|
+
* Builds the message for a run that produced no `TEST-START` despite
|
|
58
|
+
* requesting paths — including exit-code-zero runs whose `Passed: 0`
|
|
59
|
+
* summary would otherwise read as a silent false green.
|
|
60
|
+
*/
|
|
61
|
+
export declare function buildNoTestsRanMessage(exitCode: number, requestedPaths: readonly string[]): string;
|