@hominis/fireforge 0.15.9 → 0.16.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 +73 -0
- package/README.md +2 -0
- package/dist/src/cli.d.ts +4 -1
- package/dist/src/cli.js +6 -3
- package/dist/src/commands/download.js +9 -0
- package/dist/src/commands/export-all.js +46 -0
- package/dist/src/commands/export.js +10 -1
- package/dist/src/commands/furnace/diff.js +22 -2
- package/dist/src/commands/furnace/override.js +35 -12
- package/dist/src/commands/furnace/preview.js +33 -1
- package/dist/src/commands/furnace/rename.js +14 -3
- package/dist/src/commands/lint.js +10 -1
- package/dist/src/commands/package.js +16 -5
- package/dist/src/commands/re-export.js +25 -0
- package/dist/src/commands/register.js +2 -18
- package/dist/src/commands/run.js +23 -2
- package/dist/src/commands/status.js +25 -3
- package/dist/src/commands/test.js +6 -24
- package/dist/src/commands/token.js +14 -1
- package/dist/src/commands/watch.js +14 -2
- package/dist/src/core/branding.d.ts +23 -0
- package/dist/src/core/branding.js +39 -0
- package/dist/src/core/browser-wire.js +68 -23
- package/dist/src/core/mach-build-artifacts.d.ts +41 -0
- package/dist/src/core/mach-build-artifacts.js +70 -0
- package/dist/src/core/mach-error-hints.js +15 -0
- package/dist/src/core/mach-mozconfig.d.ts +25 -0
- package/dist/src/core/mach-mozconfig.js +66 -0
- package/dist/src/core/mach.d.ts +12 -1
- package/dist/src/core/mach.js +14 -1
- package/dist/src/core/manifest-rules.js +22 -1
- package/dist/src/utils/fs.d.ts +12 -0
- package/dist/src/utils/fs.js +12 -0
- package/dist/src/utils/paths.d.ts +19 -0
- package/dist/src/utils/paths.js +33 -0
- package/package.json +1 -1
|
@@ -3,6 +3,7 @@ import { join } from 'node:path';
|
|
|
3
3
|
import { MozconfigError } from '../errors/build.js';
|
|
4
4
|
import { pathExists, readText, writeText } from '../utils/fs.js';
|
|
5
5
|
import { getPlatform } from '../utils/platform.js';
|
|
6
|
+
import { BrandingMozconfigMismatchError } from './branding.js';
|
|
6
7
|
/**
|
|
7
8
|
* Replaces template variables in a string.
|
|
8
9
|
* @param content - Content with ${variable} placeholders
|
|
@@ -16,6 +17,66 @@ function replaceVariables(content, variables) {
|
|
|
16
17
|
.replace(/\$\{appId\}/g, variables.appId)
|
|
17
18
|
.replace(/\$\{binaryName\}/g, variables.binaryName);
|
|
18
19
|
}
|
|
20
|
+
/**
|
|
21
|
+
* Matches an `--with-branding=<path>` directive anywhere in a rendered
|
|
22
|
+
* mozconfig. The directive form is the one mach reads; an optional
|
|
23
|
+
* `ac_add_options` prefix is the on-disk convention. `m` flag anchors the
|
|
24
|
+
* search per-line so a multi-line mozconfig with older directives earlier
|
|
25
|
+
* in the file doesn't confuse the extractor. We pick the LAST match
|
|
26
|
+
* because mach itself takes the last-write-wins semantics of shell
|
|
27
|
+
* configuration for overlapping `ac_add_options` calls.
|
|
28
|
+
*/
|
|
29
|
+
const WITH_BRANDING_PATTERN = /^\s*(?:ac_add_options\s+)?--with-branding\s*=\s*(\S+)/gm;
|
|
30
|
+
/**
|
|
31
|
+
* Extracts the `--with-branding=<path>` value from a rendered mozconfig
|
|
32
|
+
* body. Returns `undefined` when no directive is present — callers treat
|
|
33
|
+
* that as "mozconfig is missing branding", which is itself an actionable
|
|
34
|
+
* configuration error.
|
|
35
|
+
*
|
|
36
|
+
* Exported for testing.
|
|
37
|
+
*/
|
|
38
|
+
export function extractWithBrandingPath(mozconfigContent) {
|
|
39
|
+
const matches = [...mozconfigContent.matchAll(WITH_BRANDING_PATTERN)];
|
|
40
|
+
const last = matches.at(-1);
|
|
41
|
+
return last?.[1];
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Preflights the just-written mozconfig against the branding tree FireForge
|
|
45
|
+
* set up. A drift between the two is silent-corruption territory — the
|
|
46
|
+
* build runs, `mach configure` reads the stale directory name out of
|
|
47
|
+
* mozconfig, and then the recursive make backend errors out with a "path
|
|
48
|
+
* does not exist" message that names the branding dir the mozconfig
|
|
49
|
+
* referenced. By parsing the mozconfig here and comparing to
|
|
50
|
+
* `config.binaryName`, we turn that into a single-line actionable error
|
|
51
|
+
* before `mach` runs.
|
|
52
|
+
*
|
|
53
|
+
* @param engineDir Path to the engine directory (the branding tree lives here)
|
|
54
|
+
* @param mozconfigPath Path to the mozconfig just written
|
|
55
|
+
* @param config FireForge configuration (reads `binaryName`)
|
|
56
|
+
* @throws BrandingMozconfigMismatchError on drift or missing directive
|
|
57
|
+
*/
|
|
58
|
+
export async function assertBrandingMozconfigAgreement(engineDir, mozconfigPath, config) {
|
|
59
|
+
const mozconfigContent = await readText(mozconfigPath);
|
|
60
|
+
const found = extractWithBrandingPath(mozconfigContent);
|
|
61
|
+
const expected = `browser/branding/${config.binaryName}`;
|
|
62
|
+
if (!found) {
|
|
63
|
+
throw new BrandingMozconfigMismatchError(expected, '(no --with-branding directive)', 'mozconfig-missing-branding');
|
|
64
|
+
}
|
|
65
|
+
// Normalise both sides to forward slashes before compare — Windows-edited
|
|
66
|
+
// configs can carry backslash path separators that the build would treat
|
|
67
|
+
// as literal characters in a repo-relative path.
|
|
68
|
+
const normalizedFound = found.replace(/\\/g, '/');
|
|
69
|
+
if (normalizedFound !== expected) {
|
|
70
|
+
throw new BrandingMozconfigMismatchError(expected, found, 'name-mismatch');
|
|
71
|
+
}
|
|
72
|
+
// Last line of defence: even with matching names, a missing branding tree
|
|
73
|
+
// means the scaffold step hasn't run. Preflight here so the operator
|
|
74
|
+
// doesn't pay for a configure-through-build cycle to discover it.
|
|
75
|
+
const brandingMozBuild = join(engineDir, expected, 'moz.build');
|
|
76
|
+
if (!(await pathExists(brandingMozBuild))) {
|
|
77
|
+
throw new BrandingMozconfigMismatchError(expected, found, 'branding-dir-missing');
|
|
78
|
+
}
|
|
79
|
+
}
|
|
19
80
|
/**
|
|
20
81
|
* Generates a mozconfig file from templates.
|
|
21
82
|
* @param configsDir - Path to the configs directory
|
|
@@ -46,5 +107,10 @@ export async function generateMozconfig(configsDir, engineDir, config) {
|
|
|
46
107
|
const platformContent = await readText(platformPath);
|
|
47
108
|
content += `# Platform configuration (${platform})\n${replaceVariables(platformContent, variables)}`;
|
|
48
109
|
await writeText(outputPath, content);
|
|
110
|
+
// Preflight: the mozconfig we just wrote must reference the branding
|
|
111
|
+
// directory FireForge actually set up. Catching the drift here (after the
|
|
112
|
+
// write, before anything consumes mozconfig) keeps `generateMozconfig`
|
|
113
|
+
// the single source of truth for both the render and the sanity-check.
|
|
114
|
+
await assertBrandingMozconfigAgreement(engineDir, outputPath, config);
|
|
49
115
|
}
|
|
50
116
|
//# sourceMappingURL=mach-mozconfig.js.map
|
package/dist/src/core/mach.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type SmokeLineCallback, type SmokeRunResult } from '../utils/process.js';
|
|
2
|
-
export { attemptMozinfoRewrite, type BuildArtifactCheck, buildArtifactMismatchMessage, hasBuildArtifacts, type MozinfoRewriteResult, } from './mach-build-artifacts.js';
|
|
2
|
+
export { attemptMozinfoRewrite, type BuildArtifactCheck, buildArtifactMismatchMessage, hasBuildArtifacts, hasRunnableBundle, type MozinfoRewriteResult, type RunnableBundleCheck, } from './mach-build-artifacts.js';
|
|
3
3
|
export { generateMozconfig, type MozconfigVariables } from './mach-mozconfig.js';
|
|
4
4
|
export { ensurePython, resetResolvedPython } from './mach-python.js';
|
|
5
5
|
/**
|
|
@@ -111,6 +111,17 @@ export declare function runMachSmoke(args: string[], engineDir: string, options:
|
|
|
111
111
|
* @returns Exit code
|
|
112
112
|
*/
|
|
113
113
|
export declare function machPackage(engineDir: string): Promise<number>;
|
|
114
|
+
/**
|
|
115
|
+
* Creates a distribution package while streaming output to the terminal
|
|
116
|
+
* and capturing the stderr tail for post-run diagnostics. Callers that
|
|
117
|
+
* want to consult {@link explainMachError} on failure should use this
|
|
118
|
+
* variant; the inherit-only `machPackage` above remains for callers that
|
|
119
|
+
* just need an exit code.
|
|
120
|
+
*
|
|
121
|
+
* @param engineDir - Path to the engine directory
|
|
122
|
+
* @returns Captured mach result (stdout tail, stderr tail, exit code)
|
|
123
|
+
*/
|
|
124
|
+
export declare function machPackageCapture(engineDir: string): Promise<MachCommandResult>;
|
|
114
125
|
/**
|
|
115
126
|
* Runs mach watch for auto-rebuilding.
|
|
116
127
|
* @param engineDir - Path to the engine directory
|
package/dist/src/core/mach.js
CHANGED
|
@@ -7,7 +7,7 @@ import { exec, execInherit, execInheritCapture, execSmokeRun, execStream, } from
|
|
|
7
7
|
import { explainMachError } from './mach-error-hints.js';
|
|
8
8
|
import { getPython } from './mach-python.js';
|
|
9
9
|
// Re-export sub-modules so existing `from './mach.js'` imports keep working.
|
|
10
|
-
export { attemptMozinfoRewrite, buildArtifactMismatchMessage, hasBuildArtifacts, } from './mach-build-artifacts.js';
|
|
10
|
+
export { attemptMozinfoRewrite, buildArtifactMismatchMessage, hasBuildArtifacts, hasRunnableBundle, } from './mach-build-artifacts.js';
|
|
11
11
|
export { generateMozconfig } from './mach-mozconfig.js';
|
|
12
12
|
export { ensurePython, resetResolvedPython } from './mach-python.js';
|
|
13
13
|
/**
|
|
@@ -197,6 +197,19 @@ export async function runMachSmoke(args, engineDir, options) {
|
|
|
197
197
|
export async function machPackage(engineDir) {
|
|
198
198
|
return runMach(['package'], engineDir, { inherit: true });
|
|
199
199
|
}
|
|
200
|
+
/**
|
|
201
|
+
* Creates a distribution package while streaming output to the terminal
|
|
202
|
+
* and capturing the stderr tail for post-run diagnostics. Callers that
|
|
203
|
+
* want to consult {@link explainMachError} on failure should use this
|
|
204
|
+
* variant; the inherit-only `machPackage` above remains for callers that
|
|
205
|
+
* just need an exit code.
|
|
206
|
+
*
|
|
207
|
+
* @param engineDir - Path to the engine directory
|
|
208
|
+
* @returns Captured mach result (stdout tail, stderr tail, exit code)
|
|
209
|
+
*/
|
|
210
|
+
export async function machPackageCapture(engineDir) {
|
|
211
|
+
return runMachCapture(['package'], engineDir);
|
|
212
|
+
}
|
|
200
213
|
/**
|
|
201
214
|
* Runs mach watch for auto-rebuilding.
|
|
202
215
|
* @param engineDir - Path to the engine directory
|
|
@@ -16,7 +16,15 @@ export function getRules(binaryName) {
|
|
|
16
16
|
extractArgs: (m) => [m[1] ?? ''],
|
|
17
17
|
},
|
|
18
18
|
{
|
|
19
|
-
|
|
19
|
+
// `.inc.xhtml` fragments under browser/base/content/ are deliberately
|
|
20
|
+
// excluded: they are consumed via `#include` from a registered chrome
|
|
21
|
+
// document (typically browser.xhtml) and do not get their own
|
|
22
|
+
// packaged chrome URI. Before this carve-out, `status` flagged every
|
|
23
|
+
// wired fragment as "potentially unregistered" and `register --dry-run`
|
|
24
|
+
// proposed a bogus jar.mn entry. The lookahead blocks the match so
|
|
25
|
+
// `getUnregistrableAdvice` gets a chance to emit the correct
|
|
26
|
+
// guidance for the `.inc.xhtml` case.
|
|
27
|
+
pattern: /^browser\/base\/content\/(?!.+\.inc\.xhtml$)(.+\.(?:js|mjs|xhtml|css))$/,
|
|
20
28
|
isRegistered: (engineDir, fileName) => isBrowserContentRegistered(engineDir, fileName),
|
|
21
29
|
register: (engineDir, after, dryRun, fileName) => registerBrowserContent(engineDir, fileName, after, undefined, dryRun),
|
|
22
30
|
extractArgs: (m) => [m[1] ?? ''],
|
|
@@ -98,6 +106,19 @@ function getUnregistrableAdvice(filePath) {
|
|
|
98
106
|
if (filePath.endsWith('.ftl')) {
|
|
99
107
|
return "FTL locale files are auto-discovered via jar.mn glob patterns and don't need manual registration.";
|
|
100
108
|
}
|
|
109
|
+
// `.inc.xhtml` fragments live under browser/base/content/ but are
|
|
110
|
+
// consumed via `#include` from a registered chrome document (browser.xhtml
|
|
111
|
+
// by default; a fork's custom top-level doc when `wire --dom-target` is
|
|
112
|
+
// set). The preprocessor resolves the include at packaging time, so the
|
|
113
|
+
// fragment never needs its own chrome URI entry in jar.mn. Give the
|
|
114
|
+
// operator the actionable `wire` path instead of letting the generic
|
|
115
|
+
// "unknown file pattern" message above fire.
|
|
116
|
+
if (/^browser\/base\/content\/.+\.inc\.xhtml$/.test(filePath)) {
|
|
117
|
+
return ('`.inc.xhtml` fragments are consumed via `#include` from a registered chrome document ' +
|
|
118
|
+
'(e.g. browser.xhtml). They do not need an independent jar.mn entry — run ' +
|
|
119
|
+
'"fireforge wire <name> --dom <path>" to insert the #include, or add the directive manually ' +
|
|
120
|
+
'in the top-level chrome document.');
|
|
121
|
+
}
|
|
101
122
|
const testMatch = filePath.match(/^browser\/base\/content\/test\/([^/]+)\/(?!browser\.toml$).+$/);
|
|
102
123
|
if (testMatch) {
|
|
103
124
|
const dir = testMatch[1];
|
package/dist/src/utils/fs.d.ts
CHANGED
|
@@ -78,6 +78,18 @@ export declare function writeFileAtomic(path: string, content: string | Buffer):
|
|
|
78
78
|
* @param dest - Destination directory path
|
|
79
79
|
*/
|
|
80
80
|
export declare function copyDir(src: string, dest: string): Promise<void>;
|
|
81
|
+
/**
|
|
82
|
+
* Matches the atomic-temp-file shape emitted by `createAtomicTempPath`
|
|
83
|
+
* anywhere in a normalised (forward-slash) path. The `.fireforge-tmp-`
|
|
84
|
+
* marker plus a PID/UUID tail is unique to our own rename-based atomic
|
|
85
|
+
* writes, so callers (notably `status`) can filter these mid-flight
|
|
86
|
+
* artefacts out of their listings without racing the rename.
|
|
87
|
+
*
|
|
88
|
+
* Intentionally anchored so a legitimately-named backup file like
|
|
89
|
+
* `.notes.fireforge-tmp-backup` (no PID+UUID continuation) is NOT treated
|
|
90
|
+
* as one of our temps. The full shape is `.<filename>.fireforge-tmp-<pid>-<uuid>`.
|
|
91
|
+
*/
|
|
92
|
+
export declare const FIREFORGE_TMP_PATH_PATTERN: RegExp;
|
|
81
93
|
/**
|
|
82
94
|
* Checks available disk space at a path and warns via the provided
|
|
83
95
|
* callback when it falls below `minBytes`.
|
package/dist/src/utils/fs.js
CHANGED
|
@@ -180,6 +180,18 @@ export async function copyDir(src, dest) {
|
|
|
180
180
|
}
|
|
181
181
|
}
|
|
182
182
|
}
|
|
183
|
+
/**
|
|
184
|
+
* Matches the atomic-temp-file shape emitted by `createAtomicTempPath`
|
|
185
|
+
* anywhere in a normalised (forward-slash) path. The `.fireforge-tmp-`
|
|
186
|
+
* marker plus a PID/UUID tail is unique to our own rename-based atomic
|
|
187
|
+
* writes, so callers (notably `status`) can filter these mid-flight
|
|
188
|
+
* artefacts out of their listings without racing the rename.
|
|
189
|
+
*
|
|
190
|
+
* Intentionally anchored so a legitimately-named backup file like
|
|
191
|
+
* `.notes.fireforge-tmp-backup` (no PID+UUID continuation) is NOT treated
|
|
192
|
+
* as one of our temps. The full shape is `.<filename>.fireforge-tmp-<pid>-<uuid>`.
|
|
193
|
+
*/
|
|
194
|
+
export const FIREFORGE_TMP_PATH_PATTERN = /(^|\/)\.[^/]+\.fireforge-tmp-\d+-[0-9a-f-]{36}$/i;
|
|
183
195
|
/**
|
|
184
196
|
* Generates a unique temp file path for atomic writes.
|
|
185
197
|
*
|
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
/** Converts Windows path separators to forward slashes for stable comparisons. */
|
|
2
2
|
export declare function normalizePathSlashes(path: string): string;
|
|
3
|
+
/**
|
|
4
|
+
* Strips a leading `engine/` (or `engine\\`) segment from a user-supplied
|
|
5
|
+
* path so the same command invocation accepts both repo-root-relative paths
|
|
6
|
+
* (`engine/browser/base/content/foo.js`) and engine-relative paths
|
|
7
|
+
* (`browser/base/content/foo.js`). The match is case-insensitive because
|
|
8
|
+
* default macOS and Windows filesystems treat `Engine/` and `engine/` as
|
|
9
|
+
* the same directory; a literal lowercase-only check previously left `mach`
|
|
10
|
+
* / the manifest writers resolving against a wrongly-cased prefix. Leading
|
|
11
|
+
* whitespace is ignored so tab-completed inputs don't slip past the strip.
|
|
12
|
+
*
|
|
13
|
+
* The return value is trimmed of the same leading whitespace when the
|
|
14
|
+
* prefix matched, and otherwise passed through verbatim — callers that
|
|
15
|
+
* care about internal whitespace can trim on their side.
|
|
16
|
+
*
|
|
17
|
+
* @param filePath Path as provided by the user
|
|
18
|
+
* @returns Path relative to the engine directory (or the original when the
|
|
19
|
+
* prefix was absent)
|
|
20
|
+
*/
|
|
21
|
+
export declare function stripEnginePrefix(filePath: string): string;
|
|
3
22
|
/** Checks whether a path is explicitly absolute on either POSIX or Windows. */
|
|
4
23
|
export declare function isExplicitAbsolutePath(path: string): boolean;
|
|
5
24
|
/** Resolves a candidate path and returns whether it stays within the given root. */
|
package/dist/src/utils/paths.js
CHANGED
|
@@ -2,10 +2,43 @@
|
|
|
2
2
|
import { isAbsolute, relative, resolve } from 'node:path';
|
|
3
3
|
const WINDOWS_ABSOLUTE_PATH = /^[a-zA-Z]:[\\/]/;
|
|
4
4
|
const RELATIVE_PATH_ROOT = resolve('/__fireforge_path_root__');
|
|
5
|
+
/**
|
|
6
|
+
* Matches a leading `engine/` or `engine\\` segment (case-insensitive,
|
|
7
|
+
* tolerates leading whitespace). Shared between `register`, `test`, `lint`,
|
|
8
|
+
* and `export` so every command that takes an engine-relative path accepts
|
|
9
|
+
* both the repo-root form (`engine/browser/...`) and the engine-relative
|
|
10
|
+
* form (`browser/...`) without diverging.
|
|
11
|
+
*/
|
|
12
|
+
const ENGINE_PREFIX_PATTERN = /^\s*engine[/\\]/i;
|
|
5
13
|
/** Converts Windows path separators to forward slashes for stable comparisons. */
|
|
6
14
|
export function normalizePathSlashes(path) {
|
|
7
15
|
return path.replace(/\\/g, '/');
|
|
8
16
|
}
|
|
17
|
+
/**
|
|
18
|
+
* Strips a leading `engine/` (or `engine\\`) segment from a user-supplied
|
|
19
|
+
* path so the same command invocation accepts both repo-root-relative paths
|
|
20
|
+
* (`engine/browser/base/content/foo.js`) and engine-relative paths
|
|
21
|
+
* (`browser/base/content/foo.js`). The match is case-insensitive because
|
|
22
|
+
* default macOS and Windows filesystems treat `Engine/` and `engine/` as
|
|
23
|
+
* the same directory; a literal lowercase-only check previously left `mach`
|
|
24
|
+
* / the manifest writers resolving against a wrongly-cased prefix. Leading
|
|
25
|
+
* whitespace is ignored so tab-completed inputs don't slip past the strip.
|
|
26
|
+
*
|
|
27
|
+
* The return value is trimmed of the same leading whitespace when the
|
|
28
|
+
* prefix matched, and otherwise passed through verbatim — callers that
|
|
29
|
+
* care about internal whitespace can trim on their side.
|
|
30
|
+
*
|
|
31
|
+
* @param filePath Path as provided by the user
|
|
32
|
+
* @returns Path relative to the engine directory (or the original when the
|
|
33
|
+
* prefix was absent)
|
|
34
|
+
*/
|
|
35
|
+
export function stripEnginePrefix(filePath) {
|
|
36
|
+
const match = ENGINE_PREFIX_PATTERN.exec(filePath);
|
|
37
|
+
if (match) {
|
|
38
|
+
return filePath.slice(match[0].length);
|
|
39
|
+
}
|
|
40
|
+
return filePath;
|
|
41
|
+
}
|
|
9
42
|
/** Checks whether a path is explicitly absolute on either POSIX or Windows. */
|
|
10
43
|
export function isExplicitAbsolutePath(path) {
|
|
11
44
|
return isAbsolute(path) || WINDOWS_ABSOLUTE_PATH.test(path);
|