@hominis/fireforge 0.40.0 → 0.41.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 +79 -0
- package/README.md +16 -10
- package/dist/bin/fireforge.js +25 -4
- package/dist/build-info.json +9 -0
- package/dist/src/cli.js +20 -3
- package/dist/src/commands/bootstrap.js +5 -1
- package/dist/src/commands/discard.js +2 -22
- package/dist/src/commands/doctor/post-rebase-audit.js +27 -4
- package/dist/src/commands/doctor-check-core.d.ts +20 -0
- package/dist/src/commands/doctor-check-core.js +28 -0
- package/dist/src/commands/doctor-external-toolchains.js +2 -0
- package/dist/src/commands/doctor-furnace-manifest-sync.js +144 -46
- package/dist/src/commands/doctor-furnace.js +13 -11
- package/dist/src/commands/doctor-orphaned-harness.js +2 -1
- package/dist/src/commands/doctor.js +2 -2
- package/dist/src/commands/export-all.js +9 -1
- package/dist/src/commands/export-flow.js +13 -3
- package/dist/src/commands/export-placement-conflicts.d.ts +17 -0
- package/dist/src/commands/export-placement-conflicts.js +74 -0
- package/dist/src/commands/export-placement-gate.js +1 -1
- package/dist/src/commands/export-shared.d.ts +8 -1
- package/dist/src/commands/export-shared.js +16 -7
- package/dist/src/commands/export.js +2 -2
- package/dist/src/commands/furnace/apply.js +4 -3
- package/dist/src/commands/furnace/chrome-doc-remove.js +13 -2
- package/dist/src/commands/furnace/chrome-doc.js +13 -2
- package/dist/src/commands/furnace/create-browser-test.d.ts +5 -0
- package/dist/src/commands/furnace/create-browser-test.js +5 -0
- package/dist/src/commands/furnace/create.js +2 -2
- package/dist/src/commands/furnace/diff.js +2 -1
- package/dist/src/commands/furnace/init.js +4 -6
- package/dist/src/commands/furnace/override.js +2 -2
- package/dist/src/commands/furnace/remove-mutations.d.ts +40 -0
- package/dist/src/commands/furnace/remove-mutations.js +171 -0
- package/dist/src/commands/furnace/remove.js +21 -128
- package/dist/src/commands/furnace/rename-browser-test.js +54 -5
- package/dist/src/commands/furnace/scan.js +27 -36
- package/dist/src/commands/furnace/status.js +30 -28
- package/dist/src/commands/furnace/validate.js +25 -18
- package/dist/src/commands/import.js +6 -20
- package/dist/src/commands/lint-per-patch.js +12 -59
- package/dist/src/commands/lint-per-run-checkjs.d.ts +64 -0
- package/dist/src/commands/lint-per-run-checkjs.js +102 -0
- package/dist/src/commands/lint.js +4 -4
- package/dist/src/commands/manifest.js +1 -1
- package/dist/src/commands/package.js +9 -16
- package/dist/src/commands/patch/compact.js +5 -5
- package/dist/src/commands/patch/delete.js +5 -5
- package/dist/src/commands/patch/lint-ignore.js +3 -2
- package/dist/src/commands/patch/move-files-create.js +22 -4
- package/dist/src/commands/patch/move-files-into.js +14 -10
- package/dist/src/commands/patch/move-files.js +6 -5
- package/dist/src/commands/patch/rename.d.ts +2 -2
- package/dist/src/commands/patch/rename.js +151 -50
- package/dist/src/commands/patch/reorder.d.ts +9 -0
- package/dist/src/commands/patch/reorder.js +14 -10
- package/dist/src/commands/patch/split-plan.d.ts +13 -6
- package/dist/src/commands/patch/split-plan.js +22 -12
- package/dist/src/commands/patch/split.d.ts +1 -1
- package/dist/src/commands/patch/split.js +23 -9
- package/dist/src/commands/patch/staged-dependency-validate.d.ts +15 -0
- package/dist/src/commands/patch/staged-dependency-validate.js +49 -0
- package/dist/src/commands/patch/staged-dependency.js +10 -0
- package/dist/src/commands/re-export-adjacent.js +5 -6
- package/dist/src/commands/re-export-bulk-scan.d.ts +13 -0
- package/dist/src/commands/re-export-bulk-scan.js +142 -2
- package/dist/src/commands/re-export-drift.d.ts +74 -0
- package/dist/src/commands/re-export-drift.js +234 -0
- package/dist/src/commands/re-export-files.js +5 -3
- package/dist/src/commands/re-export-lint.d.ts +97 -0
- package/dist/src/commands/re-export-lint.js +210 -0
- package/dist/src/commands/re-export-options.js +8 -0
- package/dist/src/commands/re-export-register.d.ts +18 -0
- package/dist/src/commands/re-export-register.js +61 -0
- package/dist/src/commands/re-export-scan.js +13 -7
- package/dist/src/commands/re-export-single.d.ts +20 -0
- package/dist/src/commands/re-export-single.js +261 -0
- package/dist/src/commands/re-export.d.ts +0 -4
- package/dist/src/commands/re-export.js +113 -268
- package/dist/src/commands/rebase/abort.js +13 -4
- package/dist/src/commands/rebase/continue.js +11 -4
- package/dist/src/commands/rebase/index.js +19 -29
- package/dist/src/commands/rebase/patch-loop.js +12 -0
- package/dist/src/commands/reset.js +4 -23
- package/dist/src/commands/resolve.js +2 -9
- package/dist/src/commands/run.js +11 -21
- package/dist/src/commands/source.js +3 -9
- package/dist/src/commands/status-check.d.ts +13 -0
- package/dist/src/commands/status-check.js +31 -13
- package/dist/src/commands/status-json.d.ts +31 -0
- package/dist/src/commands/status-json.js +76 -0
- package/dist/src/commands/status-output.d.ts +1 -0
- package/dist/src/commands/status-output.js +10 -1
- package/dist/src/commands/status-ownership.d.ts +52 -0
- package/dist/src/commands/status-ownership.js +57 -0
- package/dist/src/commands/status.js +184 -175
- package/dist/src/commands/test-build-phase.d.ts +19 -0
- package/dist/src/commands/test-build-phase.js +168 -0
- package/dist/src/commands/test-diagnose.js +30 -2
- package/dist/src/commands/test-modes.d.ts +9 -1
- package/dist/src/commands/test-modes.js +46 -13
- package/dist/src/commands/test-register.js +59 -27
- package/dist/src/commands/test-run.d.ts +29 -3
- package/dist/src/commands/test-run.js +41 -4
- package/dist/src/commands/test-verdict.d.ts +32 -0
- package/dist/src/commands/test-verdict.js +48 -0
- package/dist/src/commands/test.d.ts +9 -0
- package/dist/src/commands/test.js +100 -123
- package/dist/src/commands/token-coverage.js +4 -9
- package/dist/src/commands/tree.d.ts +3 -0
- package/dist/src/commands/tree.js +171 -25
- package/dist/src/commands/verify.js +2 -2
- package/dist/src/commands/watch.js +22 -21
- package/dist/src/commands/wire.js +1 -1
- package/dist/src/core/ast-utils.d.ts +11 -0
- package/dist/src/core/ast-utils.js +11 -0
- package/dist/src/core/build-audit-platform.js +6 -1
- package/dist/src/core/build-audit-registration.js +2 -0
- package/dist/src/core/build-audit-resolve.d.ts +13 -0
- package/dist/src/core/build-audit-resolve.js +34 -13
- package/dist/src/core/build-audit.js +4 -20
- package/dist/src/core/build-baseline-types.d.ts +29 -8
- package/dist/src/core/build-baseline-types.js +2 -1
- package/dist/src/core/build-baseline.d.ts +10 -2
- package/dist/src/core/build-baseline.js +39 -8
- package/dist/src/core/build-prepare.d.ts +4 -0
- package/dist/src/core/build-prepare.js +7 -1
- package/dist/src/core/config-paths.d.ts +1 -1
- package/dist/src/core/config-paths.js +2 -0
- package/dist/src/core/config-validate-test-toolchains.js +3 -2
- package/dist/src/core/config-validate.js +34 -36
- package/dist/src/core/config.js +16 -3
- package/dist/src/core/coverage-extend.d.ts +89 -0
- package/dist/src/core/coverage-extend.js +188 -0
- package/dist/src/core/destructive.js +3 -1
- package/dist/src/core/discard-baseline.js +18 -4
- package/dist/src/core/engine-precondition.d.ts +35 -0
- package/dist/src/core/engine-precondition.js +50 -0
- package/dist/src/core/engine-session-lock.d.ts +38 -1
- package/dist/src/core/engine-session-lock.js +78 -4
- package/dist/src/core/file-lock.d.ts +7 -0
- package/dist/src/core/file-lock.js +20 -30
- package/dist/src/core/firefox.js +5 -2
- package/dist/src/core/furnace-apply-helpers.d.ts +12 -1
- package/dist/src/core/furnace-apply-helpers.js +12 -3
- package/dist/src/core/furnace-apply-output.js +9 -0
- package/dist/src/core/furnace-apply-overwrite-warn.d.ts +63 -0
- package/dist/src/core/furnace-apply-overwrite-warn.js +123 -0
- package/dist/src/core/furnace-apply-undeploy-actions.d.ts +11 -0
- package/dist/src/core/furnace-apply-undeploy-actions.js +49 -0
- package/dist/src/core/furnace-apply.d.ts +16 -4
- package/dist/src/core/furnace-apply.js +39 -43
- package/dist/src/core/furnace-config.js +2 -0
- package/dist/src/core/furnace-css-fragments.js +2 -0
- package/dist/src/core/furnace-jsconfig.js +14 -7
- package/dist/src/core/furnace-registration-ast.js +8 -8
- package/dist/src/core/furnace-registration-remove.js +6 -3
- package/dist/src/core/furnace-registration.js +9 -12
- package/dist/src/core/furnace-rollback.js +1 -1
- package/dist/src/core/furnace-scanner.js +2 -1
- package/dist/src/core/furnace-stale-export.js +2 -1
- package/dist/src/core/furnace-state-persist.d.ts +4 -6
- package/dist/src/core/furnace-validate-compatibility.js +4 -7
- package/dist/src/core/furnace-validate-helpers.js +33 -1
- package/dist/src/core/furnace-validate-registration.js +16 -0
- package/dist/src/core/furnace-validate.js +4 -2
- package/dist/src/core/git-diff.js +37 -2
- package/dist/src/core/lint-cache.d.ts +1 -1
- package/dist/src/core/lint-cache.js +33 -10
- package/dist/src/core/mach-build-artifacts.d.ts +63 -1
- package/dist/src/core/mach-build-artifacts.js +108 -2
- package/dist/src/core/mach-resource-shim.js +1 -0
- package/dist/src/core/marionette-port.d.ts +16 -0
- package/dist/src/core/marionette-port.js +93 -8
- package/dist/src/core/marionette-preflight.js +6 -5
- package/dist/src/core/patch-apply.d.ts +20 -13
- package/dist/src/core/patch-apply.js +32 -33
- package/dist/src/core/patch-export.js +3 -5
- package/dist/src/core/patch-lint-checkjs.d.ts +25 -3
- package/dist/src/core/patch-lint-checkjs.js +78 -7
- package/dist/src/core/patch-lint-chrome-jsdoc.js +9 -0
- package/dist/src/core/patch-lint-creators.d.ts +30 -0
- package/dist/src/core/patch-lint-creators.js +45 -0
- package/dist/src/core/patch-lint-cross.d.ts +1 -17
- package/dist/src/core/patch-lint-cross.js +16 -32
- package/dist/src/core/patch-lint-jsdoc.d.ts +3 -1
- package/dist/src/core/patch-lint-jsdoc.js +15 -5
- package/dist/src/core/patch-lint-module-registration.d.ts +15 -0
- package/dist/src/core/patch-lint-module-registration.js +88 -0
- package/dist/src/core/patch-lint-mozbuild.js +99 -11
- package/dist/src/core/patch-lint-reexports.d.ts +1 -0
- package/dist/src/core/patch-lint-reexports.js +1 -0
- package/dist/src/core/patch-lint.d.ts +8 -0
- package/dist/src/core/patch-lint.js +84 -6
- package/dist/src/core/patch-lock.d.ts +12 -1
- package/dist/src/core/patch-lock.js +40 -4
- package/dist/src/core/patch-manifest-consistency.js +15 -3
- package/dist/src/core/patch-parse.d.ts +8 -0
- package/dist/src/core/patch-parse.js +10 -0
- package/dist/src/core/patch-policy.d.ts +7 -0
- package/dist/src/core/patch-policy.js +4 -1
- package/dist/src/core/patch-transform.d.ts +10 -0
- package/dist/src/core/patch-transform.js +13 -2
- package/dist/src/core/rebase-session.d.ts +44 -2
- package/dist/src/core/rebase-session.js +114 -17
- package/dist/src/core/register-browser-content.js +2 -1
- package/dist/src/core/register-shared-css.js +2 -1
- package/dist/src/core/smoke-patterns.d.ts +0 -8
- package/dist/src/core/smoke-patterns.js +3 -2
- package/dist/src/core/status-binary.d.ts +55 -0
- package/dist/src/core/status-binary.js +124 -0
- package/dist/src/core/status-classify.d.ts +6 -1
- package/dist/src/core/status-classify.js +101 -73
- package/dist/src/core/test-harness-crash.d.ts +32 -1
- package/dist/src/core/test-harness-crash.js +59 -4
- package/dist/src/core/test-path-scope.js +2 -0
- package/dist/src/core/test-stale-check.js +1 -20
- package/dist/src/core/toolchain-preflight.js +24 -26
- package/dist/src/core/tree-guard.d.ts +1 -1
- package/dist/src/core/tree-guard.js +67 -12
- package/dist/src/core/tree-store.d.ts +86 -10
- package/dist/src/core/tree-store.js +310 -72
- package/dist/src/core/typecheck.js +2 -1
- package/dist/src/core/wire-destroy.js +14 -3
- package/dist/src/core/wire-init.js +37 -40
- package/dist/src/core/wire-subscript.js +11 -3
- package/dist/src/core/wire-utils.js +4 -4
- package/dist/src/core/xpcshell-appdir.js +3 -0
- package/dist/src/errors/base.d.ts +13 -0
- package/dist/src/errors/base.js +13 -0
- package/dist/src/errors/rebase.d.ts +13 -0
- package/dist/src/errors/rebase.js +17 -0
- package/dist/src/types/commands/options.d.ts +86 -1
- package/dist/src/types/commands/patches.d.ts +8 -0
- package/dist/src/types/furnace.d.ts +7 -0
- package/dist/src/utils/build-info.d.ts +31 -0
- package/dist/src/utils/build-info.js +98 -0
- package/dist/src/utils/concurrency.d.ts +8 -0
- package/dist/src/utils/concurrency.js +21 -0
- package/dist/src/utils/errors.d.ts +39 -0
- package/dist/src/utils/errors.js +55 -0
- package/dist/src/utils/fs.js +5 -12
- package/dist/src/utils/logger.d.ts +14 -2
- package/dist/src/utils/logger.js +43 -13
- package/dist/src/utils/options.d.ts +21 -4
- package/dist/src/utils/options.js +26 -6
- package/dist/src/utils/package-root.js +7 -4
- package/dist/src/utils/process-group.js +13 -2
- package/dist/src/utils/stdio-drain.d.ts +28 -0
- package/dist/src/utils/stdio-drain.js +54 -0
- package/dist/src/utils/validation.d.ts +23 -2
- package/dist/src/utils/validation.js +29 -3
- package/package.json +9 -9
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
import type { CustomComponentConfig, DryRunAction, OverrideComponentConfig, StepError } from '../types/furnace.js';
|
|
2
2
|
import { type RollbackJournal } from './furnace-rollback.js';
|
|
3
|
+
interface DirectoryEntry {
|
|
4
|
+
isFile(): boolean;
|
|
5
|
+
isSymbolicLink?(): boolean;
|
|
6
|
+
name: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* True for a plain-file directory entry — symlinks and directories are
|
|
10
|
+
* never copy candidates. Exported for the patch-owned overwrite probe
|
|
11
|
+
* (FORGE J6), which walks the same override copy-candidate set as apply.
|
|
12
|
+
*/
|
|
13
|
+
export declare function isRegularFile(entry: DirectoryEntry): boolean;
|
|
3
14
|
/**
|
|
4
15
|
* Filter deciding which files in an override workspace directory are candidates
|
|
5
16
|
* for copying into the engine. Exported so `furnace remove` can invert apply
|
|
@@ -62,7 +73,7 @@ export declare function undeployOverrideFiles(engineDir: string, config: Overrid
|
|
|
62
73
|
removed: string[];
|
|
63
74
|
}>;
|
|
64
75
|
/** Compares current component file checksums against the previously recorded state. */
|
|
65
|
-
export declare function hasComponentChanged(componentDir: string, previousChecksums: Record<string, string>): Promise<boolean>;
|
|
76
|
+
export declare function hasComponentChanged(componentDir: string, previousChecksums: Record<string, string>, currentChecksums?: Record<string, string>): Promise<boolean>;
|
|
66
77
|
/**
|
|
67
78
|
* Detects whether an override component's deployed files are missing from the
|
|
68
79
|
* engine or differ from the source. Used as a guard before skipping apply on a
|
|
@@ -15,7 +15,12 @@ import { recordCreatedDir, snapshotFile } from './furnace-rollback.js';
|
|
|
15
15
|
import { checkRegistrationConsistency } from './furnace-validate-registration.js';
|
|
16
16
|
import { isGitRepository } from './git.js';
|
|
17
17
|
import { fileExistsInHead, restoreTrackedPath } from './git-file-ops.js';
|
|
18
|
-
|
|
18
|
+
/**
|
|
19
|
+
* True for a plain-file directory entry — symlinks and directories are
|
|
20
|
+
* never copy candidates. Exported for the patch-owned overwrite probe
|
|
21
|
+
* (FORGE J6), which walks the same override copy-candidate set as apply.
|
|
22
|
+
*/
|
|
23
|
+
export function isRegularFile(entry) {
|
|
19
24
|
if (!entry.isFile())
|
|
20
25
|
return false;
|
|
21
26
|
if (typeof entry.isSymbolicLink === 'function' && entry.isSymbolicLink())
|
|
@@ -177,8 +182,12 @@ export async function undeployOverrideFiles(engineDir, config, deletedFiles, ftl
|
|
|
177
182
|
return { restored, removed };
|
|
178
183
|
}
|
|
179
184
|
/** Compares current component file checksums against the previously recorded state. */
|
|
180
|
-
export async function hasComponentChanged(componentDir, previousChecksums) {
|
|
181
|
-
const current = await computeComponentChecksums(componentDir);
|
|
185
|
+
export async function hasComponentChanged(componentDir, previousChecksums, currentChecksums) {
|
|
186
|
+
const current = currentChecksums ?? (await computeComponentChecksums(componentDir));
|
|
187
|
+
return componentChecksumsChanged(current, previousChecksums);
|
|
188
|
+
}
|
|
189
|
+
/** Pure checksum-map comparison used when the caller already walked the component. */
|
|
190
|
+
function componentChecksumsChanged(current, previousChecksums) {
|
|
182
191
|
const currentKeys = Object.keys(current);
|
|
183
192
|
const previousKeys = Object.keys(previousChecksums);
|
|
184
193
|
if (currentKeys.length !== previousKeys.length) {
|
|
@@ -51,6 +51,15 @@ export function logApplyResult(result, isDryRun) {
|
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
|
+
// Patch-owned overwrite warnings (FORGE J6) print on every non-dry-run
|
|
55
|
+
// outcome — including the rolled-back branch, where the overwrite
|
|
56
|
+
// happened before the rollback restored it and the operator still needs
|
|
57
|
+
// to know the deployed copy was momentarily replaced.
|
|
58
|
+
if (!isDryRun && result.warnings !== undefined) {
|
|
59
|
+
for (const line of result.warnings) {
|
|
60
|
+
warn(line);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
54
63
|
for (const err of result.errors) {
|
|
55
64
|
error(`${err.name} — ${err.error}`);
|
|
56
65
|
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Patch-owned overwrite warnings for `furnace apply` (FORGE J6).
|
|
3
|
+
*
|
|
4
|
+
* For Furnace-managed components, apply OVERWRITES the deployed engine
|
|
5
|
+
* copies with the `components/` sources. A fix made directly in `engine/`
|
|
6
|
+
* and exported into a patch — but never back-ported to the component
|
|
7
|
+
* source — is silently undone by the next apply, and the only prior
|
|
8
|
+
* signal was a drift line that read like "someone's uncommitted work".
|
|
9
|
+
* This module detects that exact case BEFORE the copy: the deployed
|
|
10
|
+
* bytes differ from the component source AND the file is patch-owned,
|
|
11
|
+
* and produces a loud per-file warning naming the file, the owning
|
|
12
|
+
* patch, and the consequence.
|
|
13
|
+
*
|
|
14
|
+
* The per-file byte comparison already exists —
|
|
15
|
+
* `checkRegistrationConsistency` for custom components (fragment- and
|
|
16
|
+
* ftl-aware) and the copy-candidate walk for overrides — this module
|
|
17
|
+
* only surfaces what was previously computed and thrown away.
|
|
18
|
+
*/
|
|
19
|
+
import type { CustomComponentConfig, OverrideComponentConfig } from '../types/furnace.js';
|
|
20
|
+
/** One deployed, patch-owned file about to be overwritten with different bytes. */
|
|
21
|
+
export interface PatchOwnedOverwriteWarning {
|
|
22
|
+
component: string;
|
|
23
|
+
/** Engine-relative path of the deployed file being replaced. */
|
|
24
|
+
file: string;
|
|
25
|
+
/** Patch filenames claiming the path in patches.json. */
|
|
26
|
+
owners: string[];
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Loads the file → owning-patch map for the overwrite warning (FORGE J6).
|
|
30
|
+
* A missing or unreadable patches manifest degrades to an empty map — the
|
|
31
|
+
* warning is advisory and must never block an apply.
|
|
32
|
+
*/
|
|
33
|
+
export declare function loadPatchClaimsForApply(root: string): Promise<ReadonlyMap<string, readonly string[]>>;
|
|
34
|
+
/** Appends formatted overwrite warnings onto the run's result. */
|
|
35
|
+
export declare function recordOverwriteWarnings(result: {
|
|
36
|
+
warnings?: string[];
|
|
37
|
+
}, warnings: readonly PatchOwnedOverwriteWarning[]): void;
|
|
38
|
+
/** Renders the operator-facing warning line for one overwrite. */
|
|
39
|
+
export declare function formatPatchOwnedOverwriteWarning(warning: PatchOwnedOverwriteWarning): string;
|
|
40
|
+
/** Inputs for {@link findPatchOwnedOverwrites}, discriminated by component type. */
|
|
41
|
+
export type PatchOwnedOverwriteProbe = {
|
|
42
|
+
type: 'custom';
|
|
43
|
+
root: string;
|
|
44
|
+
name: string;
|
|
45
|
+
config: CustomComponentConfig;
|
|
46
|
+
ftlDir: string;
|
|
47
|
+
patchClaims: ReadonlyMap<string, readonly string[]>;
|
|
48
|
+
} | {
|
|
49
|
+
type: 'override';
|
|
50
|
+
engineDir: string;
|
|
51
|
+
name: string;
|
|
52
|
+
componentDir: string;
|
|
53
|
+
config: OverrideComponentConfig;
|
|
54
|
+
ftlDir: string;
|
|
55
|
+
patchClaims: ReadonlyMap<string, readonly string[]>;
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* Finds deployed files this component's apply is about to overwrite whose
|
|
59
|
+
* engine bytes differ from the component source AND that are patch-owned.
|
|
60
|
+
* Probe failures degrade to a verbose line and an empty list — the apply
|
|
61
|
+
* itself must not fail because the warning probe could not run.
|
|
62
|
+
*/
|
|
63
|
+
export declare function findPatchOwnedOverwrites(args: PatchOwnedOverwriteProbe): Promise<PatchOwnedOverwriteWarning[]>;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* Patch-owned overwrite warnings for `furnace apply` (FORGE J6).
|
|
4
|
+
*
|
|
5
|
+
* For Furnace-managed components, apply OVERWRITES the deployed engine
|
|
6
|
+
* copies with the `components/` sources. A fix made directly in `engine/`
|
|
7
|
+
* and exported into a patch — but never back-ported to the component
|
|
8
|
+
* source — is silently undone by the next apply, and the only prior
|
|
9
|
+
* signal was a drift line that read like "someone's uncommitted work".
|
|
10
|
+
* This module detects that exact case BEFORE the copy: the deployed
|
|
11
|
+
* bytes differ from the component source AND the file is patch-owned,
|
|
12
|
+
* and produces a loud per-file warning naming the file, the owning
|
|
13
|
+
* patch, and the consequence.
|
|
14
|
+
*
|
|
15
|
+
* The per-file byte comparison already exists —
|
|
16
|
+
* `checkRegistrationConsistency` for custom components (fragment- and
|
|
17
|
+
* ftl-aware) and the copy-candidate walk for overrides — this module
|
|
18
|
+
* only surfaces what was previously computed and thrown away.
|
|
19
|
+
*/
|
|
20
|
+
import { readdir } from 'node:fs/promises';
|
|
21
|
+
import { join, relative } from 'node:path';
|
|
22
|
+
import { toError } from '../utils/errors.js';
|
|
23
|
+
import { pathExists, readText } from '../utils/fs.js';
|
|
24
|
+
import { verbose } from '../utils/logger.js';
|
|
25
|
+
import { getProjectPaths } from './config.js';
|
|
26
|
+
import { getOverrideEngineTargetPath, isOverrideCopyCandidate, isRegularFile, } from './furnace-apply-helpers.js';
|
|
27
|
+
import { FTL_DIR } from './furnace-constants.js';
|
|
28
|
+
import { checkRegistrationConsistency } from './furnace-validate-registration.js';
|
|
29
|
+
import { loadPatchesManifest } from './patch-manifest.js';
|
|
30
|
+
import { buildPatchClaims } from './status-classify.js';
|
|
31
|
+
/**
|
|
32
|
+
* Loads the file → owning-patch map for the overwrite warning (FORGE J6).
|
|
33
|
+
* A missing or unreadable patches manifest degrades to an empty map — the
|
|
34
|
+
* warning is advisory and must never block an apply.
|
|
35
|
+
*/
|
|
36
|
+
export async function loadPatchClaimsForApply(root) {
|
|
37
|
+
try {
|
|
38
|
+
const manifest = await loadPatchesManifest(getProjectPaths(root).patches);
|
|
39
|
+
return manifest ? buildPatchClaims(manifest.patches) : new Map();
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return new Map();
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/** Appends formatted overwrite warnings onto the run's result. */
|
|
46
|
+
export function recordOverwriteWarnings(result, warnings) {
|
|
47
|
+
if (warnings.length === 0)
|
|
48
|
+
return;
|
|
49
|
+
result.warnings ??= [];
|
|
50
|
+
result.warnings.push(...warnings.map(formatPatchOwnedOverwriteWarning));
|
|
51
|
+
}
|
|
52
|
+
/** Renders the operator-facing warning line for one overwrite. */
|
|
53
|
+
export function formatPatchOwnedOverwriteWarning(warning) {
|
|
54
|
+
const owners = warning.owners.join(', ');
|
|
55
|
+
return (`${warning.component}: overwriting deployed ${warning.file} — its engine content differs ` +
|
|
56
|
+
`from the components/ source, and the path is owned by patch ${owners}. If the engine ` +
|
|
57
|
+
`edit was an intentional shipped fix, port it into the component source and re-export ` +
|
|
58
|
+
`the patch — the deployed copy is being replaced now.`);
|
|
59
|
+
}
|
|
60
|
+
/** Custom components: reuse the validate oracle's drifted-file list. */
|
|
61
|
+
async function findCustomOverwrites(args) {
|
|
62
|
+
const { root, name, config, ftlDir, patchClaims } = args;
|
|
63
|
+
const status = await checkRegistrationConsistency(root, name, config, ftlDir);
|
|
64
|
+
const warnings = [];
|
|
65
|
+
for (const entryName of status.driftedFiles) {
|
|
66
|
+
const engineRel = entryName.endsWith('.ftl')
|
|
67
|
+
? `${ftlDir || FTL_DIR}/${entryName}`
|
|
68
|
+
: `${config.targetPath}/${entryName}`;
|
|
69
|
+
const owners = patchClaims.get(engineRel);
|
|
70
|
+
if (owners !== undefined && owners.length > 0) {
|
|
71
|
+
warnings.push({ component: name, file: engineRel, owners: [...owners] });
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return warnings;
|
|
75
|
+
}
|
|
76
|
+
/** Override components: byte-compare each copy candidate against its engine target. */
|
|
77
|
+
async function findOverrideOverwrites(args) {
|
|
78
|
+
const { engineDir, name, componentDir, config, ftlDir, patchClaims } = args;
|
|
79
|
+
const warnings = [];
|
|
80
|
+
const entries = await readdir(componentDir, { withFileTypes: true, encoding: 'utf8' });
|
|
81
|
+
for (const entry of entries) {
|
|
82
|
+
if (!isRegularFile(entry))
|
|
83
|
+
continue;
|
|
84
|
+
if (!isOverrideCopyCandidate(entry.name, config.type))
|
|
85
|
+
continue;
|
|
86
|
+
const enginePath = getOverrideEngineTargetPath(engineDir, config, entry.name, ftlDir);
|
|
87
|
+
// A missing target is a fresh deploy, not a lost engine edit.
|
|
88
|
+
if (!(await pathExists(enginePath)))
|
|
89
|
+
continue;
|
|
90
|
+
const engineRel = relative(engineDir, enginePath).replace(/\\/g, '/');
|
|
91
|
+
const owners = patchClaims.get(engineRel);
|
|
92
|
+
if (owners === undefined || owners.length === 0)
|
|
93
|
+
continue;
|
|
94
|
+
const [engineContent, sourceContent] = await Promise.all([
|
|
95
|
+
readText(enginePath),
|
|
96
|
+
readText(join(componentDir, entry.name)),
|
|
97
|
+
]);
|
|
98
|
+
if (engineContent !== sourceContent) {
|
|
99
|
+
warnings.push({ component: name, file: engineRel, owners: [...owners] });
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return warnings;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Finds deployed files this component's apply is about to overwrite whose
|
|
106
|
+
* engine bytes differ from the component source AND that are patch-owned.
|
|
107
|
+
* Probe failures degrade to a verbose line and an empty list — the apply
|
|
108
|
+
* itself must not fail because the warning probe could not run.
|
|
109
|
+
*/
|
|
110
|
+
export async function findPatchOwnedOverwrites(args) {
|
|
111
|
+
if (args.patchClaims.size === 0)
|
|
112
|
+
return [];
|
|
113
|
+
try {
|
|
114
|
+
if (args.type === 'custom') {
|
|
115
|
+
return await findCustomOverwrites(args);
|
|
116
|
+
}
|
|
117
|
+
return await findOverrideOverwrites(args);
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
verbose(`Skipping patch-owned overwrite probe for ${args.name}: ${toError(error).message}`);
|
|
121
|
+
return [];
|
|
122
|
+
}
|
|
123
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dry-run action builders for files deleted from a component workspace
|
|
3
|
+
* since the last apply. Split out of `furnace-apply.ts` (at the per-file
|
|
4
|
+
* line budget after the FORGE J6 overwrite-warning wiring); the real
|
|
5
|
+
* undeploy paths stay in `furnace-apply-helpers.ts`.
|
|
6
|
+
*/
|
|
7
|
+
import type { CustomComponentConfig, DryRunAction, OverrideComponentConfig } from '../types/furnace.js';
|
|
8
|
+
/** Plans the engine-restore actions for deleted override workspace files. */
|
|
9
|
+
export declare function buildOverrideUndeployActions(name: string, config: OverrideComponentConfig, engineDir: string, deletedFiles: string[], ftlDir: string): DryRunAction[];
|
|
10
|
+
/** Plans the engine-removal + deregistration actions for deleted custom workspace files. */
|
|
11
|
+
export declare function buildCustomUndeployActions(name: string, config: CustomComponentConfig, engineDir: string, deletedFiles: string[], ftlDir: string): DryRunAction[];
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* Dry-run action builders for files deleted from a component workspace
|
|
4
|
+
* since the last apply. Split out of `furnace-apply.ts` (at the per-file
|
|
5
|
+
* line budget after the FORGE J6 overwrite-warning wiring); the real
|
|
6
|
+
* undeploy paths stay in `furnace-apply-helpers.ts`.
|
|
7
|
+
*/
|
|
8
|
+
import { join } from 'node:path';
|
|
9
|
+
import { getOverrideEngineTargetPath } from './furnace-apply-helpers.js';
|
|
10
|
+
/** Plans the engine-restore actions for deleted override workspace files. */
|
|
11
|
+
export function buildOverrideUndeployActions(name, config, engineDir, deletedFiles, ftlDir) {
|
|
12
|
+
return deletedFiles.map((fileName) => ({
|
|
13
|
+
component: name,
|
|
14
|
+
action: 'undeploy-restore',
|
|
15
|
+
target: getOverrideEngineTargetPath(engineDir, config, fileName, ftlDir),
|
|
16
|
+
description: `Restore engine/${fileName.endsWith('.ftl') ? `${ftlDir}/${fileName}` : `${config.basePath}/${fileName}`} to Firefox baseline`,
|
|
17
|
+
}));
|
|
18
|
+
}
|
|
19
|
+
/** Plans the engine-removal + deregistration actions for deleted custom workspace files. */
|
|
20
|
+
export function buildCustomUndeployActions(name, config, engineDir, deletedFiles, ftlDir) {
|
|
21
|
+
const actions = [];
|
|
22
|
+
for (const fileName of deletedFiles) {
|
|
23
|
+
const enginePath = fileName.endsWith('.ftl')
|
|
24
|
+
? join(engineDir, ftlDir, fileName)
|
|
25
|
+
: join(engineDir, config.targetPath, fileName);
|
|
26
|
+
actions.push({
|
|
27
|
+
component: name,
|
|
28
|
+
action: 'undeploy-remove',
|
|
29
|
+
target: enginePath,
|
|
30
|
+
description: `Remove orphaned ${fileName} from engine`,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
// jar.mn re-sync planned for any custom-file deletion when registered.
|
|
34
|
+
if (config.register && deletedFiles.some((f) => f.endsWith('.mjs') || f.endsWith('.css'))) {
|
|
35
|
+
actions.push({
|
|
36
|
+
component: name,
|
|
37
|
+
action: 'unregister-jar',
|
|
38
|
+
description: `Re-sync ${name} jar.mn entries to drop deleted files`,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
if (config.register && deletedFiles.some((f) => f === `${name}.mjs`)) {
|
|
42
|
+
actions.push({
|
|
43
|
+
component: name,
|
|
44
|
+
action: 'unregister-ce',
|
|
45
|
+
description: `Deregister ${name} from customElements.js (.mjs deleted)`,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
return actions;
|
|
49
|
+
}
|
|
@@ -2,6 +2,21 @@ import type { ApplyResult, DryRunAction } from '../types/furnace.js';
|
|
|
2
2
|
import { type FurnaceOperationContext } from './furnace-operation.js';
|
|
3
3
|
import { type RollbackJournal } from './furnace-rollback.js';
|
|
4
4
|
export { computeComponentChecksums, extractComponentChecksums, hasComponentChanged, hasCustomEngineDrift, hasOverrideEngineDrift, prefixChecksums, } from './furnace-apply-helpers.js';
|
|
5
|
+
/**
|
|
6
|
+
* Result of {@link applyAllComponents}.
|
|
7
|
+
*
|
|
8
|
+
* Named because the anonymous intersection this replaced forced eight
|
|
9
|
+
* `Awaited<ReturnType<typeof applyAllComponents>>` indirections across
|
|
10
|
+
* `furnace/deploy.ts`, `furnace/preview.ts`, `furnace-state-persist.ts` and
|
|
11
|
+
* `build-prepare.ts` — none of which could be read without opening this file.
|
|
12
|
+
*
|
|
13
|
+
* `actions` is populated only on a dry run; `rollbackJournal` only when
|
|
14
|
+
* `persistState: false` hands journal ownership back to the caller.
|
|
15
|
+
*/
|
|
16
|
+
export type ApplyAllComponentsResult = ApplyResult & {
|
|
17
|
+
actions?: DryRunAction[];
|
|
18
|
+
rollbackJournal?: RollbackJournal;
|
|
19
|
+
};
|
|
5
20
|
/**
|
|
6
21
|
* Applies all override and custom components to the engine source tree.
|
|
7
22
|
*
|
|
@@ -28,10 +43,7 @@ export declare function applyAllComponents(root: string, dryRun?: boolean, optio
|
|
|
28
43
|
persistState?: boolean;
|
|
29
44
|
operationContext?: FurnaceOperationContext;
|
|
30
45
|
componentName?: string;
|
|
31
|
-
}): Promise<
|
|
32
|
-
actions?: DryRunAction[];
|
|
33
|
-
rollbackJournal?: RollbackJournal;
|
|
34
|
-
}>; /**
|
|
46
|
+
}): Promise<ApplyAllComponentsResult>; /**
|
|
35
47
|
* Shared context threaded through the batch apply loops. Bundles the state
|
|
36
48
|
* both loops mutate (result/actions/checksums) with the immutable run
|
|
37
49
|
* parameters — the previous 12–13 positional parameters let call sites
|
|
@@ -5,7 +5,9 @@ import { toError } from '../utils/errors.js';
|
|
|
5
5
|
import { pathExists } from '../utils/fs.js';
|
|
6
6
|
import { info } from '../utils/logger.js';
|
|
7
7
|
import { getProjectPaths, loadConfig } from './config.js';
|
|
8
|
-
import { applyCustomComponent, applyOverrideComponent, computeComponentChecksums, diffDeletedFiles, extractComponentChecksums,
|
|
8
|
+
import { applyCustomComponent, applyOverrideComponent, computeComponentChecksums, diffDeletedFiles, extractComponentChecksums, hasComponentChanged, hasCustomEngineDrift, hasOverrideEngineDrift, prefixChecksums, undeployCustomFiles, undeployOverrideFiles, } from './furnace-apply-helpers.js';
|
|
9
|
+
import { findPatchOwnedOverwrites, loadPatchClaimsForApply, recordOverwriteWarnings, } from './furnace-apply-overwrite-warn.js';
|
|
10
|
+
import { buildCustomUndeployActions, buildOverrideUndeployActions, } from './furnace-apply-undeploy-actions.js';
|
|
9
11
|
import { getFurnacePaths, loadFurnaceConfig, loadFurnaceState, updateFurnaceState, } from './furnace-config.js';
|
|
10
12
|
import { CUSTOM_ELEMENTS_JS, JAR_MN, resolveFtlDir } from './furnace-constants.js';
|
|
11
13
|
import { topologicalSortCustom } from './furnace-graph-utils.js';
|
|
@@ -22,14 +24,6 @@ function addMissingComponentError(result, name, directoryPath) {
|
|
|
22
24
|
error: `Component directory not found: ${directoryPath}`,
|
|
23
25
|
});
|
|
24
26
|
}
|
|
25
|
-
function buildOverrideUndeployActions(name, config, engineDir, deletedFiles, ftlDir) {
|
|
26
|
-
return deletedFiles.map((fileName) => ({
|
|
27
|
-
component: name,
|
|
28
|
-
action: 'undeploy-restore',
|
|
29
|
-
target: getOverrideEngineTargetPath(engineDir, config, fileName, ftlDir),
|
|
30
|
-
description: `Restore engine/${fileName.endsWith('.ftl') ? `${ftlDir}/${fileName}` : `${config.basePath}/${fileName}`} to Firefox baseline`,
|
|
31
|
-
}));
|
|
32
|
-
}
|
|
33
27
|
async function applyOverrideBatch(ctx) {
|
|
34
28
|
const { config, furnacePaths, state, engineDir, ftlDir, dryRun } = ctx;
|
|
35
29
|
const { result, allActions, newChecksums, rollbackJournal, componentName } = ctx;
|
|
@@ -47,8 +41,9 @@ async function applyOverrideBatch(ctx) {
|
|
|
47
41
|
continue;
|
|
48
42
|
}
|
|
49
43
|
const previous = extractComponentChecksums(state.appliedChecksums, 'override', name);
|
|
44
|
+
const currentChecksums = await computeComponentChecksums(componentDir);
|
|
50
45
|
if (!dryRun) {
|
|
51
|
-
const changed = await hasComponentChanged(componentDir, previous);
|
|
46
|
+
const changed = await hasComponentChanged(componentDir, previous, currentChecksums);
|
|
52
47
|
if (!changed) {
|
|
53
48
|
// Fast path holds only if the engine still reflects what we deployed.
|
|
54
49
|
// reset/download/manual edits can silently erase engine files; the
|
|
@@ -67,7 +62,6 @@ async function applyOverrideBatch(ctx) {
|
|
|
67
62
|
// Compute which files (if any) were removed from the workspace since
|
|
68
63
|
// the last apply. We do this for both dry-run and real runs so the
|
|
69
64
|
// planned-actions output stays honest.
|
|
70
|
-
const currentChecksums = await computeComponentChecksums(componentDir);
|
|
71
65
|
const deletedFiles = diffDeletedFiles(previous, currentChecksums);
|
|
72
66
|
if (dryRun) {
|
|
73
67
|
if (deletedFiles.length > 0) {
|
|
@@ -78,6 +72,20 @@ async function applyOverrideBatch(ctx) {
|
|
|
78
72
|
const { restored, removed } = await undeployOverrideFiles(engineDir, overrideConfig, deletedFiles, ftlDir, rollbackJournal);
|
|
79
73
|
filesAffectedTotal.push(...restored, ...removed);
|
|
80
74
|
}
|
|
75
|
+
if (!dryRun) {
|
|
76
|
+
// Runs unconditionally — including when the component source
|
|
77
|
+
// changed — because the lost-work case is precisely a deployed
|
|
78
|
+
// engine edit that the incoming copy is about to replace (J6).
|
|
79
|
+
recordOverwriteWarnings(result, await findPatchOwnedOverwrites({
|
|
80
|
+
type: 'override',
|
|
81
|
+
engineDir,
|
|
82
|
+
name,
|
|
83
|
+
componentDir,
|
|
84
|
+
config: overrideConfig,
|
|
85
|
+
ftlDir,
|
|
86
|
+
patchClaims: ctx.patchClaims,
|
|
87
|
+
}));
|
|
88
|
+
}
|
|
81
89
|
const { affectedPaths: filesAffected, actions } = await applyOverrideComponent(engineDir, name, componentDir, overrideConfig, ftlDir, dryRun, rollbackJournal);
|
|
82
90
|
if (dryRun && actions) {
|
|
83
91
|
allActions.push(...actions);
|
|
@@ -96,36 +104,6 @@ async function applyOverrideBatch(ctx) {
|
|
|
96
104
|
}
|
|
97
105
|
}
|
|
98
106
|
}
|
|
99
|
-
function buildCustomUndeployActions(name, config, engineDir, deletedFiles, ftlDir) {
|
|
100
|
-
const actions = [];
|
|
101
|
-
for (const fileName of deletedFiles) {
|
|
102
|
-
const enginePath = fileName.endsWith('.ftl')
|
|
103
|
-
? join(engineDir, ftlDir, fileName)
|
|
104
|
-
: join(engineDir, config.targetPath, fileName);
|
|
105
|
-
actions.push({
|
|
106
|
-
component: name,
|
|
107
|
-
action: 'undeploy-remove',
|
|
108
|
-
target: enginePath,
|
|
109
|
-
description: `Remove orphaned ${fileName} from engine`,
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
|
-
// jar.mn re-sync planned for any custom-file deletion when registered.
|
|
113
|
-
if (config.register && deletedFiles.some((f) => f.endsWith('.mjs') || f.endsWith('.css'))) {
|
|
114
|
-
actions.push({
|
|
115
|
-
component: name,
|
|
116
|
-
action: 'unregister-jar',
|
|
117
|
-
description: `Re-sync ${name} jar.mn entries to drop deleted files`,
|
|
118
|
-
});
|
|
119
|
-
}
|
|
120
|
-
if (config.register && deletedFiles.some((f) => f === `${name}.mjs`)) {
|
|
121
|
-
actions.push({
|
|
122
|
-
component: name,
|
|
123
|
-
action: 'unregister-ce',
|
|
124
|
-
description: `Deregister ${name} from customElements.js (.mjs deleted)`,
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
return actions;
|
|
128
|
-
}
|
|
129
107
|
/**
|
|
130
108
|
* After undeploying deleted files, the in-engine jar.mn and
|
|
131
109
|
* customElements.js still carry entries for the removed files. Re-sync them
|
|
@@ -222,8 +200,9 @@ async function applyCustomBatch(ctx, root, markerComment) {
|
|
|
222
200
|
continue;
|
|
223
201
|
}
|
|
224
202
|
const previous = extractComponentChecksums(state.appliedChecksums, 'custom', name);
|
|
203
|
+
const currentChecksums = await computeComponentChecksums(componentDir);
|
|
225
204
|
if (!dryRun) {
|
|
226
|
-
const changed = await hasComponentChanged(componentDir, previous);
|
|
205
|
+
const changed = await hasComponentChanged(componentDir, previous, currentChecksums);
|
|
227
206
|
if (!changed) {
|
|
228
207
|
// As with overrides, the checksum record is not sufficient on its
|
|
229
208
|
// own: a reset/download that cleared the engine must trigger a
|
|
@@ -241,7 +220,6 @@ async function applyCustomBatch(ctx, root, markerComment) {
|
|
|
241
220
|
// Diff against previous to find files the developer has deleted from
|
|
242
221
|
// the workspace since last apply. Run for both dry-run and real apply
|
|
243
222
|
// so plan output and execution stay aligned.
|
|
244
|
-
const currentChecksums = await computeComponentChecksums(componentDir);
|
|
245
223
|
const deletedFiles = diffDeletedFiles(previous, currentChecksums);
|
|
246
224
|
if (dryRun) {
|
|
247
225
|
if (deletedFiles.length > 0) {
|
|
@@ -252,6 +230,19 @@ async function applyCustomBatch(ctx, root, markerComment) {
|
|
|
252
230
|
const removed = await undeployCustomFiles(engineDir, customConfig, deletedFiles, ftlDir, rollbackJournal);
|
|
253
231
|
filesAffectedTotal.push(...removed);
|
|
254
232
|
}
|
|
233
|
+
if (!dryRun) {
|
|
234
|
+
// Runs unconditionally — the `changed === true` path used to skip
|
|
235
|
+
// drift detection entirely, which is exactly when a deployed
|
|
236
|
+
// engine-only fix gets silently replaced (J6).
|
|
237
|
+
recordOverwriteWarnings(result, await findPatchOwnedOverwrites({
|
|
238
|
+
type: 'custom',
|
|
239
|
+
root,
|
|
240
|
+
name,
|
|
241
|
+
config: customConfig,
|
|
242
|
+
ftlDir,
|
|
243
|
+
patchClaims: ctx.patchClaims,
|
|
244
|
+
}));
|
|
245
|
+
}
|
|
255
246
|
const { affectedPaths: filesAffected, stepErrors, actions, } = await applyCustomComponent(engineDir, name, componentDir, customConfig, ftlDir, dryRun, rollbackJournal, markerComment !== undefined ? { markerComment } : {});
|
|
256
247
|
if (dryRun && actions) {
|
|
257
248
|
allActions.push(...actions);
|
|
@@ -345,6 +336,10 @@ export async function applyAllComponents(root, dryRun = false, options) {
|
|
|
345
336
|
throw new FurnaceError(`Component "${componentName}" not found in furnace.json. Run "fireforge furnace list" to see registered components.`, componentName);
|
|
346
337
|
}
|
|
347
338
|
}
|
|
339
|
+
// Patch-claim map for the overwrite warning (FORGE J6): loaded once per
|
|
340
|
+
// apply. A missing/unreadable manifest degrades to an empty map — the
|
|
341
|
+
// warning is advisory and must never block the apply.
|
|
342
|
+
const patchClaims = await loadPatchClaimsForApply(root);
|
|
348
343
|
const batchContext = {
|
|
349
344
|
config,
|
|
350
345
|
furnacePaths,
|
|
@@ -357,6 +352,7 @@ export async function applyAllComponents(root, dryRun = false, options) {
|
|
|
357
352
|
newChecksums,
|
|
358
353
|
rollbackJournal,
|
|
359
354
|
componentName,
|
|
355
|
+
patchClaims,
|
|
360
356
|
};
|
|
361
357
|
await applyOverrideBatch(batchContext);
|
|
362
358
|
await applyCustomBatch(batchContext, root, markerComment);
|
|
@@ -360,6 +360,8 @@ export async function writeFurnaceConfig(root, config) {
|
|
|
360
360
|
existing = raw;
|
|
361
361
|
}
|
|
362
362
|
catch {
|
|
363
|
+
// A missing or corrupt furnace.json means there is no prior document to
|
|
364
|
+
// preserve key order from; the writer falls back to canonical ordering.
|
|
363
365
|
existing = undefined;
|
|
364
366
|
}
|
|
365
367
|
}
|
|
@@ -194,6 +194,8 @@ export async function validateCssFragments(componentDir, tagName, sharedDir, eng
|
|
|
194
194
|
entries = await readdir(componentDir);
|
|
195
195
|
}
|
|
196
196
|
catch {
|
|
197
|
+
// An unreadable component directory contributes no fragment issues; return
|
|
198
|
+
// what has been collected so far rather than failing validation.
|
|
197
199
|
return issues;
|
|
198
200
|
}
|
|
199
201
|
for (const fileName of entries) {
|
|
@@ -24,6 +24,7 @@ import { readdir } from 'node:fs/promises';
|
|
|
24
24
|
import { dirname, join, relative, resolve } from 'node:path';
|
|
25
25
|
import { applyEdits, modify, parse } from 'jsonc-parser';
|
|
26
26
|
import { FurnaceError } from '../errors/furnace.js';
|
|
27
|
+
import { toError } from '../utils/errors.js';
|
|
27
28
|
import { pathExists, readText, writeTextIfChanged } from '../utils/fs.js';
|
|
28
29
|
import { info } from '../utils/logger.js';
|
|
29
30
|
import { normalizePathSlashes } from '../utils/paths.js';
|
|
@@ -32,8 +33,14 @@ import { getFurnacePaths } from './furnace-config.js';
|
|
|
32
33
|
const CHROME_ELEMENTS_URL_PREFIX = 'chrome://global/content/elements/';
|
|
33
34
|
/**
|
|
34
35
|
* Computes the desired managed `paths` entries: one per `.mjs` file of
|
|
35
|
-
* every
|
|
36
|
-
* mapped to the workspace source relative to the jsconfig
|
|
36
|
+
* every custom component — registered or not — keyed by its deployed
|
|
37
|
+
* chrome URL and mapped to the workspace source relative to the jsconfig
|
|
38
|
+
* directory. `register: false` components (notably `kind: "library"`
|
|
39
|
+
* base-class modules, which REQUIRE register: false) are included because
|
|
40
|
+
* deploy copies their files and writes jar.mn entries under
|
|
41
|
+
* `content/global/elements/` unconditionally — only the customElements.js
|
|
42
|
+
* registration is gated on `register` — so their chrome URLs are just as
|
|
43
|
+
* real as a registered component's.
|
|
37
44
|
*
|
|
38
45
|
* Workspace sources (not deployed engine copies) are the mapping target —
|
|
39
46
|
* they are the files developers edit, and they exist even when the engine
|
|
@@ -42,9 +49,7 @@ const CHROME_ELEMENTS_URL_PREFIX = 'chrome://global/content/elements/';
|
|
|
42
49
|
async function computeDesiredChromePathEntries(config, customDir, jsconfigAbsPath) {
|
|
43
50
|
const jsconfigDir = dirname(jsconfigAbsPath);
|
|
44
51
|
const entries = {};
|
|
45
|
-
for (const
|
|
46
|
-
if (!customConfig.register)
|
|
47
|
-
continue;
|
|
52
|
+
for (const name of Object.keys(config.custom)) {
|
|
48
53
|
const componentDir = join(customDir, name);
|
|
49
54
|
if (!(await pathExists(componentDir)))
|
|
50
55
|
continue;
|
|
@@ -113,13 +118,15 @@ export async function syncFurnaceJsconfigPaths(root, config, options) {
|
|
|
113
118
|
const parseErrors = [];
|
|
114
119
|
let jsconfig;
|
|
115
120
|
try {
|
|
121
|
+
// No cast: the target is declared `unknown` and is validated below, so
|
|
122
|
+
// an assertion here would be discarded on assignment anyway.
|
|
116
123
|
jsconfig = parse(jsconfigText, parseErrors, {
|
|
117
124
|
allowTrailingComma: true,
|
|
118
125
|
disallowComments: false,
|
|
119
126
|
});
|
|
120
127
|
}
|
|
121
128
|
catch (error) {
|
|
122
|
-
throw new FurnaceError(`Could not parse ${jsconfigRel} as JSONC: ${error
|
|
129
|
+
throw new FurnaceError(`Could not parse ${jsconfigRel} as JSONC: ${toError(error).message}.`);
|
|
123
130
|
}
|
|
124
131
|
if (parseErrors.length > 0 ||
|
|
125
132
|
typeof jsconfig !== 'object' ||
|
|
@@ -151,7 +158,7 @@ export async function syncFurnaceJsconfigPaths(root, config, options) {
|
|
|
151
158
|
// a hand-written `./` prefix) is not rewritten as "stale" on every run.
|
|
152
159
|
// The existing value is kept verbatim when equivalent — no churn either
|
|
153
160
|
// way; only a genuinely different target updates (to the `./` form).
|
|
154
|
-
if (!samePathValue(value[0]
|
|
161
|
+
if (!samePathValue(value[0], want[0] ?? '')) {
|
|
155
162
|
result.updated.push(key);
|
|
156
163
|
nextPaths[key] = want;
|
|
157
164
|
}
|