@hominis/fireforge 0.10.1 → 0.11.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 +93 -1
- package/README.md +125 -238
- package/dist/bin/fireforge.js +26 -0
- package/dist/src/cli.d.ts +1 -1
- package/dist/src/cli.js +131 -52
- package/dist/src/commands/bootstrap.js +6 -2
- package/dist/src/commands/build.js +4 -2
- package/dist/src/commands/discard.js +16 -4
- package/dist/src/commands/doctor-furnace.d.ts +8 -0
- package/dist/src/commands/doctor-furnace.js +422 -0
- package/dist/src/commands/doctor.d.ts +115 -0
- package/dist/src/commands/doctor.js +327 -258
- package/dist/src/commands/download.js +16 -1
- package/dist/src/commands/export-all.js +15 -0
- package/dist/src/commands/export-flow.d.ts +91 -0
- package/dist/src/commands/export-flow.js +344 -0
- package/dist/src/commands/export.js +151 -5
- package/dist/src/commands/furnace/apply.d.ts +3 -2
- package/dist/src/commands/furnace/apply.js +169 -36
- package/dist/src/commands/furnace/create.js +162 -52
- package/dist/src/commands/furnace/deploy.js +156 -144
- package/dist/src/commands/furnace/diff.d.ts +8 -4
- package/dist/src/commands/furnace/diff.js +142 -73
- package/dist/src/commands/furnace/index.d.ts +6 -2
- package/dist/src/commands/furnace/index.js +76 -25
- package/dist/src/commands/furnace/init.d.ts +11 -0
- package/dist/src/commands/furnace/init.js +76 -0
- package/dist/src/commands/furnace/list.d.ts +4 -1
- package/dist/src/commands/furnace/list.js +35 -3
- package/dist/src/commands/furnace/override.d.ts +8 -0
- package/dist/src/commands/furnace/override.js +216 -26
- package/dist/src/commands/furnace/preview.js +184 -30
- package/dist/src/commands/furnace/refresh.d.ts +10 -0
- package/dist/src/commands/furnace/refresh.js +268 -0
- package/dist/src/commands/furnace/remove.js +285 -89
- package/dist/src/commands/furnace/rename.d.ts +5 -0
- package/dist/src/commands/furnace/rename.js +308 -0
- package/dist/src/commands/furnace/scan.d.ts +4 -1
- package/dist/src/commands/furnace/scan.js +72 -11
- package/dist/src/commands/furnace/status.js +85 -20
- package/dist/src/commands/furnace/sync.d.ts +12 -0
- package/dist/src/commands/furnace/sync.js +77 -0
- package/dist/src/commands/furnace/validate.d.ts +4 -1
- package/dist/src/commands/furnace/validate.js +99 -3
- package/dist/src/commands/furnace/validation-output.d.ts +24 -1
- package/dist/src/commands/furnace/validation-output.js +93 -1
- package/dist/src/commands/import.js +37 -4
- package/dist/src/commands/lint.js +11 -2
- package/dist/src/commands/manifest.d.ts +39 -0
- package/dist/src/commands/manifest.js +59 -0
- package/dist/src/commands/patch/delete.d.ts +28 -0
- package/dist/src/commands/patch/delete.js +209 -0
- package/dist/src/commands/patch/index.d.ts +17 -0
- package/dist/src/commands/patch/index.js +25 -0
- package/dist/src/commands/patch/reorder.d.ts +30 -0
- package/dist/src/commands/patch/reorder.js +377 -0
- package/dist/src/commands/re-export-files.d.ts +17 -0
- package/dist/src/commands/re-export-files.js +177 -0
- package/dist/src/commands/re-export.js +44 -0
- package/dist/src/commands/rebase/abort.d.ts +1 -1
- package/dist/src/commands/rebase/abort.js +12 -3
- package/dist/src/commands/rebase/confirm.d.ts +3 -3
- package/dist/src/commands/rebase/confirm.js +4 -4
- package/dist/src/commands/rebase/index.js +13 -4
- package/dist/src/commands/reset.js +20 -4
- package/dist/src/commands/run.js +46 -1
- package/dist/src/commands/setup-support.js +5 -5
- package/dist/src/commands/status.js +97 -6
- package/dist/src/commands/test.js +5 -37
- package/dist/src/commands/verify.d.ts +31 -0
- package/dist/src/commands/verify.js +126 -0
- package/dist/src/core/build-prepare.js +40 -16
- package/dist/src/core/destructive.d.ts +96 -0
- package/dist/src/core/destructive.js +137 -0
- package/dist/src/core/diff-hunks.d.ts +73 -0
- package/dist/src/core/diff-hunks.js +268 -0
- package/dist/src/core/firefox.d.ts +1 -1
- package/dist/src/core/firefox.js +1 -1
- package/dist/src/core/furnace-apply-helpers.d.ts +89 -6
- package/dist/src/core/furnace-apply-helpers.js +302 -57
- package/dist/src/core/furnace-apply-output.d.ts +16 -0
- package/dist/src/core/furnace-apply-output.js +57 -0
- package/dist/src/core/furnace-apply.d.ts +21 -3
- package/dist/src/core/furnace-apply.js +260 -29
- package/dist/src/core/furnace-checksum-utils.d.ts +4 -0
- package/dist/src/core/furnace-checksum-utils.js +24 -0
- package/dist/src/core/furnace-config.d.ts +28 -1
- package/dist/src/core/furnace-config.js +180 -17
- package/dist/src/core/furnace-constants.d.ts +22 -0
- package/dist/src/core/furnace-constants.js +36 -0
- package/dist/src/core/furnace-graph-utils.d.ts +11 -0
- package/dist/src/core/furnace-graph-utils.js +94 -0
- package/dist/src/core/furnace-operation.d.ts +108 -0
- package/dist/src/core/furnace-operation.js +220 -0
- package/dist/src/core/furnace-refresh.d.ts +20 -0
- package/dist/src/core/furnace-refresh.js +118 -0
- package/dist/src/core/furnace-registration-ast.d.ts +5 -0
- package/dist/src/core/furnace-registration-ast.js +134 -4
- package/dist/src/core/furnace-registration-remove.d.ts +25 -3
- package/dist/src/core/furnace-registration-remove.js +196 -62
- package/dist/src/core/furnace-registration-validate.d.ts +13 -1
- package/dist/src/core/furnace-registration-validate.js +15 -3
- package/dist/src/core/furnace-registration.d.ts +27 -4
- package/dist/src/core/furnace-registration.js +93 -11
- package/dist/src/core/furnace-rollback.d.ts +11 -0
- package/dist/src/core/furnace-rollback.js +78 -7
- package/dist/src/core/furnace-scanner.d.ts +8 -2
- package/dist/src/core/furnace-scanner.js +152 -55
- package/dist/src/core/furnace-stories.js +7 -5
- package/dist/src/core/furnace-validate-accessibility.js +7 -1
- package/dist/src/core/furnace-validate-compatibility.d.ts +1 -1
- package/dist/src/core/furnace-validate-compatibility.js +85 -1
- package/dist/src/core/furnace-validate-helpers.d.ts +4 -0
- package/dist/src/core/furnace-validate-helpers.js +31 -0
- package/dist/src/core/furnace-validate-registration.d.ts +17 -2
- package/dist/src/core/furnace-validate-registration.js +73 -3
- package/dist/src/core/furnace-validate-structure.d.ts +10 -2
- package/dist/src/core/furnace-validate-structure.js +45 -3
- package/dist/src/core/furnace-validate.d.ts +10 -1
- package/dist/src/core/furnace-validate.js +80 -6
- package/dist/src/core/furnace-version-drift.d.ts +55 -0
- package/dist/src/core/furnace-version-drift.js +101 -0
- package/dist/src/core/git-file-ops.d.ts +8 -0
- package/dist/src/core/git-file-ops.js +19 -6
- package/dist/src/core/lint-projection.d.ts +25 -0
- package/dist/src/core/lint-projection.js +44 -0
- package/dist/src/core/mach.d.ts +4 -2
- package/dist/src/core/mach.js +17 -2
- package/dist/src/core/markdown-table.d.ts +104 -0
- package/dist/src/core/markdown-table.js +266 -0
- package/dist/src/core/ownership-table.d.ts +53 -0
- package/dist/src/core/ownership-table.js +144 -0
- package/dist/src/core/patch-apply.d.ts +17 -3
- package/dist/src/core/patch-apply.js +86 -8
- package/dist/src/core/patch-export.d.ts +119 -5
- package/dist/src/core/patch-export.js +183 -25
- package/dist/src/core/patch-lint-cross.d.ts +195 -0
- package/dist/src/core/patch-lint-cross.js +428 -0
- package/dist/src/core/patch-lint-diff.d.ts +33 -0
- package/dist/src/core/patch-lint-diff.js +84 -0
- package/dist/src/core/patch-lint.d.ts +2 -4
- package/dist/src/core/patch-lint.js +12 -50
- package/dist/src/core/patch-lock.js +2 -1
- package/dist/src/core/patch-manifest-io.d.ts +102 -1
- package/dist/src/core/patch-manifest-io.js +270 -2
- package/dist/src/core/patch-manifest-query.d.ts +1 -1
- package/dist/src/core/patch-manifest-query.js +1 -1
- package/dist/src/core/patch-manifest.d.ts +1 -1
- package/dist/src/core/patch-manifest.js +1 -1
- package/dist/src/core/patch-transform.d.ts +12 -0
- package/dist/src/core/patch-transform.js +21 -7
- package/dist/src/core/token-manager.js +67 -69
- package/dist/src/core/wire-destroy.js +6 -3
- package/dist/src/core/wire-init.js +10 -4
- package/dist/src/core/wire-subscript.js +9 -3
- package/dist/src/core/wire-utils.d.ts +52 -5
- package/dist/src/core/wire-utils.js +69 -6
- package/dist/src/errors/base.d.ts +20 -0
- package/dist/src/errors/base.js +24 -0
- package/dist/src/errors/furnace.js +7 -1
- package/dist/src/errors/rebase.js +6 -1
- package/dist/src/types/commands/index.d.ts +1 -1
- package/dist/src/types/commands/options.d.ts +125 -4
- package/dist/src/types/commands/patches.d.ts +11 -1
- package/dist/src/types/config.d.ts +1 -1
- package/dist/src/types/furnace.d.ts +55 -1
- package/dist/src/utils/fs.d.ts +12 -0
- package/dist/src/utils/fs.js +30 -1
- package/dist/src/utils/package-root.d.ts +5 -0
- package/dist/src/utils/package-root.js +12 -0
- package/dist/src/utils/process.js +9 -4
- package/dist/src/utils/validation.d.ts +20 -2
- package/dist/src/utils/validation.js +26 -3
- package/package.json +1 -1
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* Shared destructive-operation contract: interactive confirmation, non-TTY
|
|
4
|
+
* refusal, dry-run plumbing, hard-refusal on structural conflicts, and a
|
|
5
|
+
* JSONL audit log.
|
|
6
|
+
*
|
|
7
|
+
* This exists because the repair primitives (`patch delete`, `patch reorder`,
|
|
8
|
+
* `re-export --files`) and the new `export --dry-run`/`export --order` flags
|
|
9
|
+
* all share the same dance: build a change summary, gate it behind a prompt,
|
|
10
|
+
* accept a `--yes` bypass for CI, accept a `--dry-run` no-op, and refuse
|
|
11
|
+
* outright when the change would introduce a structural conflict (e.g. a
|
|
12
|
+
* forward-import that later-patch lint would then block). Without a single
|
|
13
|
+
* helper, every new destructive command would re-implement this and drift.
|
|
14
|
+
*/
|
|
15
|
+
import { appendFile } from 'node:fs/promises';
|
|
16
|
+
import { join } from 'node:path';
|
|
17
|
+
import { confirm } from '@clack/prompts';
|
|
18
|
+
import { InvalidArgumentError } from '../errors/base.js';
|
|
19
|
+
import { ensureDir } from '../utils/fs.js';
|
|
20
|
+
import { info, isCancel, warn } from '../utils/logger.js';
|
|
21
|
+
/** Filename of the audit log, relative to the patches directory. */
|
|
22
|
+
export const HISTORY_LOG_FILENAME = '.fireforge-history.jsonl';
|
|
23
|
+
/**
|
|
24
|
+
* Prints the change summary lines at warn-severity so the prompt stands out.
|
|
25
|
+
*/
|
|
26
|
+
function printSummary(title, summary) {
|
|
27
|
+
warn(title);
|
|
28
|
+
for (const line of summary) {
|
|
29
|
+
info(` ${line}`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Prints a conflict report at warn-severity. Used by both the refusal path
|
|
34
|
+
* and the dry-run-with-conflicts preview path.
|
|
35
|
+
*/
|
|
36
|
+
function printConflicts(conflicts) {
|
|
37
|
+
warn(`Refused: ${conflicts.reason}`);
|
|
38
|
+
for (const detail of conflicts.details) {
|
|
39
|
+
info(` ${detail}`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Executes the destructive-operation contract: summary → conflict refusal →
|
|
44
|
+
* dry-run / force / prompt / non-TTY refusal.
|
|
45
|
+
*
|
|
46
|
+
* Returns the decision for the caller to act on; callers must not execute the
|
|
47
|
+
* mutation when the result is `'dry-run'` or `'cancelled'`, and must call
|
|
48
|
+
* {@link appendHistory} only after the mutation succeeds (never on dry-run or
|
|
49
|
+
* cancellation).
|
|
50
|
+
*
|
|
51
|
+
* @param input - Operation description, flags, and optional conflict report
|
|
52
|
+
* @returns `'proceed'` to execute, `'dry-run'` to skip execution, or
|
|
53
|
+
* `'cancelled'` when the user declined the prompt
|
|
54
|
+
*/
|
|
55
|
+
export async function confirmDestructive(input) {
|
|
56
|
+
const { operation, title, summary, yes, dryRun, unsafeOverride, conflicts } = input;
|
|
57
|
+
// Dry-run: print everything that would happen (including refusal notice if
|
|
58
|
+
// the real run would be refused), but never mutate and never prompt.
|
|
59
|
+
if (dryRun) {
|
|
60
|
+
printSummary(`[dry-run] ${title}`, summary);
|
|
61
|
+
if (conflicts) {
|
|
62
|
+
printConflicts(conflicts);
|
|
63
|
+
info(' The non-dry-run would be refused. Use --force-unsafe only if you accept the risk.');
|
|
64
|
+
}
|
|
65
|
+
return 'dry-run';
|
|
66
|
+
}
|
|
67
|
+
// Hard refusal: structural conflicts are only bypassable by --force-unsafe,
|
|
68
|
+
// never by plain --yes. The distinction exists so that `--yes` can
|
|
69
|
+
// remain a safe-for-CI bypass of interactive prompts without also being a
|
|
70
|
+
// lint-override.
|
|
71
|
+
if (conflicts && !unsafeOverride) {
|
|
72
|
+
printConflicts(conflicts);
|
|
73
|
+
throw new InvalidArgumentError(`Refusing to run "${operation}": ${conflicts.reason}. ` +
|
|
74
|
+
'Fix the underlying issue (usually re-export --files or a different target), ' +
|
|
75
|
+
'or pass --force-unsafe if you genuinely accept the risk.', `--force-unsafe`);
|
|
76
|
+
}
|
|
77
|
+
if (conflicts && unsafeOverride) {
|
|
78
|
+
printConflicts(conflicts);
|
|
79
|
+
info(' Proceeding because --force-unsafe was provided.');
|
|
80
|
+
}
|
|
81
|
+
// --yes bypasses confirmation but not the non-dry-run execution path.
|
|
82
|
+
// Still print the summary so the operator (or CI log) has a record of what
|
|
83
|
+
// happened even when no prompt was shown.
|
|
84
|
+
if (yes) {
|
|
85
|
+
printSummary(title, summary);
|
|
86
|
+
return 'proceed';
|
|
87
|
+
}
|
|
88
|
+
// Non-TTY without --yes: refuse with a clear message pointing at --yes.
|
|
89
|
+
// Never silently proceed.
|
|
90
|
+
// Node types `isTTY` as `boolean`, but at runtime it is `true | undefined`
|
|
91
|
+
// (absent when the handle is not a TTY). Unusual harnesses (vitest stdio
|
|
92
|
+
// capture, CI spawners, mocked pipes) can also leave it unset on only
|
|
93
|
+
// one handle. We cast to the real runtime type and compare against
|
|
94
|
+
// literal `true` so any falsy variant — false, undefined, or a
|
|
95
|
+
// null-patched mock — routes to the non-interactive path.
|
|
96
|
+
const stdinIsTTY = process.stdin.isTTY;
|
|
97
|
+
const stdoutIsTTY = process.stdout.isTTY;
|
|
98
|
+
const isInteractive = stdinIsTTY === true && stdoutIsTTY === true;
|
|
99
|
+
if (!isInteractive) {
|
|
100
|
+
printSummary(title, summary);
|
|
101
|
+
throw new InvalidArgumentError(`Interactive confirmation not available for "${operation}". ` +
|
|
102
|
+
'Use --yes to run non-interactively (required for CI).', '--yes');
|
|
103
|
+
}
|
|
104
|
+
// Interactive path: print the summary first so the user sees the full
|
|
105
|
+
// picture, then prompt. Cancellation and negative answer both map to
|
|
106
|
+
// 'cancelled' so the caller can return without running anything.
|
|
107
|
+
printSummary(title, summary);
|
|
108
|
+
const confirmed = await confirm({
|
|
109
|
+
message: `${title} — proceed?`,
|
|
110
|
+
initialValue: false,
|
|
111
|
+
});
|
|
112
|
+
if (isCancel(confirmed) || confirmed !== true) {
|
|
113
|
+
return 'cancelled';
|
|
114
|
+
}
|
|
115
|
+
return 'proceed';
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Appends a single JSONL record to `patches/.fireforge-history.jsonl`.
|
|
119
|
+
*
|
|
120
|
+
* Call order matters: append only after the mutation succeeds, never
|
|
121
|
+
* pre-emptively, so rolled-back failures do not leave ghost entries. The log
|
|
122
|
+
* is append-only and advisory — no code path reads it back; it exists purely
|
|
123
|
+
* so operators have a post-hoc audit trail when something goes wrong.
|
|
124
|
+
*
|
|
125
|
+
* @param patchesDir - Path to the patches directory
|
|
126
|
+
* @param entry - Serializable history record
|
|
127
|
+
*/
|
|
128
|
+
export async function appendHistory(patchesDir, entry) {
|
|
129
|
+
await ensureDir(patchesDir);
|
|
130
|
+
const record = {
|
|
131
|
+
ts: new Date().toISOString(),
|
|
132
|
+
...entry,
|
|
133
|
+
};
|
|
134
|
+
const line = `${JSON.stringify(record)}\n`;
|
|
135
|
+
await appendFile(join(patchesDir, HISTORY_LOG_FILENAME), line, 'utf-8');
|
|
136
|
+
}
|
|
137
|
+
//# sourceMappingURL=destructive.js.map
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An edit op produced by {@link computeLineDiff}. Indices are 0-based into the
|
|
3
|
+
* source line arrays; `line` holds the verbatim text so callers do not have to
|
|
4
|
+
* keep the input arrays around while rendering.
|
|
5
|
+
*/
|
|
6
|
+
export interface LineDiffOp {
|
|
7
|
+
type: 'equal' | 'delete' | 'insert';
|
|
8
|
+
oldIndex?: number;
|
|
9
|
+
newIndex?: number;
|
|
10
|
+
line: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* A single line inside a hunk, rendered as one of the three unified-diff
|
|
14
|
+
* markers. Content is the raw line text with no marker prefix so callers can
|
|
15
|
+
* apply their own coloring before the prefix is added.
|
|
16
|
+
*/
|
|
17
|
+
export interface HunkLine {
|
|
18
|
+
marker: ' ' | '-' | '+';
|
|
19
|
+
content: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* A unified-diff hunk. Line numbers are 1-based to match `@@ -X,N +Y,M @@`
|
|
23
|
+
* convention.
|
|
24
|
+
*/
|
|
25
|
+
export interface DiffHunk {
|
|
26
|
+
oldStart: number;
|
|
27
|
+
oldLength: number;
|
|
28
|
+
newStart: number;
|
|
29
|
+
newLength: number;
|
|
30
|
+
lines: HunkLine[];
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* A pre-split line output ready for the caller to apply color and emit. Kept
|
|
34
|
+
* free of color codes so the same structure can be unit-tested without
|
|
35
|
+
* depending on the logger.
|
|
36
|
+
*/
|
|
37
|
+
export interface RenderedDiffLine {
|
|
38
|
+
kind: 'header' | 'context' | 'removed' | 'added';
|
|
39
|
+
text: string;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* LCS-based line diff. Runs in O(m·n) time and memory using a single flat
|
|
43
|
+
* `Int32Array` for the DP table. Guarded by {@link LCS_LINE_LIMIT} so callers
|
|
44
|
+
* on very large files transparently get the coalesced fallback instead of
|
|
45
|
+
* allocating hundreds of megabytes.
|
|
46
|
+
*
|
|
47
|
+
* Behavior note: the backtrack walks diagonals in a deterministic order, so
|
|
48
|
+
* the same input always produces the same op sequence. This matters for
|
|
49
|
+
* snapshot tests and for UI stability when the user re-runs `furnace diff`.
|
|
50
|
+
*/
|
|
51
|
+
export declare function computeLineDiff(oldLines: readonly string[], newLines: readonly string[]): LineDiffOp[];
|
|
52
|
+
/**
|
|
53
|
+
* Groups an op stream into unified-diff hunks with `context` lines of
|
|
54
|
+
* surrounding equal-line context. Adjacent edit runs whose expanded context
|
|
55
|
+
* regions overlap are merged into a single hunk, matching `diff -U N`.
|
|
56
|
+
*
|
|
57
|
+
* Returns an empty array when the two inputs were identical.
|
|
58
|
+
*/
|
|
59
|
+
export declare function buildHunks(ops: readonly LineDiffOp[], context: number): DiffHunk[];
|
|
60
|
+
/**
|
|
61
|
+
* Computes a multi-hunk line diff between two strings. This is the public
|
|
62
|
+
* entry point that {@link furnaceDiffCommand} uses. On normal inputs this
|
|
63
|
+
* runs the exact LCS path and returns a proper multi-hunk diff; on inputs
|
|
64
|
+
* that exceed {@link LCS_LINE_LIMIT} it falls back to a single coalesced
|
|
65
|
+
* hunk so the command stays useful instead of OOMing.
|
|
66
|
+
*/
|
|
67
|
+
export declare function diffLines(oldText: string, newText: string, context?: number): DiffHunk[];
|
|
68
|
+
/**
|
|
69
|
+
* Renders hunks into a flat list of lines the caller can emit one by one.
|
|
70
|
+
* Kept color-free so unit tests can assert exact output and so the caller
|
|
71
|
+
* can apply its own formatting (e.g. `formatErrorText` / `formatSuccessText`).
|
|
72
|
+
*/
|
|
73
|
+
export declare function renderHunks(hunks: readonly DiffHunk[]): RenderedDiffLine[];
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/* eslint-disable @typescript-eslint/no-non-null-assertion --
|
|
3
|
+
* This module is a tight DP/backtrack line-diff whose bounds are checked
|
|
4
|
+
* structurally (every index into `ops`, `dp`, or the line arrays is either
|
|
5
|
+
* guarded by an explicit `k < range.end` / `i <= m` loop condition or sits
|
|
6
|
+
* inside an array built and consumed in the same function). Wrapping every
|
|
7
|
+
* access in an `if (value === undefined) throw` branch would bloat the hot
|
|
8
|
+
* path without catching a real class of bugs — the non-null assertions here
|
|
9
|
+
* encode the invariant the surrounding logic already guarantees.
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Maximum combined LCS table side-length before we bail out of the exact
|
|
13
|
+
* O(m·n) diff and fall back to the simpler single-region coalesce. Chosen so
|
|
14
|
+
* the worst-case Int32Array allocation stays under ~16 MB. Typical Firefox
|
|
15
|
+
* widget files are a few hundred lines, so the fast path handles the
|
|
16
|
+
* everyday case; the fallback exists purely to keep the command usable if
|
|
17
|
+
* someone overrides a very large file.
|
|
18
|
+
*/
|
|
19
|
+
const LCS_LINE_LIMIT = 2000;
|
|
20
|
+
/** Splits a string on `\n`, discarding a single trailing empty line from a terminal newline. */
|
|
21
|
+
function splitLines(text) {
|
|
22
|
+
const lines = text.split('\n');
|
|
23
|
+
if (lines.length > 0 && lines[lines.length - 1] === '')
|
|
24
|
+
lines.pop();
|
|
25
|
+
return lines;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* LCS-based line diff. Runs in O(m·n) time and memory using a single flat
|
|
29
|
+
* `Int32Array` for the DP table. Guarded by {@link LCS_LINE_LIMIT} so callers
|
|
30
|
+
* on very large files transparently get the coalesced fallback instead of
|
|
31
|
+
* allocating hundreds of megabytes.
|
|
32
|
+
*
|
|
33
|
+
* Behavior note: the backtrack walks diagonals in a deterministic order, so
|
|
34
|
+
* the same input always produces the same op sequence. This matters for
|
|
35
|
+
* snapshot tests and for UI stability when the user re-runs `furnace diff`.
|
|
36
|
+
*/
|
|
37
|
+
export function computeLineDiff(oldLines, newLines) {
|
|
38
|
+
const m = oldLines.length;
|
|
39
|
+
const n = newLines.length;
|
|
40
|
+
const dp = new Int32Array((m + 1) * (n + 1));
|
|
41
|
+
const stride = n + 1;
|
|
42
|
+
for (let i = 1; i <= m; i++) {
|
|
43
|
+
const oldLine = oldLines[i - 1];
|
|
44
|
+
const rowOffset = i * stride;
|
|
45
|
+
const prevRowOffset = (i - 1) * stride;
|
|
46
|
+
for (let j = 1; j <= n; j++) {
|
|
47
|
+
if (oldLine === newLines[j - 1]) {
|
|
48
|
+
dp[rowOffset + j] = dp[prevRowOffset + j - 1] + 1;
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
const up = dp[prevRowOffset + j];
|
|
52
|
+
const left = dp[rowOffset + j - 1];
|
|
53
|
+
dp[rowOffset + j] = up >= left ? up : left;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
const ops = [];
|
|
58
|
+
let i = m;
|
|
59
|
+
let j = n;
|
|
60
|
+
while (i > 0 && j > 0) {
|
|
61
|
+
if (oldLines[i - 1] === newLines[j - 1]) {
|
|
62
|
+
ops.push({ type: 'equal', oldIndex: i - 1, newIndex: j - 1, line: oldLines[i - 1] });
|
|
63
|
+
i--;
|
|
64
|
+
j--;
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
const up = dp[(i - 1) * stride + j];
|
|
68
|
+
const left = dp[i * stride + j - 1];
|
|
69
|
+
if (up >= left) {
|
|
70
|
+
ops.push({ type: 'delete', oldIndex: i - 1, line: oldLines[i - 1] });
|
|
71
|
+
i--;
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
ops.push({ type: 'insert', newIndex: j - 1, line: newLines[j - 1] });
|
|
75
|
+
j--;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
while (i > 0) {
|
|
79
|
+
ops.push({ type: 'delete', oldIndex: i - 1, line: oldLines[i - 1] });
|
|
80
|
+
i--;
|
|
81
|
+
}
|
|
82
|
+
while (j > 0) {
|
|
83
|
+
ops.push({ type: 'insert', newIndex: j - 1, line: newLines[j - 1] });
|
|
84
|
+
j--;
|
|
85
|
+
}
|
|
86
|
+
ops.reverse();
|
|
87
|
+
return ops;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Groups an op stream into unified-diff hunks with `context` lines of
|
|
91
|
+
* surrounding equal-line context. Adjacent edit runs whose expanded context
|
|
92
|
+
* regions overlap are merged into a single hunk, matching `diff -U N`.
|
|
93
|
+
*
|
|
94
|
+
* Returns an empty array when the two inputs were identical.
|
|
95
|
+
*/
|
|
96
|
+
export function buildHunks(ops, context) {
|
|
97
|
+
const editRanges = [];
|
|
98
|
+
let i = 0;
|
|
99
|
+
while (i < ops.length) {
|
|
100
|
+
if (ops[i].type !== 'equal') {
|
|
101
|
+
const start = i;
|
|
102
|
+
while (i < ops.length && ops[i].type !== 'equal')
|
|
103
|
+
i++;
|
|
104
|
+
editRanges.push({ start, end: i });
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
i++;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (editRanges.length === 0)
|
|
111
|
+
return [];
|
|
112
|
+
const expanded = [];
|
|
113
|
+
for (const range of editRanges) {
|
|
114
|
+
const start = Math.max(0, range.start - context);
|
|
115
|
+
const end = Math.min(ops.length, range.end + context);
|
|
116
|
+
const prev = expanded[expanded.length - 1];
|
|
117
|
+
if (prev && start <= prev.end) {
|
|
118
|
+
prev.end = Math.max(prev.end, end);
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
expanded.push({ start, end });
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
const hunks = [];
|
|
125
|
+
for (const range of expanded) {
|
|
126
|
+
let oldLine = 1;
|
|
127
|
+
let newLine = 1;
|
|
128
|
+
for (let k = 0; k < range.start; k++) {
|
|
129
|
+
const op = ops[k];
|
|
130
|
+
if (op.type === 'equal') {
|
|
131
|
+
oldLine++;
|
|
132
|
+
newLine++;
|
|
133
|
+
}
|
|
134
|
+
else if (op.type === 'delete') {
|
|
135
|
+
oldLine++;
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
newLine++;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
const hunkLines = [];
|
|
142
|
+
let oldLength = 0;
|
|
143
|
+
let newLength = 0;
|
|
144
|
+
for (let k = range.start; k < range.end; k++) {
|
|
145
|
+
const op = ops[k];
|
|
146
|
+
if (op.type === 'equal') {
|
|
147
|
+
hunkLines.push({ marker: ' ', content: op.line });
|
|
148
|
+
oldLength++;
|
|
149
|
+
newLength++;
|
|
150
|
+
}
|
|
151
|
+
else if (op.type === 'delete') {
|
|
152
|
+
hunkLines.push({ marker: '-', content: op.line });
|
|
153
|
+
oldLength++;
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
hunkLines.push({ marker: '+', content: op.line });
|
|
157
|
+
newLength++;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
hunks.push({
|
|
161
|
+
oldStart: oldLength === 0 ? oldLine - 1 : oldLine,
|
|
162
|
+
oldLength,
|
|
163
|
+
newStart: newLength === 0 ? newLine - 1 : newLine,
|
|
164
|
+
newLength,
|
|
165
|
+
lines: hunkLines,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
return hunks;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Fallback for inputs that exceed {@link LCS_LINE_LIMIT}. Reproduces the
|
|
172
|
+
* original single-region coalesce: match the common prefix and suffix, then
|
|
173
|
+
* emit one hunk containing the middle region plus `context` lines on each
|
|
174
|
+
* side. Intentionally dumb — the fast path handles normal files, and this
|
|
175
|
+
* only keeps the command usable on pathologically large inputs without
|
|
176
|
+
* allocating a huge DP table.
|
|
177
|
+
*/
|
|
178
|
+
function coalescedHunk(oldLines, newLines, context) {
|
|
179
|
+
let firstDiff = 0;
|
|
180
|
+
while (firstDiff < oldLines.length && firstDiff < newLines.length) {
|
|
181
|
+
if (oldLines[firstDiff] !== newLines[firstDiff])
|
|
182
|
+
break;
|
|
183
|
+
firstDiff++;
|
|
184
|
+
}
|
|
185
|
+
let lastOldDiff = oldLines.length - 1;
|
|
186
|
+
let lastNewDiff = newLines.length - 1;
|
|
187
|
+
while (lastOldDiff >= firstDiff && lastNewDiff >= firstDiff) {
|
|
188
|
+
if (oldLines[lastOldDiff] !== newLines[lastNewDiff])
|
|
189
|
+
break;
|
|
190
|
+
lastOldDiff--;
|
|
191
|
+
lastNewDiff--;
|
|
192
|
+
}
|
|
193
|
+
if (lastOldDiff < firstDiff && lastNewDiff < firstDiff) {
|
|
194
|
+
return [];
|
|
195
|
+
}
|
|
196
|
+
const contextStart = Math.max(0, firstDiff - context);
|
|
197
|
+
const contextEndOld = Math.min(oldLines.length - 1, lastOldDiff + context);
|
|
198
|
+
const contextEndNew = Math.min(newLines.length - 1, lastNewDiff + context);
|
|
199
|
+
const hunkLines = [];
|
|
200
|
+
for (let k = contextStart; k < firstDiff; k++) {
|
|
201
|
+
hunkLines.push({ marker: ' ', content: oldLines[k] });
|
|
202
|
+
}
|
|
203
|
+
for (let k = firstDiff; k <= lastOldDiff; k++) {
|
|
204
|
+
hunkLines.push({ marker: '-', content: oldLines[k] });
|
|
205
|
+
}
|
|
206
|
+
for (let k = firstDiff; k <= lastNewDiff; k++) {
|
|
207
|
+
hunkLines.push({ marker: '+', content: newLines[k] });
|
|
208
|
+
}
|
|
209
|
+
const trailingStart = Math.max(lastOldDiff + 1, lastNewDiff + 1);
|
|
210
|
+
const trailingEnd = Math.max(contextEndOld, contextEndNew);
|
|
211
|
+
for (let k = trailingStart; k <= trailingEnd && k < newLines.length; k++) {
|
|
212
|
+
hunkLines.push({ marker: ' ', content: newLines[k] });
|
|
213
|
+
}
|
|
214
|
+
const oldLength = lastOldDiff - firstDiff + 1 + (firstDiff - contextStart);
|
|
215
|
+
const newLength = lastNewDiff - firstDiff + 1 + (firstDiff - contextStart);
|
|
216
|
+
return [
|
|
217
|
+
{
|
|
218
|
+
oldStart: contextStart + 1,
|
|
219
|
+
oldLength,
|
|
220
|
+
newStart: contextStart + 1,
|
|
221
|
+
newLength,
|
|
222
|
+
lines: hunkLines,
|
|
223
|
+
},
|
|
224
|
+
];
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Computes a multi-hunk line diff between two strings. This is the public
|
|
228
|
+
* entry point that {@link furnaceDiffCommand} uses. On normal inputs this
|
|
229
|
+
* runs the exact LCS path and returns a proper multi-hunk diff; on inputs
|
|
230
|
+
* that exceed {@link LCS_LINE_LIMIT} it falls back to a single coalesced
|
|
231
|
+
* hunk so the command stays useful instead of OOMing.
|
|
232
|
+
*/
|
|
233
|
+
export function diffLines(oldText, newText, context = 3) {
|
|
234
|
+
const oldLines = splitLines(oldText);
|
|
235
|
+
const newLines = splitLines(newText);
|
|
236
|
+
if (oldLines.length > LCS_LINE_LIMIT || newLines.length > LCS_LINE_LIMIT) {
|
|
237
|
+
return coalescedHunk(oldLines, newLines, context);
|
|
238
|
+
}
|
|
239
|
+
const ops = computeLineDiff(oldLines, newLines);
|
|
240
|
+
return buildHunks(ops, context);
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Renders hunks into a flat list of lines the caller can emit one by one.
|
|
244
|
+
* Kept color-free so unit tests can assert exact output and so the caller
|
|
245
|
+
* can apply its own formatting (e.g. `formatErrorText` / `formatSuccessText`).
|
|
246
|
+
*/
|
|
247
|
+
export function renderHunks(hunks) {
|
|
248
|
+
const out = [];
|
|
249
|
+
for (const hunk of hunks) {
|
|
250
|
+
out.push({
|
|
251
|
+
kind: 'header',
|
|
252
|
+
text: `@@ -${hunk.oldStart},${hunk.oldLength} +${hunk.newStart},${hunk.newLength} @@`,
|
|
253
|
+
});
|
|
254
|
+
for (const line of hunk.lines) {
|
|
255
|
+
if (line.marker === ' ') {
|
|
256
|
+
out.push({ kind: 'context', text: ` ${line.content}` });
|
|
257
|
+
}
|
|
258
|
+
else if (line.marker === '-') {
|
|
259
|
+
out.push({ kind: 'removed', text: `- ${line.content}` });
|
|
260
|
+
}
|
|
261
|
+
else {
|
|
262
|
+
out.push({ kind: 'added', text: `+ ${line.content}` });
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
return out;
|
|
267
|
+
}
|
|
268
|
+
//# sourceMappingURL=diff-hunks.js.map
|
|
@@ -11,7 +11,7 @@ export type { ProgressCallback } from './firefox-download.js';
|
|
|
11
11
|
export { formatBytes, getFirefoxVersion } from './firefox-extract.js';
|
|
12
12
|
/**
|
|
13
13
|
* Gets the download URL for a Firefox source tarball.
|
|
14
|
-
* @param version - Firefox version (e.g., "
|
|
14
|
+
* @param version - Firefox version (e.g., "146.0esr")
|
|
15
15
|
* @param product - Firefox product type
|
|
16
16
|
* @returns Full URL to the source tarball
|
|
17
17
|
*/
|
package/dist/src/core/firefox.js
CHANGED
|
@@ -17,7 +17,7 @@ export { resolveArchive } from './firefox-archive.js';
|
|
|
17
17
|
export { formatBytes, getFirefoxVersion } from './firefox-extract.js';
|
|
18
18
|
/**
|
|
19
19
|
* Gets the download URL for a Firefox source tarball.
|
|
20
|
-
* @param version - Firefox version (e.g., "
|
|
20
|
+
* @param version - Firefox version (e.g., "146.0esr")
|
|
21
21
|
* @param product - Firefox product type
|
|
22
22
|
* @returns Full URL to the source tarball
|
|
23
23
|
*/
|
|
@@ -1,21 +1,104 @@
|
|
|
1
1
|
import type { CustomComponentConfig, DryRunAction, OverrideComponentConfig, StepError } from '../types/furnace.js';
|
|
2
2
|
import { type RollbackJournal } from './furnace-rollback.js';
|
|
3
|
+
/**
|
|
4
|
+
* Filter deciding which files in an override workspace directory are candidates
|
|
5
|
+
* for copying into the engine. Exported so `furnace remove` can invert apply
|
|
6
|
+
* using the exact same file set — the "files to restore" set is defined as the
|
|
7
|
+
* inverse of the "files apply would have written" set.
|
|
8
|
+
*/
|
|
9
|
+
export declare function isOverrideCopyCandidate(entryName: string, type: OverrideComponentConfig['type']): boolean;
|
|
10
|
+
/** Resolves the engine destination path for a single override-managed file. */
|
|
11
|
+
export declare function getOverrideEngineTargetPath(engineDir: string, config: OverrideComponentConfig, fileName: string, ftlDir: string): string;
|
|
12
|
+
/**
|
|
13
|
+
* Restores a single override-deployed engine file to its pristine HEAD state,
|
|
14
|
+
* inverting whatever apply wrote into that path.
|
|
15
|
+
*
|
|
16
|
+
* Behaviour matches the per-file branch of `restoreOverrideEngineFiles` in
|
|
17
|
+
* `furnace remove`: snapshot first, then either `git restore` (if the file
|
|
18
|
+
* exists in HEAD) or hard-delete (if the override introduced the file). The
|
|
19
|
+
* caller MUST guarantee `engineDir` is a git repository — this helper does
|
|
20
|
+
* not re-check, because both `furnace remove` and `furnace apply` already
|
|
21
|
+
* own the precondition check at their entry points and re-checking on every
|
|
22
|
+
* file would balloon git invocations.
|
|
23
|
+
*
|
|
24
|
+
* Returns the action taken so the caller can produce accurate user-facing
|
|
25
|
+
* counts (`restored` vs `removed`). `noop` means the file was neither in HEAD
|
|
26
|
+
* nor on disk, which can happen when the engine was reset out-of-band — the
|
|
27
|
+
* caller should treat that as a successful no-op rather than an error.
|
|
28
|
+
*/
|
|
29
|
+
export declare function restoreOverrideFileToBaseline(engineDir: string, enginePath: string, journal: RollbackJournal): Promise<'restored' | 'removed' | 'noop'>;
|
|
3
30
|
/** Computes stable checksums for the source files that define a component. */
|
|
4
31
|
export declare function computeComponentChecksums(componentDir: string): Promise<Record<string, string>>;
|
|
32
|
+
/**
|
|
33
|
+
* Returns the filenames present in `previous` that are absent from `current`
|
|
34
|
+
* — i.e. files we know we deployed last time but the workspace has since
|
|
35
|
+
* deleted. The order of returned names is intentionally stable
|
|
36
|
+
* (sorted alphabetically) so test snapshots and CLI output are deterministic.
|
|
37
|
+
*/
|
|
38
|
+
export declare function diffDeletedFiles(previous: Record<string, string>, current: Record<string, string>): string[];
|
|
39
|
+
/**
|
|
40
|
+
* Removes engine copies of files that the developer has deleted from a custom
|
|
41
|
+
* component's workspace since the last apply. `.ftl` files live under the
|
|
42
|
+
* shared Fluent tree (`engine/${FTL_DIR}`); everything else lives under
|
|
43
|
+
* `engine/${config.targetPath}`. Snapshots each removal into the journal so a
|
|
44
|
+
* mid-apply failure can roll the engine back to its pre-undeploy state. Files
|
|
45
|
+
* that are already missing from the engine are silently no-op (the engine
|
|
46
|
+
* may have been reset out-of-band — refusing here would surface a confusing
|
|
47
|
+
* error in a recovery path).
|
|
48
|
+
*
|
|
49
|
+
* Does **not** touch jar.mn or customElements.js: registration churn is the
|
|
50
|
+
* caller's responsibility, since it must coordinate with the new file list
|
|
51
|
+
* computed by the regular apply step that follows.
|
|
52
|
+
*/
|
|
53
|
+
export declare function undeployCustomFiles(engineDir: string, config: CustomComponentConfig, deletedFiles: string[], ftlDir: string, rollbackJournal?: RollbackJournal): Promise<string[]>;
|
|
54
|
+
/**
|
|
55
|
+
* Restores or removes engine copies of files that the developer has deleted
|
|
56
|
+
* from an override component's workspace since the last apply. Each file is
|
|
57
|
+
* routed through `restoreOverrideFileToBaseline`, which restores it from
|
|
58
|
+
* HEAD if it was a Firefox baseline file or hard-deletes it if the override
|
|
59
|
+
* had introduced it.
|
|
60
|
+
*
|
|
61
|
+
* Requires `engineDir` to be a git repository — overrides cannot be inverted
|
|
62
|
+
* without git HEAD as the source of truth. The caller is expected to have
|
|
63
|
+
* already validated this precondition for the apply path; we re-check here
|
|
64
|
+
* so unit tests that exercise this helper directly cannot accidentally
|
|
65
|
+
* silent-fail on a non-git fixture.
|
|
66
|
+
*/
|
|
67
|
+
export declare function undeployOverrideFiles(engineDir: string, config: OverrideComponentConfig, deletedFiles: string[], ftlDir: string, rollbackJournal?: RollbackJournal): Promise<{
|
|
68
|
+
restored: string[];
|
|
69
|
+
removed: string[];
|
|
70
|
+
}>;
|
|
5
71
|
/** Compares current component file checksums against the previously recorded state. */
|
|
6
72
|
export declare function hasComponentChanged(componentDir: string, previousChecksums: Record<string, string>): Promise<boolean>;
|
|
73
|
+
/**
|
|
74
|
+
* Detects whether an override component's deployed files are missing from the
|
|
75
|
+
* engine or differ from the source. Used as a guard before skipping apply on a
|
|
76
|
+
* checksum match, so that reset/download/manual engine edits do not leave the
|
|
77
|
+
* caller with a stale "up to date" report.
|
|
78
|
+
*
|
|
79
|
+
* When `cachedEngineChecksums` is provided (populated on last successful apply),
|
|
80
|
+
* the function computes a SHA-256 hash of the engine file and compares it
|
|
81
|
+
* against the cached value. This avoids reading the full workspace source for
|
|
82
|
+
* the comparison when the engine hash still matches, which is the common case
|
|
83
|
+
* for projects with many components.
|
|
84
|
+
*/
|
|
85
|
+
export declare function hasOverrideEngineDrift(engineDir: string, componentDir: string, config: OverrideComponentConfig, ftlDir: string, cachedEngineChecksums?: Record<string, string>): Promise<boolean>;
|
|
86
|
+
/**
|
|
87
|
+
* Detects whether a custom component's deployed copies, jar.mn entries, or
|
|
88
|
+
* customElements.js registration are missing from the engine or out of sync.
|
|
89
|
+
* Delegates to `checkRegistrationConsistency` so the oracle stays aligned with
|
|
90
|
+
* the validate command.
|
|
91
|
+
*/
|
|
92
|
+
export declare function hasCustomEngineDrift(root: string, name: string, componentDir: string, config: CustomComponentConfig, ftlDir: string): Promise<boolean>;
|
|
7
93
|
/** Applies a custom component into the engine tree and captures registration step errors. */
|
|
8
|
-
export declare function applyCustomComponent(engineDir: string, name: string, componentDir: string, config: CustomComponentConfig, dryRun?: boolean, rollbackJournal?: RollbackJournal): Promise<{
|
|
94
|
+
export declare function applyCustomComponent(engineDir: string, name: string, componentDir: string, config: CustomComponentConfig, ftlDir: string, dryRun?: boolean, rollbackJournal?: RollbackJournal): Promise<{
|
|
9
95
|
affectedPaths: string[];
|
|
10
96
|
stepErrors: StepError[];
|
|
11
97
|
actions?: DryRunAction[];
|
|
12
98
|
}>;
|
|
13
99
|
/** Applies an override component by copying its matching files onto the engine tree. */
|
|
14
|
-
export declare function applyOverrideComponent(engineDir: string, name: string, componentDir: string, config: OverrideComponentConfig, dryRun?: boolean, rollbackJournal?: RollbackJournal): Promise<{
|
|
100
|
+
export declare function applyOverrideComponent(engineDir: string, name: string, componentDir: string, config: OverrideComponentConfig, ftlDir: string, dryRun?: boolean, rollbackJournal?: RollbackJournal): Promise<{
|
|
15
101
|
affectedPaths: string[];
|
|
16
102
|
actions?: DryRunAction[];
|
|
17
103
|
}>;
|
|
18
|
-
|
|
19
|
-
export declare function extractComponentChecksums(allChecksums: Record<string, string> | undefined, type: string, name: string): Record<string, string>;
|
|
20
|
-
/** Prefixes component checksums so they can be stored in the flattened state format. */
|
|
21
|
-
export declare function prefixChecksums(checksums: Record<string, string>, type: string, name: string): Record<string, string>;
|
|
104
|
+
export { extractComponentChecksums, prefixChecksums } from './furnace-checksum-utils.js';
|