@hominis/fireforge 0.33.0 → 0.34.1
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 +16 -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/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/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 +70 -16
- package/dist/src/core/mach-resource-shim.js +310 -52
- package/dist/src/core/mach.d.ts +51 -10
- package/dist/src/core/mach.js +76 -32
- package/dist/src/core/manifest-helpers.d.ts +13 -0
- package/dist/src/core/manifest-helpers.js +1 -1
- 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/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 +26 -1
- package/dist/src/core/test-harness-crash.js +149 -11
- 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 +12 -0
- package/package.json +3 -3
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* Ties on the lower-cased key fall back to byte order so the comparison is
|
|
11
11
|
* total and stable.
|
|
12
12
|
*/
|
|
13
|
-
function mozbuildSortCompare(a, b) {
|
|
13
|
+
export function mozbuildSortCompare(a, b) {
|
|
14
14
|
const la = a.toLowerCase();
|
|
15
15
|
const lb = b.toLowerCase();
|
|
16
16
|
if (la < lb)
|
|
@@ -10,8 +10,14 @@ export interface PatternRule {
|
|
|
10
10
|
/** Extract arguments from the regex match */
|
|
11
11
|
extractArgs: (match: RegExpMatchArray) => string[];
|
|
12
12
|
}
|
|
13
|
-
/**
|
|
14
|
-
|
|
13
|
+
/**
|
|
14
|
+
* Returns manifest registration rules for the supported engine file
|
|
15
|
+
* patterns. `createManifest` (from `register --create-manifest`) lets the
|
|
16
|
+
* module and xpcshell-test rules scaffold a missing manifest (directory
|
|
17
|
+
* moz.build / xpcshell.toml) and wire the parent chain instead of failing
|
|
18
|
+
* with "Manifest not found".
|
|
19
|
+
*/
|
|
20
|
+
export declare function getRules(binaryName: string, createManifest?: boolean): PatternRule[];
|
|
15
21
|
/**
|
|
16
22
|
* Checks if a file path matches any known registrable pattern.
|
|
17
23
|
* @param filePath - Path relative to engine/
|
|
@@ -34,6 +40,10 @@ export declare function isFileRegistered(root: string, filePath: string): Promis
|
|
|
34
40
|
* @param filePath - Path relative to engine/
|
|
35
41
|
* @param dryRun - If true, return what would be done without writing
|
|
36
42
|
* @param after - Optional substring to place entry after (instead of alphabetical)
|
|
43
|
+
* @param options - `createManifest` scaffolds a missing manifest (directory
|
|
44
|
+
* moz.build / xpcshell.toml) and wires the parent chain instead of failing
|
|
37
45
|
* @returns Registration result
|
|
38
46
|
*/
|
|
39
|
-
export declare function registerFile(root: string, filePath: string, dryRun?: boolean, after?: string
|
|
47
|
+
export declare function registerFile(root: string, filePath: string, dryRun?: boolean, after?: string, options?: {
|
|
48
|
+
createManifest?: boolean;
|
|
49
|
+
}): Promise<RegisterResult>;
|
|
@@ -5,8 +5,15 @@ import { pathExists, readText } from '../utils/fs.js';
|
|
|
5
5
|
import { escapeRegex } from '../utils/regex.js';
|
|
6
6
|
import { getProjectPaths, loadConfig } from './config.js';
|
|
7
7
|
import { registerBrowserContent, registerFireForgeModule, registerSharedCSS, registerTestManifest, registerToolkitWidget, } from './manifest-register.js';
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
import { isXpcshellTestRegistered, registerXpcshellTest } from './register-xpcshell-test.js';
|
|
9
|
+
/**
|
|
10
|
+
* Returns manifest registration rules for the supported engine file
|
|
11
|
+
* patterns. `createManifest` (from `register --create-manifest`) lets the
|
|
12
|
+
* module and xpcshell-test rules scaffold a missing manifest (directory
|
|
13
|
+
* moz.build / xpcshell.toml) and wire the parent chain instead of failing
|
|
14
|
+
* with "Manifest not found".
|
|
15
|
+
*/
|
|
16
|
+
export function getRules(binaryName, createManifest = false) {
|
|
10
17
|
const moduleDir = `browser/modules/${binaryName}`;
|
|
11
18
|
return [
|
|
12
19
|
{
|
|
@@ -39,7 +46,10 @@ export function getRules(binaryName) {
|
|
|
39
46
|
extractArgs: (m) => [m[1] ?? ''],
|
|
40
47
|
},
|
|
41
48
|
{
|
|
42
|
-
|
|
49
|
+
// Fork-owned browser.toml manifests at ARBITRARY depth under
|
|
50
|
+
// browser/base/content/test/ (0.34.0 field report: only one level
|
|
51
|
+
// was supported, rejecting nested browser-chrome manifests).
|
|
52
|
+
pattern: /^browser\/base\/content\/test\/((?:[^/]+\/)*[^/]+)\/browser\.toml$/,
|
|
43
53
|
isRegistered: (_engineDir, testDir) => isTestManifestRegistered(_engineDir, testDir),
|
|
44
54
|
register: (_engineDir, _after, dryRun, testDir) => registerTestManifest(_engineDir, testDir, dryRun),
|
|
45
55
|
extractArgs: (m) => [m[1] ?? ''],
|
|
@@ -47,7 +57,7 @@ export function getRules(binaryName) {
|
|
|
47
57
|
{
|
|
48
58
|
pattern: new RegExp(`^browser/modules/${escapeRegex(binaryName)}/(.+\\.sys\\.mjs)$`),
|
|
49
59
|
isRegistered: (_engineDir, fileName) => isFireForgeModuleRegistered(_engineDir, fileName, moduleDir),
|
|
50
|
-
register: (_engineDir, _after, dryRun, fileName) => registerFireForgeModule(_engineDir, fileName, moduleDir, dryRun),
|
|
60
|
+
register: (_engineDir, _after, dryRun, fileName) => registerFireForgeModule(_engineDir, fileName, moduleDir, dryRun, createManifest),
|
|
51
61
|
extractArgs: (m) => [m[1] ?? ''],
|
|
52
62
|
},
|
|
53
63
|
{
|
|
@@ -56,6 +66,20 @@ export function getRules(binaryName) {
|
|
|
56
66
|
register: (_engineDir, _after, dryRun, tagName, fileName) => registerToolkitWidget(_engineDir, tagName, fileName, dryRun),
|
|
57
67
|
extractArgs: (m) => [m[1] ?? '', m[2] ?? ''],
|
|
58
68
|
},
|
|
69
|
+
{
|
|
70
|
+
// xpcshell test files (0.34.0 field report: rejected as "Unknown
|
|
71
|
+
// file pattern"). Any `test_*.js` outside browser/base/content/test/
|
|
72
|
+
// (which stays browser-chrome territory with its browser.toml
|
|
73
|
+
// guidance) registers into the directory's xpcshell.toml; with
|
|
74
|
+
// --create-manifest a missing manifest is scaffolded and wired via
|
|
75
|
+
// XPCSHELL_TESTS_MANIFESTS.
|
|
76
|
+
pattern: /^(?!browser\/base\/content\/test\/)(.+)\/(test_[^/]+\.js)$/,
|
|
77
|
+
isRegistered: (engineDir, dirRel, fileName) => isXpcshellTestRegistered(engineDir, dirRel, fileName),
|
|
78
|
+
register: (engineDir, _after, dryRun, dirRel, fileName) => registerXpcshellTest(engineDir, dirRel, fileName, dryRun, createManifest),
|
|
79
|
+
// Both groups are guaranteed by the pattern; slice avoids dead
|
|
80
|
+
// fallback branches.
|
|
81
|
+
extractArgs: (m) => m.slice(1, 3),
|
|
82
|
+
},
|
|
59
83
|
];
|
|
60
84
|
}
|
|
61
85
|
async function isSharedCSSRegistered(engineDir, fileName) {
|
|
@@ -149,7 +173,8 @@ function getUnregistrableAdvice(filePath) {
|
|
|
149
173
|
return (`xpcshell.toml manifests are not auto-registered by "fireforge register". ` +
|
|
150
174
|
`Add "XPCSHELL_TESTS_MANIFESTS += ['${basename(filePath)}']" to the ` +
|
|
151
175
|
`moz.build that covers ${filePath.replace(/\/xpcshell\.toml$/, '/')}, ` +
|
|
152
|
-
'or
|
|
176
|
+
'or register one of its test files with "fireforge register <path>/test_*.js --create-manifest", ' +
|
|
177
|
+
'which scaffolds the manifest and wires XPCSHELL_TESTS_MANIFESTS automatically. ' +
|
|
153
178
|
'Browser-chrome tests (browser.toml) are auto-registered via "fireforge register <path>/browser.toml".');
|
|
154
179
|
}
|
|
155
180
|
const testMatch = filePath.match(/^browser\/base\/content\/test\/([^/]+)\/(?!browser\.toml$).+$/);
|
|
@@ -183,12 +208,17 @@ export async function isFileRegistered(root, filePath) {
|
|
|
183
208
|
if (advice) {
|
|
184
209
|
throw new InvalidArgumentError(advice, 'path');
|
|
185
210
|
}
|
|
186
|
-
throw new InvalidArgumentError(
|
|
211
|
+
throw new InvalidArgumentError(buildUnknownPatternMessage(normalizedPath, config.binaryName), 'path');
|
|
212
|
+
}
|
|
213
|
+
/** Shared "Unknown file pattern" message naming every supported pattern. */
|
|
214
|
+
function buildUnknownPatternMessage(normalizedPath, binaryName) {
|
|
215
|
+
return (`Unknown file pattern: "${normalizedPath}". Supported patterns:\n` +
|
|
187
216
|
' browser/themes/shared/*.css\n' +
|
|
188
217
|
' browser/base/content/*.{js,mjs,xhtml,css}\n' +
|
|
189
|
-
' browser/base/content/test
|
|
190
|
-
` browser/modules/${
|
|
191
|
-
' toolkit/content/widgets/*/*.{mjs,css}'
|
|
218
|
+
' browser/base/content/test/**/browser.toml\n' +
|
|
219
|
+
` browser/modules/${binaryName}/*.sys.mjs\n` +
|
|
220
|
+
' toolkit/content/widgets/*/*.{mjs,css}\n' +
|
|
221
|
+
' **/test_*.js (xpcshell test files; registers into the nearest xpcshell.toml)');
|
|
192
222
|
}
|
|
193
223
|
/**
|
|
194
224
|
* Registers a file in the appropriate build manifest.
|
|
@@ -197,12 +227,14 @@ export async function isFileRegistered(root, filePath) {
|
|
|
197
227
|
* @param filePath - Path relative to engine/
|
|
198
228
|
* @param dryRun - If true, return what would be done without writing
|
|
199
229
|
* @param after - Optional substring to place entry after (instead of alphabetical)
|
|
230
|
+
* @param options - `createManifest` scaffolds a missing manifest (directory
|
|
231
|
+
* moz.build / xpcshell.toml) and wires the parent chain instead of failing
|
|
200
232
|
* @returns Registration result
|
|
201
233
|
*/
|
|
202
|
-
export async function registerFile(root, filePath, dryRun = false, after) {
|
|
234
|
+
export async function registerFile(root, filePath, dryRun = false, after, options = {}) {
|
|
203
235
|
const { engine: engineDir } = getProjectPaths(root);
|
|
204
236
|
const config = await loadConfig(root);
|
|
205
|
-
const rules = getRules(config.binaryName);
|
|
237
|
+
const rules = getRules(config.binaryName, options.createManifest === true);
|
|
206
238
|
// Normalize path separators
|
|
207
239
|
const normalizedPath = filePath.replace(/\\/g, '/');
|
|
208
240
|
for (const rule of rules) {
|
|
@@ -216,11 +248,6 @@ export async function registerFile(root, filePath, dryRun = false, after) {
|
|
|
216
248
|
if (advice) {
|
|
217
249
|
throw new InvalidArgumentError(advice, 'path');
|
|
218
250
|
}
|
|
219
|
-
throw new InvalidArgumentError(
|
|
220
|
-
' browser/themes/shared/*.css\n' +
|
|
221
|
-
' browser/base/content/*.{js,mjs,xhtml,css}\n' +
|
|
222
|
-
' browser/base/content/test/*/browser.toml\n' +
|
|
223
|
-
` browser/modules/${config.binaryName}/*.sys.mjs\n` +
|
|
224
|
-
' toolkit/content/widgets/*/*.{mjs,css}', 'path');
|
|
251
|
+
throw new InvalidArgumentError(buildUnknownPatternMessage(normalizedPath, config.binaryName), 'path');
|
|
225
252
|
}
|
|
226
253
|
//# sourceMappingURL=manifest-rules.js.map
|
|
@@ -20,6 +20,16 @@ export declare function getNextPatchNumber(patchesDir: string): Promise<string>;
|
|
|
20
20
|
* to a slug `export` could never reach.
|
|
21
21
|
*/
|
|
22
22
|
export declare function sanitizeName(name: string): string;
|
|
23
|
+
/**
|
|
24
|
+
* Strips a leading `NNN-<category>-` prefix from a sanitized name slug.
|
|
25
|
+
* Operators frequently pass the DESIRED FILENAME stem to `--name`
|
|
26
|
+
* (`--name 203-ui-foo --category ui`), which 0.33.0 double-prefixed into
|
|
27
|
+
* `203-ui-203-ui-foo.patch`. The filename builders prepend the order and
|
|
28
|
+
* category themselves, so a matching prefix in the name is always
|
|
29
|
+
* redundant. Applied repeatedly so a twice-prefixed slug (from a previous
|
|
30
|
+
* double-prefix incident) also collapses. Exported for direct testing.
|
|
31
|
+
*/
|
|
32
|
+
export declare function stripRedundantCategoryPrefix(sanitizedName: string, category: string): string;
|
|
23
33
|
/**
|
|
24
34
|
* Generates the next patch filename with category.
|
|
25
35
|
* @param patchesDir - Path to the patches directory
|
|
@@ -46,6 +46,27 @@ export function sanitizeName(name) {
|
|
|
46
46
|
.replace(/^-+|-+$/g, '')
|
|
47
47
|
.slice(0, 50);
|
|
48
48
|
}
|
|
49
|
+
/**
|
|
50
|
+
* Strips a leading `NNN-<category>-` prefix from a sanitized name slug.
|
|
51
|
+
* Operators frequently pass the DESIRED FILENAME stem to `--name`
|
|
52
|
+
* (`--name 203-ui-foo --category ui`), which 0.33.0 double-prefixed into
|
|
53
|
+
* `203-ui-203-ui-foo.patch`. The filename builders prepend the order and
|
|
54
|
+
* category themselves, so a matching prefix in the name is always
|
|
55
|
+
* redundant. Applied repeatedly so a twice-prefixed slug (from a previous
|
|
56
|
+
* double-prefix incident) also collapses. Exported for direct testing.
|
|
57
|
+
*/
|
|
58
|
+
export function stripRedundantCategoryPrefix(sanitizedName, category) {
|
|
59
|
+
const prefix = new RegExp(`^\\d+-${category}-`);
|
|
60
|
+
let stripped = sanitizedName;
|
|
61
|
+
while (prefix.test(stripped)) {
|
|
62
|
+
stripped = stripped.replace(prefix, '');
|
|
63
|
+
}
|
|
64
|
+
return stripped.length > 0 ? stripped : sanitizedName;
|
|
65
|
+
}
|
|
66
|
+
/** Sanitizes a patch name and drops a redundant `NNN-<category>-` prefix. */
|
|
67
|
+
function patchNameSlug(name, category) {
|
|
68
|
+
return stripRedundantCategoryPrefix(sanitizeName(name), category);
|
|
69
|
+
}
|
|
49
70
|
/**
|
|
50
71
|
* Generates the next patch filename with category.
|
|
51
72
|
* @param patchesDir - Path to the patches directory
|
|
@@ -55,7 +76,7 @@ export function sanitizeName(name) {
|
|
|
55
76
|
*/
|
|
56
77
|
export async function getNextPatchFilename(patchesDir, category, name) {
|
|
57
78
|
const patchNumber = await getNextPatchNumber(patchesDir);
|
|
58
|
-
const sanitizedName =
|
|
79
|
+
const sanitizedName = patchNameSlug(name, category);
|
|
59
80
|
return `${patchNumber}-${category}-${sanitizedName}.patch`;
|
|
60
81
|
}
|
|
61
82
|
/**
|
|
@@ -253,7 +274,7 @@ async function computeExportPlanUnderLock(input) {
|
|
|
253
274
|
? allocatePolicyOrder(input.config, manifestBefore?.patches ?? [], input.category)
|
|
254
275
|
: null;
|
|
255
276
|
const patchFilename = policyOrder !== null
|
|
256
|
-
? `${String(policyOrder).padStart(3, '0')}-${input.category}-${
|
|
277
|
+
? `${String(policyOrder).padStart(3, '0')}-${input.category}-${patchNameSlug(input.name, input.category)}.patch`
|
|
257
278
|
: await getNextPatchFilename(input.patchesDir, input.category, input.name);
|
|
258
279
|
const patchPath = join(input.patchesDir, patchFilename);
|
|
259
280
|
const metadata = {
|
|
@@ -8,4 +8,4 @@ import type { RegisterResult } from './register-result.js';
|
|
|
8
8
|
* Entry format:
|
|
9
9
|
* "{name}.sys.mjs",
|
|
10
10
|
*/
|
|
11
|
-
export declare function registerFireForgeModule(engineDir: string, fileName: string, moduleDir: string, dryRun?: boolean): Promise<RegisterResult>;
|
|
11
|
+
export declare function registerFireForgeModule(engineDir: string, fileName: string, moduleDir: string, dryRun?: boolean, createManifest?: boolean): Promise<RegisterResult>;
|
|
@@ -8,6 +8,7 @@ import { pathExists, readText, writeText } from '../utils/fs.js';
|
|
|
8
8
|
import { findAlphabeticalMozBuildPosition, findAlphabeticalPosition } from './manifest-helpers.js';
|
|
9
9
|
import { tokenizeMozBuildList } from './manifest-tokenizers.js';
|
|
10
10
|
import { withParserFallback } from './parser-fallback.js';
|
|
11
|
+
import { scaffoldModuleMozBuild } from './register-scaffold.js';
|
|
11
12
|
/**
|
|
12
13
|
* Tokenizer-based implementation for module registration.
|
|
13
14
|
*/
|
|
@@ -55,11 +56,23 @@ function legacyRegisterFireForgeModule(content, fileName, entry, moduleDir) {
|
|
|
55
56
|
* Entry format:
|
|
56
57
|
* "{name}.sys.mjs",
|
|
57
58
|
*/
|
|
58
|
-
export async function registerFireForgeModule(engineDir, fileName, moduleDir, dryRun = false) {
|
|
59
|
+
export async function registerFireForgeModule(engineDir, fileName, moduleDir, dryRun = false, createManifest = false) {
|
|
59
60
|
const manifest = `${moduleDir}/moz.build`;
|
|
60
61
|
const manifestPath = join(engineDir, manifest);
|
|
61
62
|
if (!(await pathExists(manifestPath))) {
|
|
62
|
-
|
|
63
|
+
if (createManifest) {
|
|
64
|
+
// 0.34.0 field report: registering a module under a directory with
|
|
65
|
+
// no moz.build failed with "Manifest not found". Scaffold the
|
|
66
|
+
// directory manifest and wire the parent DIRS chain.
|
|
67
|
+
const scaffoldActions = await scaffoldModuleMozBuild(engineDir, moduleDir, fileName, dryRun);
|
|
68
|
+
return {
|
|
69
|
+
manifest,
|
|
70
|
+
entry: ` "${fileName}",`,
|
|
71
|
+
skipped: false,
|
|
72
|
+
scaffoldActions,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
throw new GeneralError(`Manifest not found: ${manifest}. Pass --create-manifest to scaffold it and wire the parent DIRS entry.`);
|
|
63
76
|
}
|
|
64
77
|
const entry = ` "${fileName}",`.replace(/\\/g, '/');
|
|
65
78
|
const content = await readText(manifestPath);
|
|
@@ -18,4 +18,13 @@ export interface RegisterResult {
|
|
|
18
18
|
skipped: boolean;
|
|
19
19
|
/** Whether --after target was not found and fell back to alphabetical */
|
|
20
20
|
afterFallback?: boolean | undefined;
|
|
21
|
+
/**
|
|
22
|
+
* Manifests created or wired by `--create-manifest` scaffolding
|
|
23
|
+
* (directory moz.build, parent DIRS chain, XPCSHELL_TESTS_MANIFESTS),
|
|
24
|
+
* for user display.
|
|
25
|
+
*/
|
|
26
|
+
scaffoldActions?: {
|
|
27
|
+
manifest: string;
|
|
28
|
+
change: string;
|
|
29
|
+
}[] | undefined;
|
|
21
30
|
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Manifest scaffolding for `fireforge register --create-manifest`
|
|
3
|
+
* (0.34.0 field report): registering a module under a directory with no
|
|
4
|
+
* moz.build used to fail with "Manifest not found" because `register`
|
|
5
|
+
* only inserts into EXISTING manifests. This module owns the two
|
|
6
|
+
* scaffolding primitives:
|
|
7
|
+
*
|
|
8
|
+
* - creating a directory `moz.build` (license header + the requested
|
|
9
|
+
* list directive) and wiring the parent chain's `DIRS` entries up to
|
|
10
|
+
* the nearest existing moz.build;
|
|
11
|
+
* - wiring an `XPCSHELL_TESTS_MANIFESTS` entry for a (possibly freshly
|
|
12
|
+
* created) `xpcshell.toml` into the nearest existing moz.build.
|
|
13
|
+
*
|
|
14
|
+
* All writers reuse the tokenizer + alphabetical-position helpers the
|
|
15
|
+
* existing register writers use, so inserted entries respect mozbuild's
|
|
16
|
+
* case-insensitive StrictOrderingOnAppendList rule.
|
|
17
|
+
*/
|
|
18
|
+
/** One manifest mutation performed (or planned, on dry-run) by a scaffold. */
|
|
19
|
+
export interface ScaffoldAction {
|
|
20
|
+
/** Engine-relative manifest path that was created or modified. */
|
|
21
|
+
manifest: string;
|
|
22
|
+
/** Human-readable description of the change. */
|
|
23
|
+
change: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Inserts `value` into the named moz.build list directive, creating the
|
|
27
|
+
* `NAME += [ ... ]` block at the end of the file when the directive does
|
|
28
|
+
* not exist yet. Returns the updated content, or null when the value is
|
|
29
|
+
* already present.
|
|
30
|
+
*/
|
|
31
|
+
export declare function upsertMozBuildListEntry(content: string, directive: string, value: string): string | null;
|
|
32
|
+
/**
|
|
33
|
+
* Ensures every parent directory between `childRelDir` and the nearest
|
|
34
|
+
* existing moz.build carries a `DIRS` entry pointing at the next segment
|
|
35
|
+
* down, creating intermediate moz.build files as needed. Walks upward
|
|
36
|
+
* from the child's parent; throws when no moz.build exists anywhere up
|
|
37
|
+
* to the engine root (an engine checkout always has one at the root, so
|
|
38
|
+
* this indicates a bogus path).
|
|
39
|
+
*/
|
|
40
|
+
export declare function ensureParentDirsWiring(engineDir: string, childRelDir: string, dryRun: boolean): Promise<ScaffoldAction[]>;
|
|
41
|
+
/**
|
|
42
|
+
* Creates `<moduleDir>/moz.build` with an `EXTRA_JS_MODULES.<namespace>`
|
|
43
|
+
* list containing `fileName`, and wires the parent DIRS chain. The
|
|
44
|
+
* namespace is the module directory's basename (the fork's binary name
|
|
45
|
+
* for `browser/modules/<binaryName>/`), matching Firefox's convention of
|
|
46
|
+
* mapping `EXTRA_JS_MODULES.<ns>` into `resource:///modules/<ns>/`.
|
|
47
|
+
*/
|
|
48
|
+
export declare function scaffoldModuleMozBuild(engineDir: string, moduleDir: string, fileName: string, dryRun: boolean): Promise<ScaffoldAction[]>;
|
|
49
|
+
/**
|
|
50
|
+
* Wires an `XPCSHELL_TESTS_MANIFESTS` entry for `manifestRelPath` (an
|
|
51
|
+
* engine-relative `.../xpcshell.toml`) into the nearest EXISTING
|
|
52
|
+
* moz.build above it. Returns the performed actions (empty when the
|
|
53
|
+
* entry already exists).
|
|
54
|
+
*/
|
|
55
|
+
export declare function ensureXpcshellManifestWiring(engineDir: string, manifestRelPath: string, dryRun: boolean): Promise<ScaffoldAction[]>;
|
|
@@ -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
|