@hominis/fireforge 0.15.9 → 0.16.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 +142 -0
- package/README.md +6 -2
- package/dist/src/cli.d.ts +4 -1
- package/dist/src/cli.js +6 -3
- package/dist/src/commands/config.js +16 -5
- package/dist/src/commands/download.js +31 -4
- package/dist/src/commands/export-all.js +96 -9
- package/dist/src/commands/export.js +10 -1
- package/dist/src/commands/furnace/chrome-doc-templates.d.ts +11 -1
- package/dist/src/commands/furnace/chrome-doc-templates.js +12 -2
- package/dist/src/commands/furnace/create.js +21 -3
- package/dist/src/commands/furnace/diff.js +22 -2
- package/dist/src/commands/furnace/index.js +1 -0
- package/dist/src/commands/furnace/init.js +76 -2
- package/dist/src/commands/furnace/override.js +35 -12
- package/dist/src/commands/furnace/preview.js +46 -1
- package/dist/src/commands/furnace/rename.js +14 -3
- package/dist/src/commands/lint.js +26 -2
- package/dist/src/commands/package.js +16 -5
- package/dist/src/commands/re-export.js +25 -0
- package/dist/src/commands/rebase/patch-loop.js +19 -0
- package/dist/src/commands/register.js +2 -18
- package/dist/src/commands/run.js +23 -2
- package/dist/src/commands/status.js +42 -8
- 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/commands/wire.js +35 -9
- 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/build-baseline.d.ts +14 -0
- package/dist/src/core/build-baseline.js +61 -1
- package/dist/src/core/config-mutate.d.ts +1 -1
- package/dist/src/core/config.d.ts +17 -0
- package/dist/src/core/config.js +35 -0
- package/dist/src/core/firefox.d.ts +16 -2
- package/dist/src/core/firefox.js +7 -2
- package/dist/src/core/furnace-config.d.ts +23 -0
- package/dist/src/core/furnace-config.js +38 -0
- 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 +38 -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/core/patch-lint.js +43 -20
- package/dist/src/core/test-stale-check.js +46 -1
- package/dist/src/core/token-manager.js +57 -4
- package/dist/src/core/token-scaffold.d.ts +36 -0
- package/dist/src/core/token-scaffold.js +74 -0
- package/dist/src/types/commands/options.d.ts +10 -0
- 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
|
@@ -23,6 +23,9 @@
|
|
|
23
23
|
* plugin, etc.) can legitimately have a fresh `dist/` with no
|
|
24
24
|
* FireForge-recorded baseline update.
|
|
25
25
|
*/
|
|
26
|
+
import { createHash } from 'node:crypto';
|
|
27
|
+
import { readFile } from 'node:fs/promises';
|
|
28
|
+
import { join } from 'node:path';
|
|
26
29
|
import { toError } from '../utils/errors.js';
|
|
27
30
|
import { verbose } from '../utils/logger.js';
|
|
28
31
|
import { isPackageablePath } from './build-audit.js';
|
|
@@ -90,7 +93,32 @@ export async function checkStaleBuildForTest(projectRoot, engineDir) {
|
|
|
90
93
|
return { stale: false, changedPaths: [], truncated: 0, baseline: undefined };
|
|
91
94
|
}
|
|
92
95
|
const changed = await collectChangedEnginePaths(engineDir, baseline);
|
|
93
|
-
|
|
96
|
+
let packageable = changed.filter((path) => isPackageablePath(path)).sort();
|
|
97
|
+
// Content-hash comparison: when the baseline carries a fingerprint set,
|
|
98
|
+
// fold each candidate path through a live re-hash and drop paths whose
|
|
99
|
+
// current content matches the baseline. Pre-0.16.0 baselines have no
|
|
100
|
+
// `packageableFingerprints` field; those fall through and the
|
|
101
|
+
// path-only comparison behaves as before (every workdir-dirty
|
|
102
|
+
// packageable path is reported as stale). The concrete motivating
|
|
103
|
+
// case: a project with imported patches + Furnace-applied components
|
|
104
|
+
// always has a persistent workdir diff against HEAD. Before the
|
|
105
|
+
// fingerprint layer, `git diff --name-only HEAD` returned that diff
|
|
106
|
+
// on every build, so the stale check fired immediately after a
|
|
107
|
+
// successful build even though nothing had actually changed. The
|
|
108
|
+
// fingerprints capture "these files had this content when the build
|
|
109
|
+
// ran"; a path stays stale only when its live hash diverges.
|
|
110
|
+
const fingerprints = baseline.packageableFingerprints;
|
|
111
|
+
if (fingerprints) {
|
|
112
|
+
const staleAfterHashCheck = [];
|
|
113
|
+
for (const path of packageable) {
|
|
114
|
+
const recorded = fingerprints[path];
|
|
115
|
+
const live = await hashEngineFile(engineDir, path);
|
|
116
|
+
if (recorded === undefined || live === undefined || recorded !== live) {
|
|
117
|
+
staleAfterHashCheck.push(path);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
packageable = staleAfterHashCheck;
|
|
121
|
+
}
|
|
94
122
|
if (packageable.length === 0) {
|
|
95
123
|
return { stale: false, changedPaths: [], truncated: 0, baseline };
|
|
96
124
|
}
|
|
@@ -98,6 +126,23 @@ export async function checkStaleBuildForTest(projectRoot, engineDir) {
|
|
|
98
126
|
const truncated = Math.max(0, packageable.length - head.length);
|
|
99
127
|
return { stale: true, changedPaths: head, truncated, baseline };
|
|
100
128
|
}
|
|
129
|
+
/**
|
|
130
|
+
* Reads a file under the engine directory and returns a hex-encoded
|
|
131
|
+
* SHA-256 of its contents, matching the hash the baseline writer
|
|
132
|
+
* produces. Returns `undefined` on any IO error (missing file,
|
|
133
|
+
* permission denied, etc.) so the caller can treat the path as still
|
|
134
|
+
* stale rather than crashing the preflight.
|
|
135
|
+
*/
|
|
136
|
+
async function hashEngineFile(engineDir, relPath) {
|
|
137
|
+
try {
|
|
138
|
+
const buffer = await readFile(join(engineDir, relPath));
|
|
139
|
+
return createHash('sha256').update(buffer).digest('hex');
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
verbose(`Stale-build preflight: could not hash ${relPath} for baseline comparison — ${toError(error).message}`);
|
|
143
|
+
return undefined;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
101
146
|
/**
|
|
102
147
|
* Formats a human-readable warning body from a {@link StaleBuildResult}.
|
|
103
148
|
* Kept separate from the probe so test code can assert on the structured
|
|
@@ -93,8 +93,56 @@ async function assertTokenCategoryExists(engineDir, tokensCssPath, category) {
|
|
|
93
93
|
}
|
|
94
94
|
}
|
|
95
95
|
}
|
|
96
|
-
|
|
97
|
-
|
|
96
|
+
const discoveredCategories = discoverCategoryHeaders(lines);
|
|
97
|
+
const available = discoveredCategories.length > 0
|
|
98
|
+
? `Available categories in the file: ${discoveredCategories.map((name) => `"${name}"`).join(', ')}.`
|
|
99
|
+
: 'The file currently has no category headers. Add one by hand near the top of the :root { … } block — the format is "/* = My Category = */" — or run "fireforge furnace init --force" to re-scaffold the default seed set.';
|
|
100
|
+
throw new GeneralError(`Category "${category}" not found in ${tokensCssPath}.\n\n` +
|
|
101
|
+
`${available}\n\n` +
|
|
102
|
+
'Categories are declared by comment headers. Single-line shape: /* = My Category = */. ' +
|
|
103
|
+
'Multi-line shape: /* =============\\n * My Category\\n * ============= */.');
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Scans a tokens CSS file for category header comments and returns the
|
|
107
|
+
* category names in document order. Used to enrich the "category not
|
|
108
|
+
* found" error body with concrete alternatives the operator can copy.
|
|
109
|
+
*
|
|
110
|
+
* Mirrors the shapes `findCategorySection` already recognises:
|
|
111
|
+
* - Single-line: `/* = Foo = *\/`
|
|
112
|
+
* - Multi-line: `/* =====` opening, `Foo` on any of the next ~5 lines,
|
|
113
|
+
* closing `*\/`.
|
|
114
|
+
*
|
|
115
|
+
* This helper exists as a pure inspector; it never throws on malformed
|
|
116
|
+
* headers and silently skips shapes it cannot parse.
|
|
117
|
+
*/
|
|
118
|
+
function discoverCategoryHeaders(lines) {
|
|
119
|
+
const categories = new Set();
|
|
120
|
+
const singleLinePattern = /\/\*\s*=+\s*(.+?)\s*=+\s*\*\//;
|
|
121
|
+
for (let i = 0; i < lines.length; i++) {
|
|
122
|
+
const line = lines[i] ?? '';
|
|
123
|
+
const singleMatch = singleLinePattern.exec(line);
|
|
124
|
+
if (singleMatch?.[1]) {
|
|
125
|
+
const extracted = singleMatch[1].trim();
|
|
126
|
+
if (extracted.length > 0)
|
|
127
|
+
categories.add(extracted);
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
if (/^\s*\/\*\s*=+/.test(line) && !/\*\//.test(line)) {
|
|
131
|
+
for (let j = i + 1; j < Math.min(i + 6, lines.length); j++) {
|
|
132
|
+
const blockLine = lines[j] ?? '';
|
|
133
|
+
if (/\*\//.test(blockLine))
|
|
134
|
+
break;
|
|
135
|
+
const trimmed = blockLine.replace(/^\s*\*\s*/, '').trim();
|
|
136
|
+
if (trimmed.length === 0)
|
|
137
|
+
continue;
|
|
138
|
+
if (/^=+$/.test(trimmed))
|
|
139
|
+
continue;
|
|
140
|
+
categories.add(trimmed);
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return [...categories];
|
|
98
146
|
}
|
|
99
147
|
/**
|
|
100
148
|
* Validates token-add inputs without mutating files.
|
|
@@ -187,8 +235,13 @@ function findCategorySection(lines, category, tokensCssPath) {
|
|
|
187
235
|
}
|
|
188
236
|
}
|
|
189
237
|
if (categoryLine === -1) {
|
|
190
|
-
|
|
191
|
-
|
|
238
|
+
const discoveredCategories = discoverCategoryHeaders(lines);
|
|
239
|
+
const available = discoveredCategories.length > 0
|
|
240
|
+
? `Available categories in the file: ${discoveredCategories.map((name) => `"${name}"`).join(', ')}.`
|
|
241
|
+
: 'The file currently has no category headers.';
|
|
242
|
+
throw new GeneralError(`Category "${category}" not found in ${tokensCssPath}.\n\n` +
|
|
243
|
+
`${available}\n\n` +
|
|
244
|
+
'Add a header by hand inside the :root block (format: "/* = My Category = */") or re-run "fireforge furnace init --force" to re-seed the default categories.');
|
|
192
245
|
}
|
|
193
246
|
// Find the end of this category section (next section header or closing })
|
|
194
247
|
// Handles both single-line (/* =...= */) and multi-line (/* ===...) section delimiters
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scaffolds the default tokens CSS file consumed by `fireforge token add`.
|
|
3
|
+
*
|
|
4
|
+
* Before 0.16.0 `fireforge furnace init` wrote `furnace.json` but not the
|
|
5
|
+
* tokens CSS — every project's first `fireforge token add` hit
|
|
6
|
+
* `Token CSS file not found: browser/themes/shared/<binaryName>-tokens.css`.
|
|
7
|
+
* The 0.16.0 init now calls into this module to write a canonical
|
|
8
|
+
* `:root { … }` shell with a seed set of category headers that
|
|
9
|
+
* `assertTokenCategoryExists` recognises, and registers the tokens CSS
|
|
10
|
+
* path in `patchLint.rawColorAllowlist` so the first token that's an
|
|
11
|
+
* actual color value does not instantly fail `fireforge lint`.
|
|
12
|
+
*/
|
|
13
|
+
import type { ProjectLicense } from '../types/config.js';
|
|
14
|
+
/**
|
|
15
|
+
* The set of categories seeded by the default scaffold. `token add
|
|
16
|
+
* --category <name>` accepts any of these without further setup; an
|
|
17
|
+
* operator who needs another category only has to add a matching
|
|
18
|
+
* `/* = My Category = *\/` header inside the `:root` block by hand.
|
|
19
|
+
*
|
|
20
|
+
* The names intentionally mirror the vocabulary used in Firefox's own
|
|
21
|
+
* token files (Colors — Canvas, Spacing, …) so operators coming from
|
|
22
|
+
* upstream don't have to relearn a fork-specific taxonomy.
|
|
23
|
+
*/
|
|
24
|
+
export declare const DEFAULT_TOKEN_CATEGORIES: readonly string[];
|
|
25
|
+
/**
|
|
26
|
+
* Generates the default tokens CSS body. Extracted from the init
|
|
27
|
+
* command so tests can assert on the generated shape without running
|
|
28
|
+
* the interactive scaffold flow.
|
|
29
|
+
*
|
|
30
|
+
* @param binaryName - `fireforge.json` `binaryName` used in the
|
|
31
|
+
* rendered file banner so operators can identify the fork on-sight.
|
|
32
|
+
* @param license - Project license; piped through `getLicenseHeader`
|
|
33
|
+
* so the scaffold is SPDX-marked and survives `fireforge lint`'s
|
|
34
|
+
* license-header checks without operator intervention.
|
|
35
|
+
*/
|
|
36
|
+
export declare function generateDefaultTokensCss(binaryName: string, license: ProjectLicense): string;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* Scaffolds the default tokens CSS file consumed by `fireforge token add`.
|
|
4
|
+
*
|
|
5
|
+
* Before 0.16.0 `fireforge furnace init` wrote `furnace.json` but not the
|
|
6
|
+
* tokens CSS — every project's first `fireforge token add` hit
|
|
7
|
+
* `Token CSS file not found: browser/themes/shared/<binaryName>-tokens.css`.
|
|
8
|
+
* The 0.16.0 init now calls into this module to write a canonical
|
|
9
|
+
* `:root { … }` shell with a seed set of category headers that
|
|
10
|
+
* `assertTokenCategoryExists` recognises, and registers the tokens CSS
|
|
11
|
+
* path in `patchLint.rawColorAllowlist` so the first token that's an
|
|
12
|
+
* actual color value does not instantly fail `fireforge lint`.
|
|
13
|
+
*/
|
|
14
|
+
import { getLicenseHeader } from './license-headers.js';
|
|
15
|
+
/**
|
|
16
|
+
* The set of categories seeded by the default scaffold. `token add
|
|
17
|
+
* --category <name>` accepts any of these without further setup; an
|
|
18
|
+
* operator who needs another category only has to add a matching
|
|
19
|
+
* `/* = My Category = *\/` header inside the `:root` block by hand.
|
|
20
|
+
*
|
|
21
|
+
* The names intentionally mirror the vocabulary used in Firefox's own
|
|
22
|
+
* token files (Colors — Canvas, Spacing, …) so operators coming from
|
|
23
|
+
* upstream don't have to relearn a fork-specific taxonomy.
|
|
24
|
+
*/
|
|
25
|
+
export const DEFAULT_TOKEN_CATEGORIES = [
|
|
26
|
+
'Colors — General',
|
|
27
|
+
'Colors — Canvas',
|
|
28
|
+
'Colors — Experiment',
|
|
29
|
+
'Spacing',
|
|
30
|
+
];
|
|
31
|
+
/**
|
|
32
|
+
* Generates the default tokens CSS body. Extracted from the init
|
|
33
|
+
* command so tests can assert on the generated shape without running
|
|
34
|
+
* the interactive scaffold flow.
|
|
35
|
+
*
|
|
36
|
+
* @param binaryName - `fireforge.json` `binaryName` used in the
|
|
37
|
+
* rendered file banner so operators can identify the fork on-sight.
|
|
38
|
+
* @param license - Project license; piped through `getLicenseHeader`
|
|
39
|
+
* so the scaffold is SPDX-marked and survives `fireforge lint`'s
|
|
40
|
+
* license-header checks without operator intervention.
|
|
41
|
+
*/
|
|
42
|
+
export function generateDefaultTokensCss(binaryName, license) {
|
|
43
|
+
const header = getLicenseHeader(license, 'css');
|
|
44
|
+
const categoryBlocks = DEFAULT_TOKEN_CATEGORIES.map((category) => ` /* = ${category} = */\n /* (add design tokens for "${category}" here; use \`fireforge token add --category "${category}" …\`) */`).join('\n\n');
|
|
45
|
+
return `${header}
|
|
46
|
+
|
|
47
|
+
/*
|
|
48
|
+
* Design tokens for ${binaryName}.
|
|
49
|
+
*
|
|
50
|
+
* Scaffolded by \`fireforge furnace init\`. Add new tokens with
|
|
51
|
+
* \`fireforge token add --category '<name>' -- <token-name> <value>\`
|
|
52
|
+
* — the command appends into the matching \`/* = <name> = *\\/\` block
|
|
53
|
+
* below and keeps the per-category ordering stable.
|
|
54
|
+
*
|
|
55
|
+
* Raw color literals inside this file are expected — the whole point
|
|
56
|
+
* of the tokens layer is that every other CSS file consumes \`var(…)\`
|
|
57
|
+
* instead of literal colors. \`fireforge furnace init\` adds this
|
|
58
|
+
* file's path to \`patchLint.rawColorAllowlist\` in fireforge.json so
|
|
59
|
+
* \`fireforge lint\` stops flagging the literals here.
|
|
60
|
+
*/
|
|
61
|
+
|
|
62
|
+
:root {
|
|
63
|
+
${categoryBlocks}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
@media (prefers-color-scheme: dark) {
|
|
67
|
+
:root {
|
|
68
|
+
/* Dark-mode overrides land here. Use \`fireforge token add --mode override --dark-value <v>\`
|
|
69
|
+
to have a token's dark value placed inside this block. */
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
`;
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=token-scaffold.js.map
|
|
@@ -372,6 +372,16 @@ export interface FurnaceCreateOptions {
|
|
|
372
372
|
* dry-run faithfully previews the real command's outcome.
|
|
373
373
|
*/
|
|
374
374
|
dryRun?: boolean;
|
|
375
|
+
/**
|
|
376
|
+
* Bypass the configured `componentPrefix` check for the supplied name.
|
|
377
|
+
* Without this flag, a name that does not start with the prefix is
|
|
378
|
+
* rejected before any files are written, so a prefix-mismatched
|
|
379
|
+
* component cannot leave behind a half-scaffolded state. Pass this
|
|
380
|
+
* flag only when you know the prefix mismatch is intentional — e.g.
|
|
381
|
+
* creating an experimental component whose name intentionally breaks
|
|
382
|
+
* the fork's convention.
|
|
383
|
+
*/
|
|
384
|
+
allowPrefixMismatch?: boolean;
|
|
375
385
|
}
|
|
376
386
|
/**
|
|
377
387
|
* Options for the wire command.
|
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);
|