@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
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.34.0
|
|
4
|
+
|
|
5
|
+
- Reworked the macOS resource-monitor (psutil) crash mitigation to an **in-process guard FireForge owns on its mach dispatches**: a `fireforge_mach_guard.pth` + module pair is installed directly into every discovered mach virtualenv site-packages (objdir `_virtualenvs` and the `~/.mozbuild` state dir) before each dispatch, because mach's venv re-exec drops `PYTHONPATH` and the 0.33.0 `sitecustomize.py` route never loaded (the env-var shim is retained only for the pre-venv bootstrap phase). The guard now covers the whole crash family: `psutil.virtual_memory`/`swap_memory`/`cpu_percent`/`cpu_times`/`disk_io_counters` are wrapped module-wide (which also covers the direct `psutil.virtual_memory()` call in `mozbuild.base._run_make`), and `SystemResourceMonitor` **construction** is guarded via an import hook that pre-populates `poll_interval` and degrades a partially-constructed monitor to a no-op — so the `AttributeError: ... poll_interval` variant can no longer escape a polling-only patch.
|
|
6
|
+
- Routed every mach build dispatch — plain `fireforge build`, `build --ui` (`mach build faster`), and the pre-test build step of `fireforge test --build` — through one protected path (`runProtectedMachBuild`) with a **uniform recognized-crash retry budget** (default 2 retries, matching `--harness-retries`; the pre-test step forwards the operator's `--harness-retries` value). Each retry spawns a fresh mach process AND re-installs the guard (re-discovering venvs), so a venv materialized by a crashed first attempt is guarded on the next one instead of every retry dying on the same wedged state. Non-crash build failures are never retried; exhausted budgets surface the crash shape and evidence line above the regular diagnostics. The suite-specific test dispatches (`mach test` / `xpcshell-test` / `mochitest`) install the same guard.
|
|
7
|
+
- Investigated the field incident where a failed pre-test build was followed by a ~64-minute full rebuild: FireForge's protected path performs **no configure/clobber beyond what `mach build faster` itself does** (retries never re-run `prepareBuildEnvironment`/`mach configure`; there is no fallback-to-full-build in FireForge). The pre-test build now also passes the previous build baseline into `prepareBuildEnvironment` exactly like `fireforge build` does, so auto-configure runs under identical conditions on both paths instead of diverging.
|
|
8
|
+
- Fixed the degraded-psutil fallback crashing mozsystemmonitor on the fallback itself (downstream report: `TypeError: '_DegradedReading' object is not subscriptable` from `_build_meta`'s `psutil.virtual_memory()[0]`, aborting `mach mochitest` at startup with zero `TEST-START` lines, and `_collect failed: '_DegradedReading' object is not iterable`). The guard's degraded reading is now a **real duck type of psutil's namedtuple results**: each wrapped function degrades to a zeroed instance of psutil's own result namedtuple (`svmem`/`sswap`/`scputimes`/`sdiskio`, resolved via `getattr` without touching the failing syscall), falling back to an extended `_DegradedReading` that supports subscripting, iteration, `len()`, `_fields` (svmem field order), and `_asdict()`. `cpu_percent` degrades to a plain `0.0` float (callers do arithmetic on it), and `disk_io_counters` deliberately degrades to a zeroed struct rather than `None`. Runtime-verified by new python3-executed regression tests covering a mozsystemmonitor-style `_build_meta`/`_collect` sequence against a degraded host.
|
|
9
|
+
- Taught the harness-crash classifier the two `_DegradedReading` crash signatures (`object is not subscriptable` / `object is not iterable`), so a startup abort with zero `TEST-START` lines from this family classifies as crash-not-failure and retries; the `_collect failed: '_DegradedReading' ...` evidence line is exempted from the noise-stripping that otherwise drops `_collect failed` degradation chatter, while a completed green summary still vetoes the classification.
|
|
10
|
+
- Fixed the sharded/retry crash classifier marking fully green runs as `CRASH (N attempts)`: a completed green embedded summary (a `TEST_START`/`TEST-START` execution signal, `Unexpected results: 0`, and `SUITE_END`) now **vetoes** signature-based crash classification for that attempt, and resource-monitor degradation lines (`UserWarning: psutil failed to run: ...`, `_collect failed: ...`, FireForge's own degradation notice) plus mach's caught telemetry tracebacks are stripped from crash evidence entirely. Exit codes follow the corrected verdict: a run whose every shard is green exits 0, and a non-sharded (`--no-shard`) run whose embedded summary completed green exits 0 even when mach's own exit code went non-zero on harness noise (a note explains the override) — parsing embedded summaries by hand is no longer necessary. Runs with a non-zero unexpected count or real `TEST-UNEXPECTED-*` lines still fail, and the post-green shutdown re-entry shape still classifies as a crash.
|
|
11
|
+
- Fixed `fireforge test <directory>` dispatching xpcshell-only directories to the mochitest runner ("could not find any mochitests under the following test path(s)"): the manifest walk now starts at the directory itself, so a directory whose own manifest is an `xpcshell.toml` dispatches to `mach xpcshell-test` just like an explicit `test_*.js` path.
|
|
12
|
+
- Added `fireforge register --create-manifest`: registering a module under a directory with no `moz.build` now scaffolds the directory manifest (MPL-header + `EXTRA_JS_MODULES.<namespace>` list) and wires the parent `DIRS` chain up to the nearest existing moz.build (creating intermediates); without the flag the "Manifest not found" error names it. Also added an **xpcshell test-file pattern** (`**/test_*.js` outside `browser/base/content/test/`) that inserts the `["test_*.js"]` section into the directory's `xpcshell.toml` in mozbuild sort order — or creates the manifest and wires `XPCSHELL_TESTS_MANIFESTS` with `--create-manifest` — and extended the browser-chrome manifest rule to fork-owned `browser.toml` manifests at **arbitrary depth** under `browser/base/content/test/` (previously one level only).
|
|
13
|
+
- Improved export ergonomics three ways: (1) `re-export <patch> --files` now accepts the `export`-style space-separated path shape — path-shaped extra positionals are folded into the file list with a notice, and the "operates on exactly one target patch" error explains both accepted forms (the dry-run "removed files (N; become unmanaged)" preview is unchanged); (2) directory exports auto-exclude files already owned by other patches, printing a per-file `Excluding <file> ... (owned by <patch>)` notice and a `re-export --files` pointer instead of hitting the duplicate-new-file-creation refusal at placement lint (whose message now recommends an explicit file list rather than `--force-unsafe`; explicitly named files are never auto-excluded); (3) `export --name 203-ui-foo --category ui` no longer produces `203-ui-203-ui-foo.patch` — a leading `NNN-<category>-` prefix matching the selected category is stripped from the name before the filename builders prepend order and category.
|
|
14
|
+
- Fixed `furnace deploy` leaving a stale toolkit `jar.mn` line after a component helper file rename (every build then failed at packaging with "File ... not found"): entry removal now keys on the `(widgets/<tag>/...)` source-mapping segment so ALL of a component's lines are removed regardless of basename, and the remove-then-re-add reconciliation therefore prunes renamed/removed helpers. `furnace validate` gained a `stale-jar-registration` error for registration lines pointing at files that no longer exist (`--fix` prunes them), and `doctor` gained a "Furnace jar.mn registrations" check (`--repair-furnace` prunes) — both previously reported success while the stale line survived.
|
|
15
|
+
- Added `furnace scan --track`, which persists every discovered untracked component into the `stock` section of furnace.json non-interactively (same locked, rollback-journaled write path as the interactive confirm flow). Scan's help and its non-interactive output now state that scan is report-only by default and where the inventory is consumed, ending the report-persist-nothing-explain-nothing behavior.
|
|
16
|
+
- Added `furnace chrome-doc create --browser-window`, a browser.xhtml-like scaffold for the document that ships as the fork's main browser window: `<html id="main-window">` root with the `windowtype="navigator:browser"`/`chromehidden`/geometry-`persist` attributes platform C++ reads before scripts run, while keeping the generic scaffold's bootstrap wiring, sentinel, and (already-correct) jar.mn registrations. When the target document matches a configured `tokenHostDocuments` entry and the flag was not passed, create warns that the browser-window variant is probably intended.
|
|
17
|
+
- Added `furnace create --test-dir <dir>` to redirect the `--with-tests` scaffold (browser-chrome and xpcshell styles) to any engine-relative directory under `browser/base/content/test/` — nested manifests register correctly — and made all test scaffolds collision-safe: existing `browser.toml`/`xpcshell.toml`/`chrome.toml` manifests are appended to (with a shared-manifest notice) instead of scaffolded over, and existing `head.js`/test implementation files are never overwritten.
|
|
18
|
+
|
|
3
19
|
## 0.33.0
|
|
4
20
|
|
|
5
21
|
- Fixed `furnace deploy`/`apply` to prune a dangling per-widget locale `jar.mn` entry for a `localized: true` widget that uses the `sharedFtl` browser-bundle convention. A stale `locale/@AB_CD@/toolkit/global/<name>.ftl` line (written by an older FireForge) pointed at a `.ftl` that never exists, so `mach build` failed hard (`Cannot find toolkit/global/<name>.ftl`) and blocked every build; apply now drops that per-widget line idempotently while leaving the shared bundle's own line untouched.
|
|
@@ -6,6 +6,7 @@ import { readBuildBaseline, writeBuildBaseline } from '../core/build-baseline.js
|
|
|
6
6
|
import { prepareBuildEnvironment } from '../core/build-prepare.js';
|
|
7
7
|
import { getProjectPaths, loadConfig } from '../core/config.js';
|
|
8
8
|
import { attemptMozinfoRewrite, build, buildArtifactMismatchMessage, buildUI, hasBuildArtifacts, hasRunnableBundle, runMach, withBuildLock, } from '../core/mach.js';
|
|
9
|
+
import { buildHarnessCrashMessage } from '../core/test-harness-crash.js';
|
|
9
10
|
import { GeneralError } from '../errors/base.js';
|
|
10
11
|
import { AmbiguousBuildArtifactsError, BuildError } from '../errors/build.js';
|
|
11
12
|
import { toError } from '../utils/errors.js';
|
|
@@ -260,7 +261,14 @@ export async function buildCommand(projectRoot, options) {
|
|
|
260
261
|
if (result.exitCode !== 0) {
|
|
261
262
|
error(`Build failed after ${timeStr}`);
|
|
262
263
|
const machCommand = options.ui ? 'mach build faster' : 'mach build';
|
|
263
|
-
|
|
264
|
+
// When the protected dispatch exhausted its recognized-crash retry
|
|
265
|
+
// budget, lead with the environmental-crash explanation so the
|
|
266
|
+
// operator does not read a resource-monitor traceback as a build
|
|
267
|
+
// regression.
|
|
268
|
+
const crashPreamble = result.crashSignature
|
|
269
|
+
? `${buildHarnessCrashMessage(result.crashSignature, result.attempts, machCommand)}\n\n`
|
|
270
|
+
: '';
|
|
271
|
+
throw new BuildError(crashPreamble + buildFailureDiagnostics(result, paths.engine, buildCheck.objDir, machCommand), machCommand);
|
|
264
272
|
}
|
|
265
273
|
// Tool-managed branding edits that land on `browser/moz.configure`
|
|
266
274
|
// before the build cause mach's post-build guard to print one of two
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `doctor` check for stale furnace jar.mn registrations (0.34.0 field
|
|
3
|
+
* report): a component-file rename left the old toolkit jar.mn line
|
|
4
|
+
* pointing at the deleted file, every build failed at packaging, and
|
|
5
|
+
* `doctor --repair-furnace` reported success without pruning. Split out
|
|
6
|
+
* of `doctor-furnace.ts` to keep that file within the line budget.
|
|
7
|
+
*/
|
|
8
|
+
import type { DoctorCheckDefinition } from './doctor-check-core.js';
|
|
9
|
+
/**
|
|
10
|
+
* "Furnace jar.mn registrations" check: detect widget registration lines
|
|
11
|
+
* pointing at component files that no longer exist in the workspace
|
|
12
|
+
* (typically left by a rename under an older FireForge). These break
|
|
13
|
+
* `mach build` at packaging; `--repair-furnace` prunes them (0.34.0 field
|
|
14
|
+
* report: doctor --repair-furnace reported success without pruning).
|
|
15
|
+
*/
|
|
16
|
+
export declare const furnaceStaleJarRegistrationCheck: DoctorCheckDefinition;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* `doctor` check for stale furnace jar.mn registrations (0.34.0 field
|
|
4
|
+
* report): a component-file rename left the old toolkit jar.mn line
|
|
5
|
+
* pointing at the deleted file, every build failed at packaging, and
|
|
6
|
+
* `doctor --repair-furnace` reported success without pruning. Split out
|
|
7
|
+
* of `doctor-furnace.ts` to keep that file within the line budget.
|
|
8
|
+
*/
|
|
9
|
+
import { getFurnacePaths } from '../core/furnace-config.js';
|
|
10
|
+
import { findStaleJarMnEntries, pruneStaleJarMnEntries } from '../core/furnace-registration.js';
|
|
11
|
+
import { toError } from '../utils/errors.js';
|
|
12
|
+
import { failure, ok, warning } from './doctor-check-core.js';
|
|
13
|
+
/**
|
|
14
|
+
* "Furnace jar.mn registrations" check: detect widget registration lines
|
|
15
|
+
* pointing at component files that no longer exist in the workspace
|
|
16
|
+
* (typically left by a rename under an older FireForge). These break
|
|
17
|
+
* `mach build` at packaging; `--repair-furnace` prunes them (0.34.0 field
|
|
18
|
+
* report: doctor --repair-furnace reported success without pruning).
|
|
19
|
+
*/
|
|
20
|
+
export const furnaceStaleJarRegistrationCheck = {
|
|
21
|
+
name: 'Furnace jar.mn registrations',
|
|
22
|
+
dependsOn: ['Furnace configuration'],
|
|
23
|
+
skipIf: (ctx) => !ctx.furnaceConfigExists || !ctx.furnaceConfig || !ctx.engineExists,
|
|
24
|
+
run: async (ctx) => {
|
|
25
|
+
const config = ctx.furnaceConfig;
|
|
26
|
+
if (!config)
|
|
27
|
+
return [];
|
|
28
|
+
const furnacePaths = getFurnacePaths(ctx.projectRoot);
|
|
29
|
+
const managedTags = Object.keys(config.custom);
|
|
30
|
+
const stale = await findStaleJarMnEntries(ctx.paths.engine, furnacePaths.customDir, managedTags);
|
|
31
|
+
if (stale.length === 0) {
|
|
32
|
+
return ok('Furnace jar.mn registrations');
|
|
33
|
+
}
|
|
34
|
+
const staleList = stale.map((entry) => `${entry.tagName}/${entry.fileName}`).join(', ');
|
|
35
|
+
if (!ctx.options.repairFurnace) {
|
|
36
|
+
return warning('Furnace jar.mn registrations', `jar.mn carries ${stale.length} registration line${stale.length === 1 ? '' : 's'} pointing at removed component file${stale.length === 1 ? '' : 's'} (${staleList}). mach build will fail at packaging ("File ... not found").`, 'Run "fireforge doctor --repair-furnace" (or "fireforge furnace validate --fix") to prune the stale lines.');
|
|
37
|
+
}
|
|
38
|
+
try {
|
|
39
|
+
const pruned = await pruneStaleJarMnEntries(ctx.paths.engine, furnacePaths.customDir, managedTags);
|
|
40
|
+
return warning('Furnace jar.mn registrations', `Pruned ${pruned.length} stale jar.mn registration line${pruned.length === 1 ? '' : 's'} (${staleList}).`);
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
return failure('Furnace jar.mn registrations', `Could not prune stale jar.mn lines: ${toError(err).message}`, 'Remove the stale lines from toolkit/content/jar.mn manually and retry.');
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
//# sourceMappingURL=doctor-furnace-jar.js.map
|
|
@@ -10,6 +10,7 @@ import { validateAllComponents } from '../core/furnace-validate.js';
|
|
|
10
10
|
import { toError } from '../utils/errors.js';
|
|
11
11
|
import { pathExists } from '../utils/fs.js';
|
|
12
12
|
import { failure, ok, warning } from './doctor-check-core.js';
|
|
13
|
+
import { furnaceStaleJarRegistrationCheck } from './doctor-furnace-jar.js';
|
|
13
14
|
import { furnaceManifestSyncCheck } from './doctor-furnace-manifest-sync.js';
|
|
14
15
|
const ENGINE_REPAIRABLE_OPERATIONS = [
|
|
15
16
|
'preview-teardown',
|
|
@@ -503,6 +504,7 @@ const furnaceStaleLockCheck = {
|
|
|
503
504
|
export const FURNACE_DOCTOR_CHECKS = [
|
|
504
505
|
furnaceConfigurationCheck,
|
|
505
506
|
furnaceStateConsistencyCheck,
|
|
507
|
+
furnaceStaleJarRegistrationCheck,
|
|
506
508
|
furnaceEnginePathsCheck,
|
|
507
509
|
furnaceStorybookCheck,
|
|
508
510
|
furnaceStaleLockCheck,
|
|
@@ -227,7 +227,10 @@ export async function commitPlacementExport(input) {
|
|
|
227
227
|
}
|
|
228
228
|
const conflicts = await projectPlacementForLint(input.patchesDir, currentPlan, input.diff);
|
|
229
229
|
if (conflicts && input.unsafeOverride !== true) {
|
|
230
|
-
throw new InvalidArgumentError(`Refusing to run export: ${conflicts.reason}.
|
|
230
|
+
throw new InvalidArgumentError(`Refusing to run export: ${conflicts.reason}. ` +
|
|
231
|
+
'If the conflict names files owned by another patch (e.g. duplicate-new-file-creation), ' +
|
|
232
|
+
're-run the export with an explicit file list that leaves those files with their owner — ' +
|
|
233
|
+
'do NOT bypass with --force-unsafe. Pass --force-unsafe only for a reviewed placement conflict.', '--force-unsafe');
|
|
231
234
|
}
|
|
232
235
|
const originalManifest = await loadPatchesManifest(input.patchesDir);
|
|
233
236
|
if (originalManifest !== null) {
|
|
@@ -98,7 +98,10 @@ export async function gatePlacementPlan(args) {
|
|
|
98
98
|
}
|
|
99
99
|
else if (conflicts && options.forceUnsafe !== true) {
|
|
100
100
|
s.stop();
|
|
101
|
-
throw new InvalidArgumentError(`Refusing to run export: ${conflicts.reason}.
|
|
101
|
+
throw new InvalidArgumentError(`Refusing to run export: ${conflicts.reason}. ` +
|
|
102
|
+
'If the conflict names files owned by another patch (e.g. duplicate-new-file-creation), ' +
|
|
103
|
+
're-run the export with an explicit file list that leaves those files with their owner — ' +
|
|
104
|
+
'do NOT bypass with --force-unsafe. Pass --force-unsafe only for a reviewed placement conflict.', '--force-unsafe');
|
|
102
105
|
}
|
|
103
106
|
return placementPlan;
|
|
104
107
|
}
|
|
@@ -12,6 +12,7 @@ import { getModifiedFilesInDir, getUntrackedFiles, getUntrackedFilesInDir, } fro
|
|
|
12
12
|
import { extractAffectedFiles } from '../core/patch-apply.js';
|
|
13
13
|
import { commitExportedPatch } from '../core/patch-export.js';
|
|
14
14
|
import { buildPatchQueueContext } from '../core/patch-lint.js';
|
|
15
|
+
import { loadPatchesManifest } from '../core/patch-manifest.js';
|
|
15
16
|
import { buildPatchSourceMetadata } from '../core/patch-source-metadata.js';
|
|
16
17
|
import { GeneralError, InvalidArgumentError } from '../errors/base.js';
|
|
17
18
|
import { toError } from '../utils/errors.js';
|
|
@@ -25,6 +26,7 @@ import { gatePlacementPlan, patchMetadataExtras } from './export-placement-gate.
|
|
|
25
26
|
import { autoFixLicenseHeaders, promptExportPatchMetadata, runPatchLint, runSupersedeAndOverlapGates, } from './export-shared.js';
|
|
26
27
|
async function collectExportFiles(paths, files) {
|
|
27
28
|
const collectedFiles = new Set();
|
|
29
|
+
const fromDirectory = new Set();
|
|
28
30
|
let fileStatuses;
|
|
29
31
|
let untrackedFiles;
|
|
30
32
|
// Accept both repo-root-relative (`engine/browser/...`) and engine-relative
|
|
@@ -49,10 +51,14 @@ async function collectExportFiles(paths, files) {
|
|
|
49
51
|
const dirPath = inputPath.endsWith('/') ? inputPath.slice(0, -1) : inputPath;
|
|
50
52
|
const modifiedFiles = await getModifiedFilesInDir(paths.engine, dirPath);
|
|
51
53
|
const dirUntrackedFiles = await getUntrackedFilesInDir(paths.engine, dirPath);
|
|
52
|
-
for (const f of modifiedFiles)
|
|
54
|
+
for (const f of modifiedFiles) {
|
|
53
55
|
collectedFiles.add(f);
|
|
54
|
-
|
|
56
|
+
fromDirectory.add(f);
|
|
57
|
+
}
|
|
58
|
+
for (const f of dirUntrackedFiles) {
|
|
55
59
|
collectedFiles.add(f);
|
|
60
|
+
fromDirectory.add(f);
|
|
61
|
+
}
|
|
56
62
|
}
|
|
57
63
|
else {
|
|
58
64
|
if (inputPath.endsWith('/')) {
|
|
@@ -72,9 +78,53 @@ async function collectExportFiles(paths, files) {
|
|
|
72
78
|
}
|
|
73
79
|
}
|
|
74
80
|
collectedFiles.add(inputPath);
|
|
81
|
+
// A file named explicitly always stays in the export set, even if a
|
|
82
|
+
// directory argument also swept it up.
|
|
83
|
+
fromDirectory.delete(inputPath);
|
|
75
84
|
}
|
|
76
85
|
}
|
|
77
|
-
return [...collectedFiles].sort();
|
|
86
|
+
return { files: [...collectedFiles].sort(), fromDirectory };
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Auto-excludes directory-derived files already owned by OTHER patches
|
|
90
|
+
* (0.34.0 field report): a directory export used to plan files owned by
|
|
91
|
+
* earlier patches into the new patch, and the duplicate-new-file-creation
|
|
92
|
+
* refusal surfaced only at placement lint, suggesting --force-unsafe —
|
|
93
|
+
* the wrong tool for "leave that file with its owner". Explicitly named
|
|
94
|
+
* files are never excluded (the overlap gates still confront the operator
|
|
95
|
+
* with those). Prints one notice per exclusion.
|
|
96
|
+
*/
|
|
97
|
+
async function excludeFilesOwnedByOtherPatches(patchesDir, collected) {
|
|
98
|
+
if (collected.fromDirectory.size === 0)
|
|
99
|
+
return collected.files;
|
|
100
|
+
const manifest = await loadPatchesManifest(patchesDir);
|
|
101
|
+
if (!manifest)
|
|
102
|
+
return collected.files;
|
|
103
|
+
const owners = new Map();
|
|
104
|
+
for (const patch of manifest.patches) {
|
|
105
|
+
for (const file of patch.filesAffected) {
|
|
106
|
+
if (!owners.has(file))
|
|
107
|
+
owners.set(file, patch.filename);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
const kept = [];
|
|
111
|
+
let excludedCount = 0;
|
|
112
|
+
for (const file of collected.files) {
|
|
113
|
+
const owner = collected.fromDirectory.has(file) ? owners.get(file) : undefined;
|
|
114
|
+
if (owner !== undefined) {
|
|
115
|
+
info(`Excluding ${file} from the directory export (owned by ${owner})`);
|
|
116
|
+
excludedCount += 1;
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
kept.push(file);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
if (excludedCount > 0) {
|
|
123
|
+
info(`Excluded ${excludedCount} file${excludedCount === 1 ? '' : 's'} owned by other patches. ` +
|
|
124
|
+
'Use "fireforge re-export <patch> --files ..." to update the owning patch, or name the ' +
|
|
125
|
+
'file explicitly in this export to move it deliberately.');
|
|
126
|
+
}
|
|
127
|
+
return kept;
|
|
78
128
|
}
|
|
79
129
|
async function generatePatchDiff(engineDir, allFiles) {
|
|
80
130
|
const diffs = [];
|
|
@@ -125,7 +175,9 @@ async function prepareExport(projectRoot, files, options) {
|
|
|
125
175
|
if (!(await isGitRepository(paths.engine))) {
|
|
126
176
|
throw new GeneralError('Engine directory is not a git repository. Run "fireforge download" to initialize.');
|
|
127
177
|
}
|
|
128
|
-
|
|
178
|
+
const collected = await collectExportFiles(paths, files);
|
|
179
|
+
let allFiles = await excludeFilesOwnedByOtherPatches(paths.patches, collected);
|
|
180
|
+
const ownershipExclusions = collected.files.length - allFiles.length;
|
|
129
181
|
// Filter out furnace-managed files when --exclude-furnace is set
|
|
130
182
|
if (options.excludeFurnace) {
|
|
131
183
|
const furnacePrefixes = await collectFurnaceManagedPrefixes(projectRoot);
|
|
@@ -140,6 +192,11 @@ async function prepareExport(projectRoot, files, options) {
|
|
|
140
192
|
}
|
|
141
193
|
if (allFiles.length === 0) {
|
|
142
194
|
const pathList = files.join(', ');
|
|
195
|
+
if (ownershipExclusions > 0) {
|
|
196
|
+
throw new GeneralError(`Every changed file under "${pathList}" is already owned by another patch ` +
|
|
197
|
+
'(see the exclusions above).\n\n' +
|
|
198
|
+
'Use "fireforge re-export <patch> --files ..." to refresh the owning patch instead.');
|
|
199
|
+
}
|
|
143
200
|
throw new GeneralError(`Paths "${pathList}" have no changes to export.\n\n` +
|
|
144
201
|
'Run "fireforge status" to see modified files.');
|
|
145
202
|
}
|
|
@@ -40,6 +40,26 @@
|
|
|
40
40
|
* the README "Platform module compatibility" section for the pattern.
|
|
41
41
|
*/
|
|
42
42
|
export declare function generateChromeDocXhtml(name: string, withTitlebar: boolean, license: string): string;
|
|
43
|
+
/**
|
|
44
|
+
* browser.xhtml-like scaffold for the document that ships as the fork's
|
|
45
|
+
* MAIN BROWSER WINDOW (0.34.0 field report): the generic dialog-shaped
|
|
46
|
+
* `<window>` scaffold was wrong for the `tokenHostDocuments[0]` /
|
|
47
|
+
* BROWSER_CHROME_URL target — platform C++ reads the root element BEFORE
|
|
48
|
+
* any script runs, and expects the `browser.xhtml` shape:
|
|
49
|
+
*
|
|
50
|
+
* - `<html id="main-window">` root (not `<window id="<name>-window">`) —
|
|
51
|
+
* upstream code from nsXULWindow sizing to session restore looks up
|
|
52
|
+
* `main-window` by id;
|
|
53
|
+
* - `windowtype="navigator:browser"`, `chromehidden=""` and the geometry
|
|
54
|
+
* `persist` allowlist declared as ROOT ATTRIBUTES so the platform's
|
|
55
|
+
* pre-script pass (window tracking, XULStore geometry, chrome flags)
|
|
56
|
+
* sees them;
|
|
57
|
+
* - the same head/bootstrap wiring (customElements.js, per-doc subscript,
|
|
58
|
+
* CSS + Fluent links) and `data-furnace-chrome-doc` sentinel as the
|
|
59
|
+
* generic scaffold, so jar.mn registration and the platform-module
|
|
60
|
+
* guard pattern are unchanged.
|
|
61
|
+
*/
|
|
62
|
+
export declare function generateBrowserWindowXhtml(name: string, license: string): string;
|
|
43
63
|
/**
|
|
44
64
|
* ESM bootstrap script for the chrome document.
|
|
45
65
|
*
|
|
@@ -98,6 +98,58 @@ export function generateChromeDocXhtml(name, withTitlebar, license) {
|
|
|
98
98
|
</window>
|
|
99
99
|
`;
|
|
100
100
|
}
|
|
101
|
+
/**
|
|
102
|
+
* browser.xhtml-like scaffold for the document that ships as the fork's
|
|
103
|
+
* MAIN BROWSER WINDOW (0.34.0 field report): the generic dialog-shaped
|
|
104
|
+
* `<window>` scaffold was wrong for the `tokenHostDocuments[0]` /
|
|
105
|
+
* BROWSER_CHROME_URL target — platform C++ reads the root element BEFORE
|
|
106
|
+
* any script runs, and expects the `browser.xhtml` shape:
|
|
107
|
+
*
|
|
108
|
+
* - `<html id="main-window">` root (not `<window id="<name>-window">`) —
|
|
109
|
+
* upstream code from nsXULWindow sizing to session restore looks up
|
|
110
|
+
* `main-window` by id;
|
|
111
|
+
* - `windowtype="navigator:browser"`, `chromehidden=""` and the geometry
|
|
112
|
+
* `persist` allowlist declared as ROOT ATTRIBUTES so the platform's
|
|
113
|
+
* pre-script pass (window tracking, XULStore geometry, chrome flags)
|
|
114
|
+
* sees them;
|
|
115
|
+
* - the same head/bootstrap wiring (customElements.js, per-doc subscript,
|
|
116
|
+
* CSS + Fluent links) and `data-furnace-chrome-doc` sentinel as the
|
|
117
|
+
* generic scaffold, so jar.mn registration and the platform-module
|
|
118
|
+
* guard pattern are unchanged.
|
|
119
|
+
*/
|
|
120
|
+
export function generateBrowserWindowXhtml(name, license) {
|
|
121
|
+
return `<?xml version="1.0"?>
|
|
122
|
+
<!-- SPDX-License-Identifier: ${license} -->
|
|
123
|
+
<html
|
|
124
|
+
xmlns="http://www.w3.org/1999/xhtml"
|
|
125
|
+
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
|
126
|
+
id="main-window"
|
|
127
|
+
windowtype="navigator:browser"
|
|
128
|
+
customtitlebar="true"
|
|
129
|
+
chromehidden=""
|
|
130
|
+
width="1024"
|
|
131
|
+
height="640"
|
|
132
|
+
persist="screenX screenY width height sizemode"
|
|
133
|
+
${FURNACE_CHROME_DOC_SENTINEL}="${name}"
|
|
134
|
+
role="application">
|
|
135
|
+
<head>
|
|
136
|
+
<meta charset="utf-8" />
|
|
137
|
+
<title data-l10n-id="${name}-window-title"></title>
|
|
138
|
+
<link rel="localization" href="browser/${name}.ftl" />
|
|
139
|
+
<link rel="stylesheet" href="chrome://global/skin/global.css" />
|
|
140
|
+
<link rel="stylesheet" href="chrome://browser/content/${name}-chrome.css" />
|
|
141
|
+
<script src="chrome://global/content/customElements.js"></script>
|
|
142
|
+
<script src="chrome://browser/content/${name}.js"></script>
|
|
143
|
+
</head>
|
|
144
|
+
<body>
|
|
145
|
+
<hbox class="titlebar-buttonbox-container">
|
|
146
|
+
<hbox class="titlebar-buttonbox"></hbox>
|
|
147
|
+
</hbox>
|
|
148
|
+
<main id="${name}-main"></main>
|
|
149
|
+
</body>
|
|
150
|
+
</html>
|
|
151
|
+
`;
|
|
152
|
+
}
|
|
101
153
|
/**
|
|
102
154
|
* ESM bootstrap script for the chrome document.
|
|
103
155
|
*
|
|
@@ -37,6 +37,16 @@ export interface FurnaceChromeDocCreateOptions {
|
|
|
37
37
|
withTests?: boolean;
|
|
38
38
|
/** Print the scaffold plan without writing files. */
|
|
39
39
|
dryRun?: boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Emit the browser.xhtml-like MAIN-WINDOW skeleton (`<html
|
|
42
|
+
* id="main-window">` with the `windowtype`/`chromehidden`/`persist`
|
|
43
|
+
* root attributes platform C++ reads before scripts run) instead of
|
|
44
|
+
* the generic dialog-shaped `<window>` document. Use for the document
|
|
45
|
+
* configured as the fork's main browser window
|
|
46
|
+
* (`tokenHostDocuments[0]` / the BROWSER_CHROME_URL target). Implies
|
|
47
|
+
* the titlebar markup.
|
|
48
|
+
*/
|
|
49
|
+
browserWindow?: boolean;
|
|
40
50
|
}
|
|
41
51
|
/**
|
|
42
52
|
* Validates a chrome-doc name. Lowercase ASCII, optional hyphens, no
|
|
@@ -18,14 +18,15 @@
|
|
|
18
18
|
*/
|
|
19
19
|
import { join } from 'node:path';
|
|
20
20
|
import { loadConfig } from '../../core/config.js';
|
|
21
|
+
import { furnaceConfigExists, loadFurnaceConfig } from '../../core/furnace-config.js';
|
|
21
22
|
import { runFurnaceMutation } from '../../core/furnace-operation.js';
|
|
22
23
|
import { createRollbackJournal, recordCreatedDir, restoreRollbackJournalOrThrow, snapshotFile, } from '../../core/furnace-rollback.js';
|
|
23
24
|
import { DEFAULT_LICENSE, getLicenseHeader } from '../../core/license-headers.js';
|
|
24
25
|
import { InvalidArgumentError } from '../../errors/base.js';
|
|
25
26
|
import { FurnaceError } from '../../errors/furnace.js';
|
|
26
27
|
import { pathExists, readText, writeText } from '../../utils/fs.js';
|
|
27
|
-
import { intro, note, outro } from '../../utils/logger.js';
|
|
28
|
-
import { generateChromeDocCss, generateChromeDocFtl, generateChromeDocJs, generateChromeDocXhtml, jarIncMnEntryForChromeDoc, jarMnEntriesForChromeDoc, localeJarMnEntryForChromeDoc, localesFtlWildcardCapturesScaffoldedName, } from './chrome-doc-templates.js';
|
|
28
|
+
import { intro, note, outro, warn } from '../../utils/logger.js';
|
|
29
|
+
import { generateBrowserWindowXhtml, generateChromeDocCss, generateChromeDocFtl, generateChromeDocJs, generateChromeDocXhtml, jarIncMnEntryForChromeDoc, jarMnEntriesForChromeDoc, localeJarMnEntryForChromeDoc, localesFtlWildcardCapturesScaffoldedName, } from './chrome-doc-templates.js';
|
|
29
30
|
import { chromeDocPackagingTestFileName, generateChromeDocPackagingManifest, generateChromeDocPackagingTest, } from './chrome-doc-tests.js';
|
|
30
31
|
/** Chrome-doc name shape: lowercase ASCII, optional hyphens, no leading digit. */
|
|
31
32
|
const CHROME_DOC_NAME_PATTERN = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
|
|
@@ -196,7 +197,9 @@ async function performChromeDocMutations(args) {
|
|
|
196
197
|
throw new FurnaceError(`${args.name}.xhtml already exists at ${xhtmlPath}. Remove it or choose a different name.`);
|
|
197
198
|
}
|
|
198
199
|
await snapshotFile(journal, xhtmlPath);
|
|
199
|
-
await writeText(xhtmlPath,
|
|
200
|
+
await writeText(xhtmlPath, args.browserWindow
|
|
201
|
+
? generateBrowserWindowXhtml(args.name, args.license)
|
|
202
|
+
: generateChromeDocXhtml(args.name, args.withTitlebar, args.license));
|
|
200
203
|
written.push(`browser/base/content/${args.name}.xhtml`);
|
|
201
204
|
const jsPath = join(contentDir, `${args.name}.js`);
|
|
202
205
|
await snapshotFile(journal, jsPath);
|
|
@@ -290,8 +293,31 @@ export async function furnaceChromeDocCreateCommand(projectRoot, name, options =
|
|
|
290
293
|
if (!(await pathExists(engineDir))) {
|
|
291
294
|
throw new FurnaceError('Engine directory not found. Run "fireforge download" first to scaffold a chrome-doc.');
|
|
292
295
|
}
|
|
293
|
-
const
|
|
296
|
+
const browserWindow = options.browserWindow ?? false;
|
|
297
|
+
// The browser-window skeleton always carries its own titlebar markup and
|
|
298
|
+
// chrome attributes; --no-titlebar only applies to the generic scaffold.
|
|
299
|
+
const withTitlebar = browserWindow ? true : (options.titlebar ?? true);
|
|
294
300
|
const withTests = options.withTests ?? false;
|
|
301
|
+
// Hint: when the scaffolded document is the configured token-host
|
|
302
|
+
// document (the fork's main browser window), the generic dialog-shaped
|
|
303
|
+
// scaffold is almost certainly wrong (0.34.0 field report: correct
|
|
304
|
+
// jar.mn registrations, wrong document body).
|
|
305
|
+
if (!browserWindow && (await furnaceConfigExists(projectRoot))) {
|
|
306
|
+
try {
|
|
307
|
+
const furnaceConfig = await loadFurnaceConfig(projectRoot);
|
|
308
|
+
const targetDocPath = `browser/base/content/${name}.xhtml`;
|
|
309
|
+
if (furnaceConfig.tokenHostDocuments?.includes(targetDocPath)) {
|
|
310
|
+
warn(`${targetDocPath} is a configured token-host document (the fork's main browser window). ` +
|
|
311
|
+
'The default scaffold emits a generic dialog-shaped <window> document; you probably ' +
|
|
312
|
+
'want "fireforge furnace chrome-doc create --browser-window" for the ' +
|
|
313
|
+
'<html id="main-window"> skeleton platform C++ expects.');
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
catch {
|
|
317
|
+
// A broken furnace.json must not block chrome-doc scaffolding; the
|
|
318
|
+
// hint is best-effort.
|
|
319
|
+
}
|
|
320
|
+
}
|
|
295
321
|
const plan = await buildChromeDocPlan({
|
|
296
322
|
engineDir,
|
|
297
323
|
name,
|
|
@@ -310,6 +336,7 @@ export async function furnaceChromeDocCreateCommand(projectRoot, name, options =
|
|
|
310
336
|
engineDir,
|
|
311
337
|
withTitlebar,
|
|
312
338
|
withTests,
|
|
339
|
+
browserWindow,
|
|
313
340
|
binaryName: forgeConfig.binaryName,
|
|
314
341
|
operationContext: ctx,
|
|
315
342
|
}));
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser-chrome test scaffolding for `furnace create --with-tests`,
|
|
3
|
+
* including the 0.34.0 `--test-dir` redirect and collision safety
|
|
4
|
+
* (existing manifests are appended to; head.js and test implementations
|
|
5
|
+
* are never overwritten). Split out of `create.ts` to keep that command
|
|
6
|
+
* file within the per-file line budget.
|
|
7
|
+
*/
|
|
8
|
+
import { type RollbackJournal } from '../../core/furnace-rollback.js';
|
|
9
|
+
import type { ProjectLicense } from '../../types/config.js';
|
|
10
|
+
import type { ResolvedTestStyle } from '../../types/furnace.js';
|
|
11
|
+
/**
|
|
12
|
+
* Validates the `--test-dir` option against the resolved test style before
|
|
13
|
+
* any writes. Mochikit lives in the upstream toolkit/content/tests/widgets
|
|
14
|
+
* tree, so a redirect makes no sense there.
|
|
15
|
+
*/
|
|
16
|
+
export declare function resolveValidatedTestDir(rawTestDir: string | undefined, testStyle: ResolvedTestStyle): string | undefined;
|
|
17
|
+
/**
|
|
18
|
+
* Normalizes and validates a `--test-dir` override: engine-relative,
|
|
19
|
+
* under `browser/base/content/test/` (so manifest registration keeps
|
|
20
|
+
* working). Returns the normalized engine-relative directory.
|
|
21
|
+
*/
|
|
22
|
+
export declare function resolveTestDirOverride(raw: string): string;
|
|
23
|
+
/**
|
|
24
|
+
*
|
|
25
|
+
*/
|
|
26
|
+
export declare function scaffoldTestFiles(componentName: string, license: ProjectLicense, forgeConfig: {
|
|
27
|
+
binaryName: string;
|
|
28
|
+
}, paths: {
|
|
29
|
+
engine: string;
|
|
30
|
+
}, journal?: RollbackJournal, testDirOverride?: string): Promise<string[]>;
|