@hominis/fireforge 0.32.0 → 0.34.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 +25 -0
- package/dist/src/commands/build.js +9 -1
- package/dist/src/commands/doctor-furnace-jar.d.ts +16 -0
- package/dist/src/commands/doctor-furnace-jar.js +47 -0
- package/dist/src/commands/doctor-furnace.js +2 -0
- package/dist/src/commands/export-flow.js +4 -1
- package/dist/src/commands/export-placement-gate.js +4 -1
- package/dist/src/commands/export.js +61 -4
- package/dist/src/commands/furnace/chrome-doc-templates.d.ts +20 -0
- package/dist/src/commands/furnace/chrome-doc-templates.js +52 -0
- package/dist/src/commands/furnace/chrome-doc.d.ts +10 -0
- package/dist/src/commands/furnace/chrome-doc.js +31 -4
- package/dist/src/commands/furnace/create-browser-test.d.ts +30 -0
- package/dist/src/commands/furnace/create-browser-test.js +180 -0
- package/dist/src/commands/furnace/create-mochikit.js +11 -4
- package/dist/src/commands/furnace/create-xpcshell.d.ts +1 -1
- package/dist/src/commands/furnace/create-xpcshell.js +38 -12
- package/dist/src/commands/furnace/create.js +7 -114
- package/dist/src/commands/furnace/index.js +6 -1
- package/dist/src/commands/furnace/scan.d.ts +1 -0
- package/dist/src/commands/furnace/scan.js +57 -27
- package/dist/src/commands/furnace/validate.js +17 -1
- package/dist/src/commands/patch/split-plan.d.ts +18 -2
- package/dist/src/commands/patch/split-plan.js +90 -16
- package/dist/src/commands/patch/split.js +12 -3
- package/dist/src/commands/re-export-options.d.ts +26 -0
- package/dist/src/commands/re-export-options.js +48 -1
- package/dist/src/commands/re-export.js +4 -1
- package/dist/src/commands/register.js +10 -1
- package/dist/src/commands/test-diagnose.js +12 -0
- package/dist/src/commands/test.js +29 -22
- package/dist/src/commands/token.js +12 -1
- package/dist/src/commands/typecheck.js +35 -0
- package/dist/src/core/build-prepare.js +23 -3
- package/dist/src/core/config-validate.js +26 -0
- package/dist/src/core/furnace-apply-dry-run.d.ts +17 -0
- package/dist/src/core/furnace-apply-dry-run.js +105 -0
- package/dist/src/core/furnace-apply-ftl.d.ts +12 -0
- package/dist/src/core/furnace-apply-ftl.js +97 -1
- package/dist/src/core/furnace-apply-helpers.js +10 -80
- package/dist/src/core/furnace-registration.d.ts +24 -0
- package/dist/src/core/furnace-registration.js +56 -3
- package/dist/src/core/furnace-validate-registration.js +18 -0
- package/dist/src/core/mach-resource-shim.d.ts +58 -0
- package/dist/src/core/mach-resource-shim.js +290 -0
- package/dist/src/core/mach.d.ts +51 -10
- package/dist/src/core/mach.js +76 -25
- package/dist/src/core/manifest-helpers.d.ts +13 -0
- package/dist/src/core/manifest-helpers.js +29 -4
- package/dist/src/core/manifest-rules.d.ts +13 -3
- package/dist/src/core/manifest-rules.js +44 -17
- package/dist/src/core/patch-export.d.ts +10 -0
- package/dist/src/core/patch-export.js +23 -2
- package/dist/src/core/patch-lint-cross.d.ts +31 -0
- package/dist/src/core/patch-lint-cross.js +83 -63
- package/dist/src/core/patch-lint-reexports.d.ts +1 -1
- package/dist/src/core/patch-lint-reexports.js +1 -1
- package/dist/src/core/register-module.d.ts +1 -1
- package/dist/src/core/register-module.js +15 -2
- package/dist/src/core/register-result.d.ts +9 -0
- package/dist/src/core/register-scaffold.d.ts +55 -0
- package/dist/src/core/register-scaffold.js +146 -0
- package/dist/src/core/register-test-manifest.js +3 -1
- package/dist/src/core/register-xpcshell-test.d.ts +30 -0
- package/dist/src/core/register-xpcshell-test.js +96 -0
- package/dist/src/core/test-harness-crash.d.ts +32 -4
- package/dist/src/core/test-harness-crash.js +152 -14
- package/dist/src/core/token-dark-mode.d.ts +9 -0
- package/dist/src/core/token-dark-mode.js +1 -1
- package/dist/src/core/token-docs.d.ts +32 -0
- package/dist/src/core/token-docs.js +101 -0
- package/dist/src/core/token-manager.d.ts +8 -0
- package/dist/src/core/token-manager.js +77 -95
- package/dist/src/core/token-variant.d.ts +39 -0
- package/dist/src/core/token-variant.js +141 -0
- package/dist/src/core/typecheck.js +56 -28
- package/dist/src/core/xpcshell-appdir.d.ts +7 -0
- package/dist/src/core/xpcshell-appdir.js +12 -3
- package/dist/src/types/commands/options.d.ts +17 -0
- package/dist/src/types/config.d.ts +13 -0
- package/package.json +4 -4
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* Manifest scaffolding for `fireforge register --create-manifest`
|
|
4
|
+
* (0.34.0 field report): registering a module under a directory with no
|
|
5
|
+
* moz.build used to fail with "Manifest not found" because `register`
|
|
6
|
+
* only inserts into EXISTING manifests. This module owns the two
|
|
7
|
+
* scaffolding primitives:
|
|
8
|
+
*
|
|
9
|
+
* - creating a directory `moz.build` (license header + the requested
|
|
10
|
+
* list directive) and wiring the parent chain's `DIRS` entries up to
|
|
11
|
+
* the nearest existing moz.build;
|
|
12
|
+
* - wiring an `XPCSHELL_TESTS_MANIFESTS` entry for a (possibly freshly
|
|
13
|
+
* created) `xpcshell.toml` into the nearest existing moz.build.
|
|
14
|
+
*
|
|
15
|
+
* All writers reuse the tokenizer + alphabetical-position helpers the
|
|
16
|
+
* existing register writers use, so inserted entries respect mozbuild's
|
|
17
|
+
* case-insensitive StrictOrderingOnAppendList rule.
|
|
18
|
+
*/
|
|
19
|
+
import { dirname, join } from 'node:path';
|
|
20
|
+
import { GeneralError } from '../errors/base.js';
|
|
21
|
+
import { pathExists, readText, writeText } from '../utils/fs.js';
|
|
22
|
+
import { getLicenseHeader } from './license-headers.js';
|
|
23
|
+
import { findAlphabeticalMozBuildPosition } from './manifest-helpers.js';
|
|
24
|
+
import { tokenizeMozBuildList } from './manifest-tokenizers.js';
|
|
25
|
+
/**
|
|
26
|
+
* moz.build files use Firefox's canonical MPL-2.0 hash-comment header
|
|
27
|
+
* regardless of the fork's own project license — they live in the engine
|
|
28
|
+
* tree among upstream MPL files.
|
|
29
|
+
*/
|
|
30
|
+
function mozBuildHeader() {
|
|
31
|
+
return getLicenseHeader('MPL-2.0', 'hash');
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Inserts `value` into the named moz.build list directive, creating the
|
|
35
|
+
* `NAME += [ ... ]` block at the end of the file when the directive does
|
|
36
|
+
* not exist yet. Returns the updated content, or null when the value is
|
|
37
|
+
* already present.
|
|
38
|
+
*/
|
|
39
|
+
export function upsertMozBuildListEntry(content, directive, value) {
|
|
40
|
+
if (content.includes(`"${value}"`))
|
|
41
|
+
return null;
|
|
42
|
+
const lines = content.split('\n');
|
|
43
|
+
const listResult = tokenizeMozBuildList(lines, new RegExp(`^${directive}\\b`));
|
|
44
|
+
if (listResult) {
|
|
45
|
+
const { insertIndex } = findAlphabeticalMozBuildPosition(listResult.tokens, value);
|
|
46
|
+
lines.splice(insertIndex, 0, ` "${value}",`);
|
|
47
|
+
return lines.join('\n');
|
|
48
|
+
}
|
|
49
|
+
// No existing list: append a fresh block, keeping exactly one blank
|
|
50
|
+
// line between the previous content and the new directive.
|
|
51
|
+
while (lines.length > 0 && (lines.at(-1) ?? '').trim() === '')
|
|
52
|
+
lines.pop();
|
|
53
|
+
const block = [`${directive} += [`, ` "${value}",`, `]`];
|
|
54
|
+
return [...lines, ...(lines.length > 0 ? [''] : []), ...block, ''].join('\n');
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Ensures every parent directory between `childRelDir` and the nearest
|
|
58
|
+
* existing moz.build carries a `DIRS` entry pointing at the next segment
|
|
59
|
+
* down, creating intermediate moz.build files as needed. Walks upward
|
|
60
|
+
* from the child's parent; throws when no moz.build exists anywhere up
|
|
61
|
+
* to the engine root (an engine checkout always has one at the root, so
|
|
62
|
+
* this indicates a bogus path).
|
|
63
|
+
*/
|
|
64
|
+
export async function ensureParentDirsWiring(engineDir, childRelDir, dryRun) {
|
|
65
|
+
const actions = [];
|
|
66
|
+
let childName = childRelDir.split('/').at(-1) ?? childRelDir;
|
|
67
|
+
let parentRel = dirname(childRelDir).replace(/\\/g, '/');
|
|
68
|
+
for (;;) {
|
|
69
|
+
if (parentRel === '.' || parentRel === '' || parentRel === '/') {
|
|
70
|
+
throw new GeneralError(`No moz.build found in any parent of ${childRelDir} up to the engine root; ` +
|
|
71
|
+
'cannot wire the DIRS chain. Check the path.');
|
|
72
|
+
}
|
|
73
|
+
const parentManifestRel = `${parentRel}/moz.build`;
|
|
74
|
+
const parentManifestPath = join(engineDir, parentManifestRel);
|
|
75
|
+
if (await pathExists(parentManifestPath)) {
|
|
76
|
+
const content = await readText(parentManifestPath);
|
|
77
|
+
const updated = upsertMozBuildListEntry(content, 'DIRS', childName);
|
|
78
|
+
if (updated !== null) {
|
|
79
|
+
if (!dryRun)
|
|
80
|
+
await writeText(parentManifestPath, updated);
|
|
81
|
+
actions.push({ manifest: parentManifestRel, change: `DIRS += ["${childName}"]` });
|
|
82
|
+
}
|
|
83
|
+
return actions;
|
|
84
|
+
}
|
|
85
|
+
// Parent has no moz.build either: scaffold one carrying the DIRS
|
|
86
|
+
// entry and keep walking up until an existing manifest anchors the
|
|
87
|
+
// chain.
|
|
88
|
+
const scaffold = `${mozBuildHeader()}\n\nDIRS += [\n "${childName}",\n]\n`;
|
|
89
|
+
if (!dryRun)
|
|
90
|
+
await writeText(parentManifestPath, scaffold);
|
|
91
|
+
actions.push({ manifest: parentManifestRel, change: `created with DIRS += ["${childName}"]` });
|
|
92
|
+
childName = parentRel.split('/').at(-1) ?? parentRel;
|
|
93
|
+
parentRel = dirname(parentRel).replace(/\\/g, '/');
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Creates `<moduleDir>/moz.build` with an `EXTRA_JS_MODULES.<namespace>`
|
|
98
|
+
* list containing `fileName`, and wires the parent DIRS chain. The
|
|
99
|
+
* namespace is the module directory's basename (the fork's binary name
|
|
100
|
+
* for `browser/modules/<binaryName>/`), matching Firefox's convention of
|
|
101
|
+
* mapping `EXTRA_JS_MODULES.<ns>` into `resource:///modules/<ns>/`.
|
|
102
|
+
*/
|
|
103
|
+
export async function scaffoldModuleMozBuild(engineDir, moduleDir, fileName, dryRun) {
|
|
104
|
+
const namespace = moduleDir.split('/').at(-1) ?? moduleDir;
|
|
105
|
+
const manifestRel = `${moduleDir}/moz.build`;
|
|
106
|
+
const content = `${mozBuildHeader()}\n\nEXTRA_JS_MODULES.${namespace} += [\n "${fileName}",\n]\n`;
|
|
107
|
+
if (!dryRun)
|
|
108
|
+
await writeText(join(engineDir, manifestRel), content);
|
|
109
|
+
const actions = [
|
|
110
|
+
{
|
|
111
|
+
manifest: manifestRel,
|
|
112
|
+
change: `created with EXTRA_JS_MODULES.${namespace} += ["${fileName}"]`,
|
|
113
|
+
},
|
|
114
|
+
];
|
|
115
|
+
actions.push(...(await ensureParentDirsWiring(engineDir, moduleDir, dryRun)));
|
|
116
|
+
return actions;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Wires an `XPCSHELL_TESTS_MANIFESTS` entry for `manifestRelPath` (an
|
|
120
|
+
* engine-relative `.../xpcshell.toml`) into the nearest EXISTING
|
|
121
|
+
* moz.build above it. Returns the performed actions (empty when the
|
|
122
|
+
* entry already exists).
|
|
123
|
+
*/
|
|
124
|
+
export async function ensureXpcshellManifestWiring(engineDir, manifestRelPath, dryRun) {
|
|
125
|
+
let dirRel = dirname(manifestRelPath).replace(/\\/g, '/');
|
|
126
|
+
for (;;) {
|
|
127
|
+
if (dirRel === '.' || dirRel === '' || dirRel === '/') {
|
|
128
|
+
throw new GeneralError(`No moz.build found in any parent of ${manifestRelPath} up to the engine root; ` +
|
|
129
|
+
'cannot wire XPCSHELL_TESTS_MANIFESTS. Check the path.');
|
|
130
|
+
}
|
|
131
|
+
const mozBuildRel = `${dirRel}/moz.build`;
|
|
132
|
+
const mozBuildPath = join(engineDir, mozBuildRel);
|
|
133
|
+
if (await pathExists(mozBuildPath)) {
|
|
134
|
+
const relEntry = manifestRelPath.slice(dirRel.length + 1);
|
|
135
|
+
const content = await readText(mozBuildPath);
|
|
136
|
+
const updated = upsertMozBuildListEntry(content, 'XPCSHELL_TESTS_MANIFESTS', relEntry);
|
|
137
|
+
if (updated === null)
|
|
138
|
+
return [];
|
|
139
|
+
if (!dryRun)
|
|
140
|
+
await writeText(mozBuildPath, updated);
|
|
141
|
+
return [{ manifest: mozBuildRel, change: `XPCSHELL_TESTS_MANIFESTS += ["${relEntry}"]` }];
|
|
142
|
+
}
|
|
143
|
+
dirRel = dirname(dirRel).replace(/\\/g, '/');
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
//# sourceMappingURL=register-scaffold.js.map
|
|
@@ -27,7 +27,9 @@ function registerTestManifestTokenized(content, testDir, entry) {
|
|
|
27
27
|
function legacyRegisterTestManifest(content, testDir, entry) {
|
|
28
28
|
const lines = content.split('\n');
|
|
29
29
|
const extractKey = (line) => {
|
|
30
|
-
|
|
30
|
+
// Nested manifests (`content/test/a/b/browser.toml`) are supported —
|
|
31
|
+
// capture the whole directory chain, not just one segment.
|
|
32
|
+
const match = /"content\/test\/(.+)\/browser\.toml"/.exec(line);
|
|
31
33
|
return match?.[1];
|
|
32
34
|
};
|
|
33
35
|
let sectionStart = -1;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* xpcshell test-file registration (0.34.0 field report): xpcshell test
|
|
3
|
+
* files (e.g. `<module-dir>/test/unit/test_*.js`) were rejected by
|
|
4
|
+
* `fireforge register` as "Unknown file pattern". This writer inserts the
|
|
5
|
+
* `["test_*.js"]` section into the directory's `xpcshell.toml`
|
|
6
|
+
* (alphabetically, idempotently) and — with `--create-manifest` — creates
|
|
7
|
+
* the manifest and wires `XPCSHELL_TESTS_MANIFESTS` into the nearest
|
|
8
|
+
* moz.build.
|
|
9
|
+
*/
|
|
10
|
+
import type { RegisterResult } from './register-result.js';
|
|
11
|
+
/**
|
|
12
|
+
* Checks whether the test file is listed in its directory's
|
|
13
|
+
* `xpcshell.toml`. Throws a "Manifest not found" error when the manifest
|
|
14
|
+
* does not exist (status catches that prefix and reports the manifest as
|
|
15
|
+
* missing rather than the file as unregistered).
|
|
16
|
+
*/
|
|
17
|
+
export declare function isXpcshellTestRegistered(engineDir: string, dirRel: string, fileName: string): Promise<boolean>;
|
|
18
|
+
/**
|
|
19
|
+
* Inserts the `["<fileName>"]` section into the manifest content in
|
|
20
|
+
* mozbuild's case-insensitive sort order relative to the other test
|
|
21
|
+
* sections, keeping one blank line between sections. Exported for direct
|
|
22
|
+
* unit testing.
|
|
23
|
+
*/
|
|
24
|
+
export declare function insertXpcshellManifestSection(content: string, fileName: string): string;
|
|
25
|
+
/**
|
|
26
|
+
* Registers an xpcshell test file in its directory's `xpcshell.toml`.
|
|
27
|
+
* With `createManifest`, a missing manifest is scaffolded and wired into
|
|
28
|
+
* the nearest moz.build via `XPCSHELL_TESTS_MANIFESTS`.
|
|
29
|
+
*/
|
|
30
|
+
export declare function registerXpcshellTest(engineDir: string, dirRel: string, fileName: string, dryRun?: boolean, createManifest?: boolean): Promise<RegisterResult>;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* xpcshell test-file registration (0.34.0 field report): xpcshell test
|
|
4
|
+
* files (e.g. `<module-dir>/test/unit/test_*.js`) were rejected by
|
|
5
|
+
* `fireforge register` as "Unknown file pattern". This writer inserts the
|
|
6
|
+
* `["test_*.js"]` section into the directory's `xpcshell.toml`
|
|
7
|
+
* (alphabetically, idempotently) and — with `--create-manifest` — creates
|
|
8
|
+
* the manifest and wires `XPCSHELL_TESTS_MANIFESTS` into the nearest
|
|
9
|
+
* moz.build.
|
|
10
|
+
*/
|
|
11
|
+
import { join } from 'node:path';
|
|
12
|
+
import { GeneralError } from '../errors/base.js';
|
|
13
|
+
import { pathExists, readText, writeText } from '../utils/fs.js';
|
|
14
|
+
import { mozbuildSortCompare } from './manifest-helpers.js';
|
|
15
|
+
import { ensureXpcshellManifestWiring } from './register-scaffold.js';
|
|
16
|
+
function manifestMissingError(manifestRel) {
|
|
17
|
+
return new GeneralError(`Manifest not found: ${manifestRel}. ` +
|
|
18
|
+
'Pass --create-manifest to scaffold the xpcshell.toml and wire XPCSHELL_TESTS_MANIFESTS ' +
|
|
19
|
+
'into the nearest moz.build.');
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Checks whether the test file is listed in its directory's
|
|
23
|
+
* `xpcshell.toml`. Throws a "Manifest not found" error when the manifest
|
|
24
|
+
* does not exist (status catches that prefix and reports the manifest as
|
|
25
|
+
* missing rather than the file as unregistered).
|
|
26
|
+
*/
|
|
27
|
+
export async function isXpcshellTestRegistered(engineDir, dirRel, fileName) {
|
|
28
|
+
const manifestRel = `${dirRel}/xpcshell.toml`;
|
|
29
|
+
const manifestPath = join(engineDir, manifestRel);
|
|
30
|
+
if (!(await pathExists(manifestPath))) {
|
|
31
|
+
throw manifestMissingError(manifestRel);
|
|
32
|
+
}
|
|
33
|
+
const content = await readText(manifestPath);
|
|
34
|
+
return content.includes(`["${fileName}"]`);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Inserts the `["<fileName>"]` section into the manifest content in
|
|
38
|
+
* mozbuild's case-insensitive sort order relative to the other test
|
|
39
|
+
* sections, keeping one blank line between sections. Exported for direct
|
|
40
|
+
* unit testing.
|
|
41
|
+
*/
|
|
42
|
+
export function insertXpcshellManifestSection(content, fileName) {
|
|
43
|
+
const lines = content.split('\n');
|
|
44
|
+
const sectionHeaderPattern = /^\["([^"]+)"\]\s*$/;
|
|
45
|
+
let insertIndex = -1;
|
|
46
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
47
|
+
const match = sectionHeaderPattern.exec(lines[i] ?? '');
|
|
48
|
+
if (!match)
|
|
49
|
+
continue;
|
|
50
|
+
if (insertIndex === -1 && mozbuildSortCompare(fileName, match[1] ?? '') < 0) {
|
|
51
|
+
insertIndex = i;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (insertIndex !== -1) {
|
|
55
|
+
lines.splice(insertIndex, 0, `["${fileName}"]`, '');
|
|
56
|
+
return lines.join('\n');
|
|
57
|
+
}
|
|
58
|
+
// Append after the last section (or at end of file when there are no
|
|
59
|
+
// sections yet), keeping exactly one trailing newline.
|
|
60
|
+
while (lines.length > 0 && (lines.at(-1) ?? '').trim() === '')
|
|
61
|
+
lines.pop();
|
|
62
|
+
return [...lines, '', `["${fileName}"]`, ''].join('\n');
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Registers an xpcshell test file in its directory's `xpcshell.toml`.
|
|
66
|
+
* With `createManifest`, a missing manifest is scaffolded and wired into
|
|
67
|
+
* the nearest moz.build via `XPCSHELL_TESTS_MANIFESTS`.
|
|
68
|
+
*/
|
|
69
|
+
export async function registerXpcshellTest(engineDir, dirRel, fileName, dryRun = false, createManifest = false) {
|
|
70
|
+
const manifestRel = `${dirRel}/xpcshell.toml`;
|
|
71
|
+
const manifestPath = join(engineDir, manifestRel);
|
|
72
|
+
const entry = `["${fileName}"]`;
|
|
73
|
+
if (!(await pathExists(manifestPath))) {
|
|
74
|
+
if (!createManifest) {
|
|
75
|
+
throw manifestMissingError(manifestRel);
|
|
76
|
+
}
|
|
77
|
+
const scaffoldActions = [
|
|
78
|
+
{ manifest: manifestRel, change: `created with ${entry}` },
|
|
79
|
+
];
|
|
80
|
+
if (!dryRun) {
|
|
81
|
+
await writeText(manifestPath, `[DEFAULT]\n\n${entry}\n`);
|
|
82
|
+
}
|
|
83
|
+
scaffoldActions.push(...(await ensureXpcshellManifestWiring(engineDir, manifestRel, dryRun)));
|
|
84
|
+
return { manifest: manifestRel, entry, skipped: false, scaffoldActions };
|
|
85
|
+
}
|
|
86
|
+
const content = await readText(manifestPath);
|
|
87
|
+
if (content.includes(entry)) {
|
|
88
|
+
return { manifest: manifestRel, entry, skipped: true };
|
|
89
|
+
}
|
|
90
|
+
const updated = insertXpcshellManifestSection(content, fileName);
|
|
91
|
+
if (!dryRun) {
|
|
92
|
+
await writeText(manifestPath, updated);
|
|
93
|
+
}
|
|
94
|
+
return { manifest: manifestRel, entry, skipped: false };
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=register-xpcshell-test.js.map
|
|
@@ -32,7 +32,32 @@ export interface HarnessCrashSignature {
|
|
|
32
32
|
export interface HarnessRunVerdict {
|
|
33
33
|
kind: HarnessRunClassification;
|
|
34
34
|
signature?: HarnessCrashSignature;
|
|
35
|
+
/**
|
|
36
|
+
* Set when a non-zero mach exit code was overridden because the output
|
|
37
|
+
* embeds a completed green summary — the exit code followed harness
|
|
38
|
+
* noise, not a test result. Callers should surface a note.
|
|
39
|
+
*/
|
|
40
|
+
greenSummaryOverride?: boolean;
|
|
35
41
|
}
|
|
42
|
+
/**
|
|
43
|
+
* Strips non-signal noise from captured output before crash-signature
|
|
44
|
+
* matching: resource-monitor degradation warnings, and traceback blocks
|
|
45
|
+
* that mach caught itself (telemetry submission tracebacks are printed but
|
|
46
|
+
* never abort the run). The stripped text is used ONLY as crash evidence —
|
|
47
|
+
* classification of test results still reads the full output.
|
|
48
|
+
*/
|
|
49
|
+
export declare function stripNonSignalNoise(output: string): string;
|
|
50
|
+
/**
|
|
51
|
+
* True when the output embeds a COMPLETED, GREEN suite summary: an
|
|
52
|
+
* execution signal, `Unexpected results: 0` (and no non-zero unexpected
|
|
53
|
+
* count), a `SUITE_END` marker, and no real `TEST-UNEXPECTED-*` lines.
|
|
54
|
+
* Such a run finished its suite; any startup-traceback-shaped noise in the
|
|
55
|
+
* same output is by definition non-fatal, so this vetoes signature-based
|
|
56
|
+
* crash classification (field report: fully green sharded runs reported
|
|
57
|
+
* `CRASH (N attempts)` because degradation warnings matched the psutil
|
|
58
|
+
* signals). Exported for direct unit testing.
|
|
59
|
+
*/
|
|
60
|
+
export declare function hasCompletedGreenSummary(output: string): boolean;
|
|
36
61
|
/**
|
|
37
62
|
* Detects the known harness-crash shapes in captured mach output.
|
|
38
63
|
* Returns undefined for anything that looks like a genuine test result.
|
|
@@ -44,15 +69,18 @@ export declare function detectHarnessCrashSignature(output: string): HarnessCras
|
|
|
44
69
|
* 1. A recognized crash signature wins regardless of exit code (the
|
|
45
70
|
* shutdown re-entry shape exits non-zero on an otherwise green run;
|
|
46
71
|
* the hang shape can even exit zero with a `Passed: 0` summary).
|
|
47
|
-
* 2. No
|
|
48
|
-
* `no-tests`, even when the exit code is zero.
|
|
49
|
-
*
|
|
72
|
+
* 2. No execution signal with explicit paths requested means no test ran —
|
|
73
|
+
* `no-tests`, even when the exit code is zero. The execution signal is
|
|
74
|
+
* a `TEST-START` line (generic `mach test` / browser-chrome dispatch)
|
|
75
|
+
* OR the suite-specific xpcshell result-summary block (the xpcshell
|
|
76
|
+
* dispatch prints no `TEST-START`). Bare `Passed:`/`Failed:` summary
|
|
77
|
+
* lines are still not trusted as evidence of execution.
|
|
50
78
|
* 3. Exit code zero with tests started is a pass; anything else is a
|
|
51
79
|
* test failure for the regular diagnosis chain.
|
|
52
80
|
*/
|
|
53
81
|
export declare function classifyHarnessRun(exitCode: number, output: string, requestedPaths: readonly string[]): HarnessRunVerdict;
|
|
54
82
|
/** Builds the operator-facing failure message after retries are exhausted. */
|
|
55
|
-
export declare function buildHarnessCrashMessage(signature: HarnessCrashSignature, attempts: number): string;
|
|
83
|
+
export declare function buildHarnessCrashMessage(signature: HarnessCrashSignature, attempts: number, commandLabel?: string): string;
|
|
56
84
|
/**
|
|
57
85
|
* Builds the message for a run that produced no `TEST-START` despite
|
|
58
86
|
* requesting paths — including exit-code-zero runs whose `Passed: 0`
|
|
@@ -23,6 +23,29 @@
|
|
|
23
23
|
* of reporting phantom test failures (or phantom passes).
|
|
24
24
|
*/
|
|
25
25
|
const TEST_START_PATTERN = /\bTEST-START\b/;
|
|
26
|
+
/**
|
|
27
|
+
* Structured-log execution marker (`mach xpcshell-test` mozlog output uses
|
|
28
|
+
* `TEST_START`/`SUITE_END` with underscores, not the hyphenated
|
|
29
|
+
* `TEST-START` the browser-chrome dispatch prints).
|
|
30
|
+
*/
|
|
31
|
+
const STRUCTURED_TEST_START_PATTERN = /\bTEST_START\b/;
|
|
32
|
+
const SUITE_END_PATTERN = /\bSUITE_END\b/;
|
|
33
|
+
const GREEN_UNEXPECTED_SUMMARY_PATTERN = /\bUnexpected results:\s*0\b/;
|
|
34
|
+
const NONZERO_UNEXPECTED_SUMMARY_PATTERN = /\bUnexpected results:\s*[1-9]/;
|
|
35
|
+
/**
|
|
36
|
+
* Execution signals emitted by the suite-specific xpcshell dispatch
|
|
37
|
+
* (`mach xpcshell-test`), which does NOT print `TEST-START` lines the way
|
|
38
|
+
* the generic `mach test` / browser-chrome dispatch does. A passing
|
|
39
|
+
* single-file xpcshell run prints a result-summary block instead
|
|
40
|
+
* (`TEST_END: Test PASS`, `Ran 16 checks`, `Unexpected results: 0`), so
|
|
41
|
+
* keying execution purely on `TEST-START` mis-reads a green xpcshell run as
|
|
42
|
+
* "no tests started" (field report: a single-file xpcshell pass exited 1).
|
|
43
|
+
*
|
|
44
|
+
* These markers are xpcshell-specific on purpose: the bare
|
|
45
|
+
* `Passed: 0` / `Failed: 0` summary that the no-output hang shape prints is
|
|
46
|
+
* deliberately NOT matched here — that case must still read as `no-tests`.
|
|
47
|
+
*/
|
|
48
|
+
const XPCSHELL_RESULT_SUMMARY_PATTERN = /\bTEST_END\b|\bRan \d+ checks?\b|\bResult summary:/i;
|
|
26
49
|
const UNEXPECTED_LINE_PATTERN = /^.*\bTEST-UNEXPECTED-[A-Z-]+\b.*$/gm;
|
|
27
50
|
const SHUTDOWN_REENTRY_PATTERN = /Application shut down \(without crashing\) in the middle of a test/i;
|
|
28
51
|
const FOCUS_STALL_PATTERN = /must wait for focus/i;
|
|
@@ -49,6 +72,97 @@ function findLine(output, patterns) {
|
|
|
49
72
|
}
|
|
50
73
|
return undefined;
|
|
51
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* Non-signal noise lines: the resource-monitor degrade path (FireForge's
|
|
77
|
+
* own guard plus mozlog's `_collect failed`) prints warnings on runs that
|
|
78
|
+
* then complete green. Field report (0.34.0 cycle): every multi-file suite
|
|
79
|
+
* was reported CRASH because these lines matched the startup-traceback
|
|
80
|
+
* signals even though the embedded summary was fully green. They are
|
|
81
|
+
* excluded from crash evidence entirely.
|
|
82
|
+
*/
|
|
83
|
+
const NOISE_LINE_PATTERNS = [
|
|
84
|
+
/\bUserWarning\b/,
|
|
85
|
+
/psutil failed to run/i,
|
|
86
|
+
/_collect failed/i,
|
|
87
|
+
/FireForge: host resource monitor degraded/i,
|
|
88
|
+
/warnings\.warn\(/,
|
|
89
|
+
];
|
|
90
|
+
/** Matches the caught/telemetry context that marks a traceback as benign. */
|
|
91
|
+
const BENIGN_TRACEBACK_CONTEXT = /telemetry|glean/i;
|
|
92
|
+
/**
|
|
93
|
+
* Strips non-signal noise from captured output before crash-signature
|
|
94
|
+
* matching: resource-monitor degradation warnings, and traceback blocks
|
|
95
|
+
* that mach caught itself (telemetry submission tracebacks are printed but
|
|
96
|
+
* never abort the run). The stripped text is used ONLY as crash evidence —
|
|
97
|
+
* classification of test results still reads the full output.
|
|
98
|
+
*/
|
|
99
|
+
export function stripNonSignalNoise(output) {
|
|
100
|
+
const lines = output.split(/\r?\n/);
|
|
101
|
+
const kept = [];
|
|
102
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
103
|
+
const line = lines[i] ?? '';
|
|
104
|
+
if (NOISE_LINE_PATTERNS.some((p) => p.test(line)))
|
|
105
|
+
continue;
|
|
106
|
+
if (TRACEBACK_PATTERN.test(line)) {
|
|
107
|
+
// Collect the whole traceback block: the header, indented frame/code
|
|
108
|
+
// lines, and the trailing unindented exception line.
|
|
109
|
+
const block = [line];
|
|
110
|
+
let j = i + 1;
|
|
111
|
+
for (; j < lines.length; j += 1) {
|
|
112
|
+
const blockLine = lines[j] ?? '';
|
|
113
|
+
block.push(blockLine);
|
|
114
|
+
const isIndented = /^\s/.test(blockLine) || blockLine.trim().length === 0;
|
|
115
|
+
if (!isIndented)
|
|
116
|
+
break; // unindented exception line terminates the block
|
|
117
|
+
}
|
|
118
|
+
if (BENIGN_TRACEBACK_CONTEXT.test(block.join('\n'))) {
|
|
119
|
+
i = j; // drop the whole caught-telemetry traceback block
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
// A real traceback stays in evidence line-by-line (minus noise lines
|
|
123
|
+
// already filtered above).
|
|
124
|
+
}
|
|
125
|
+
kept.push(line);
|
|
126
|
+
}
|
|
127
|
+
return kept.join('\n');
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* True when the output carries any execution signal: the browser-chrome
|
|
131
|
+
* `TEST-START` marker, the structured-log `TEST_START` marker, or the
|
|
132
|
+
* suite-specific xpcshell result-summary block.
|
|
133
|
+
*/
|
|
134
|
+
function hasExecutionSignal(output) {
|
|
135
|
+
return (TEST_START_PATTERN.test(output) ||
|
|
136
|
+
STRUCTURED_TEST_START_PATTERN.test(output) ||
|
|
137
|
+
hasXpcshellResultSummary(output));
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* True when the output embeds a COMPLETED, GREEN suite summary: an
|
|
141
|
+
* execution signal, `Unexpected results: 0` (and no non-zero unexpected
|
|
142
|
+
* count), a `SUITE_END` marker, and no real `TEST-UNEXPECTED-*` lines.
|
|
143
|
+
* Such a run finished its suite; any startup-traceback-shaped noise in the
|
|
144
|
+
* same output is by definition non-fatal, so this vetoes signature-based
|
|
145
|
+
* crash classification (field report: fully green sharded runs reported
|
|
146
|
+
* `CRASH (N attempts)` because degradation warnings matched the psutil
|
|
147
|
+
* signals). Exported for direct unit testing.
|
|
148
|
+
*/
|
|
149
|
+
export function hasCompletedGreenSummary(output) {
|
|
150
|
+
return (hasExecutionSignal(output) &&
|
|
151
|
+
SUITE_END_PATTERN.test(output) &&
|
|
152
|
+
GREEN_UNEXPECTED_SUMMARY_PATTERN.test(output) &&
|
|
153
|
+
!NONZERO_UNEXPECTED_SUMMARY_PATTERN.test(output) &&
|
|
154
|
+
realUnexpectedFailureLines(output).length === 0);
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* True when the captured output carries the suite-specific xpcshell
|
|
158
|
+
* result-summary block, which proves tests executed even though the
|
|
159
|
+
* xpcshell dispatch emits no `TEST-START` line. Used alongside
|
|
160
|
+
* `TEST_START_PATTERN` so a green single-file xpcshell run is not
|
|
161
|
+
* mis-classified as `no-tests`. Exported for direct unit testing.
|
|
162
|
+
*/
|
|
163
|
+
function hasXpcshellResultSummary(output) {
|
|
164
|
+
return XPCSHELL_RESULT_SUMMARY_PATTERN.test(output);
|
|
165
|
+
}
|
|
52
166
|
/** Unexpected-failure lines that are NOT the shutdown re-entry artifact. */
|
|
53
167
|
function realUnexpectedFailureLines(output) {
|
|
54
168
|
const matches = output.match(UNEXPECTED_LINE_PATTERN) ?? [];
|
|
@@ -59,13 +173,22 @@ function realUnexpectedFailureLines(output) {
|
|
|
59
173
|
* Returns undefined for anything that looks like a genuine test result.
|
|
60
174
|
*/
|
|
61
175
|
export function detectHarnessCrashSignature(output) {
|
|
62
|
-
const hasTestStart =
|
|
176
|
+
const hasTestStart = hasExecutionSignal(output);
|
|
63
177
|
const realFailures = realUnexpectedFailureLines(output);
|
|
64
|
-
//
|
|
65
|
-
//
|
|
66
|
-
//
|
|
67
|
-
|
|
68
|
-
|
|
178
|
+
// A completed green embedded summary vetoes signature-based crash
|
|
179
|
+
// classification outright: the suite finished, so any startup-shaped
|
|
180
|
+
// noise in the same output was non-fatal. (The post-green shutdown
|
|
181
|
+
// re-entry shape below is exempt — it is deliberately a crash verdict on
|
|
182
|
+
// an otherwise-green log, keyed on its own explicit markers.)
|
|
183
|
+
const greenSummaryVeto = hasCompletedGreenSummary(output);
|
|
184
|
+
// Startup traceback cluster (resource monitor / psutil), scanned over
|
|
185
|
+
// noise-stripped evidence so degradation warnings and caught telemetry
|
|
186
|
+
// tracebacks never count. Real test failures take precedence: a
|
|
187
|
+
// traceback printed during teardown of a genuinely failing run must not
|
|
188
|
+
// get the whole run retried.
|
|
189
|
+
const evidence = stripNonSignalNoise(output);
|
|
190
|
+
if (!greenSummaryVeto && TRACEBACK_PATTERN.test(evidence) && realFailures.length === 0) {
|
|
191
|
+
const signalLine = findLine(evidence, STARTUP_TRACEBACK_SIGNALS);
|
|
69
192
|
if (signalLine) {
|
|
70
193
|
return { reason: 'harness startup traceback (resource monitor/psutil)', line: signalLine };
|
|
71
194
|
}
|
|
@@ -74,7 +197,7 @@ export function detectHarnessCrashSignature(output) {
|
|
|
74
197
|
// the no-output timeout. A trailing "Passed: 0" summary is part of this
|
|
75
198
|
// shape and must not be read as a result.
|
|
76
199
|
if (!hasTestStart) {
|
|
77
|
-
const timeoutLine = findLine(
|
|
200
|
+
const timeoutLine = findLine(evidence, [NO_OUTPUT_TIMEOUT_PATTERN]);
|
|
78
201
|
if (timeoutLine) {
|
|
79
202
|
return { reason: 'no-output timeout before any test started', line: timeoutLine };
|
|
80
203
|
}
|
|
@@ -95,9 +218,12 @@ export function detectHarnessCrashSignature(output) {
|
|
|
95
218
|
* 1. A recognized crash signature wins regardless of exit code (the
|
|
96
219
|
* shutdown re-entry shape exits non-zero on an otherwise green run;
|
|
97
220
|
* the hang shape can even exit zero with a `Passed: 0` summary).
|
|
98
|
-
* 2. No
|
|
99
|
-
* `no-tests`, even when the exit code is zero.
|
|
100
|
-
*
|
|
221
|
+
* 2. No execution signal with explicit paths requested means no test ran —
|
|
222
|
+
* `no-tests`, even when the exit code is zero. The execution signal is
|
|
223
|
+
* a `TEST-START` line (generic `mach test` / browser-chrome dispatch)
|
|
224
|
+
* OR the suite-specific xpcshell result-summary block (the xpcshell
|
|
225
|
+
* dispatch prints no `TEST-START`). Bare `Passed:`/`Failed:` summary
|
|
226
|
+
* lines are still not trusted as evidence of execution.
|
|
101
227
|
* 3. Exit code zero with tests started is a pass; anything else is a
|
|
102
228
|
* test failure for the regular diagnosis chain.
|
|
103
229
|
*/
|
|
@@ -106,14 +232,26 @@ export function classifyHarnessRun(exitCode, output, requestedPaths) {
|
|
|
106
232
|
if (signature) {
|
|
107
233
|
return { kind: 'harness-crash', signature };
|
|
108
234
|
}
|
|
109
|
-
|
|
235
|
+
const ranTests = hasExecutionSignal(output);
|
|
236
|
+
if (!ranTests && requestedPaths.length > 0) {
|
|
110
237
|
return { kind: 'no-tests' };
|
|
111
238
|
}
|
|
112
|
-
|
|
239
|
+
if (exitCode === 0) {
|
|
240
|
+
return { kind: 'tests-ran-ok' };
|
|
241
|
+
}
|
|
242
|
+
// Exit codes follow the corrected verdict: a run whose embedded summary
|
|
243
|
+
// completed green is a pass even when the wrapper exit code went
|
|
244
|
+
// non-zero on harness noise (field report: a fully green --no-shard run
|
|
245
|
+
// exited 1). Real failures always carry a non-zero unexpected count or
|
|
246
|
+
// TEST-UNEXPECTED lines, both of which fail the green-summary check.
|
|
247
|
+
if (hasCompletedGreenSummary(output)) {
|
|
248
|
+
return { kind: 'tests-ran-ok', greenSummaryOverride: true };
|
|
249
|
+
}
|
|
250
|
+
return { kind: 'test-failures' };
|
|
113
251
|
}
|
|
114
252
|
/** Builds the operator-facing failure message after retries are exhausted. */
|
|
115
|
-
export function buildHarnessCrashMessage(signature, attempts) {
|
|
116
|
-
return (
|
|
253
|
+
export function buildHarnessCrashMessage(signature, attempts, commandLabel = 'mach test') {
|
|
254
|
+
return (`${commandLabel} crashed in the harness itself (not in your tests) on all ${attempts} attempt(s).\n\n` +
|
|
117
255
|
`Detected shape: ${signature.reason}\n` +
|
|
118
256
|
`Evidence line: ${signature.line}\n\n` +
|
|
119
257
|
'This failure mode is environmental (mozlog resource monitor / psutil on macOS, focus-stall ' +
|
|
@@ -38,6 +38,15 @@ export declare function stripBlockCommentsInLines(lines: string[]): string[];
|
|
|
38
38
|
* can splice into the original `lines` array at the returned index.
|
|
39
39
|
*/
|
|
40
40
|
export declare function findDarkRootInsertionIndex(lines: string[]): number | null;
|
|
41
|
+
/**
|
|
42
|
+
* Depth-counts braces from `startLine` (whose lines must already have
|
|
43
|
+
* block comments stripped), returning the index of the line on which the
|
|
44
|
+
* block opened there returns to its entry depth — i.e. the line carrying
|
|
45
|
+
* the block's closing `}` — or -1 when the block never closes. The first
|
|
46
|
+
* `{` encountered sets the entry depth, so the scan may start on the
|
|
47
|
+
* selector/at-rule line itself rather than on the opener.
|
|
48
|
+
*/
|
|
49
|
+
export declare function findBlockCloseIndex(stripped: string[], startLine: number): number;
|
|
41
50
|
/**
|
|
42
51
|
* Finds the closing `}` of the outermost
|
|
43
52
|
* `@media (prefers-color-scheme: dark)` block. Used as the fallback
|
|
@@ -122,7 +122,7 @@ export function findDarkRootInsertionIndex(lines) {
|
|
|
122
122
|
* `{` encountered sets the entry depth, so the scan may start on the
|
|
123
123
|
* selector/at-rule line itself rather than on the opener.
|
|
124
124
|
*/
|
|
125
|
-
function findBlockCloseIndex(stripped, startLine) {
|
|
125
|
+
export function findBlockCloseIndex(stripped, startLine) {
|
|
126
126
|
let depth = 0;
|
|
127
127
|
let entryDepth = 0;
|
|
128
128
|
let enteredBlock = false;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Documentation-table updates for `fireforge token add`. Extracted from
|
|
3
|
+
* `token-manager.ts` so the CSS-mutation path and the Markdown-table path
|
|
4
|
+
* each stay within the per-file line budget. Consumed only by token-manager.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Minimal token shape the docs updater needs. Declared locally (rather than
|
|
8
|
+
* importing `AddTokenOptions`) so this module has no edge back to
|
|
9
|
+
* `token-manager.ts` — `AddTokenOptions` is structurally compatible.
|
|
10
|
+
*/
|
|
11
|
+
export interface TokenDocInput {
|
|
12
|
+
tokenName: string;
|
|
13
|
+
value: string;
|
|
14
|
+
category: string;
|
|
15
|
+
mode: string;
|
|
16
|
+
description?: string | undefined;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Adds a token row to the main token table, the unmapped table (for
|
|
20
|
+
* literal values), and bumps the mode count table. Each sub-update runs
|
|
21
|
+
* against a freshly parsed view of the document so that splice indices
|
|
22
|
+
* stay valid as rewrites are layered.
|
|
23
|
+
*
|
|
24
|
+
* @param annotation - The mode annotation string the caller already computed
|
|
25
|
+
* (kept here as a parameter so this module needs no dependency on
|
|
26
|
+
* token-manager's `getModeAnnotation`).
|
|
27
|
+
*/
|
|
28
|
+
export declare function addTokenToDocs(engineDir: string, options: TokenDocInput, annotation: string): Promise<{
|
|
29
|
+
docsAdded: boolean;
|
|
30
|
+
unmappedAdded: boolean;
|
|
31
|
+
countUpdated: boolean;
|
|
32
|
+
}>;
|