@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,84 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* Unified-diff walking helpers shared between per-patch lint rules,
|
|
4
|
+
* cross-patch lint rules, and the export / re-export projection paths.
|
|
5
|
+
*
|
|
6
|
+
* Factored out of `patch-lint.ts` so the per-patch lint body and
|
|
7
|
+
* cross-patch lint body (in `patch-lint-cross.ts`) can both depend on
|
|
8
|
+
* the same diff walkers without inducing a circular import. Callers
|
|
9
|
+
* should keep importing these through `patch-lint.ts` — this file is
|
|
10
|
+
* an implementation detail.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Extracts new-file paths from a unified diff by scanning for `new file mode` markers.
|
|
14
|
+
*/
|
|
15
|
+
export function detectNewFilesInDiff(diffContent) {
|
|
16
|
+
const newFiles = new Set();
|
|
17
|
+
const lines = diffContent.split('\n');
|
|
18
|
+
let currentFile = null;
|
|
19
|
+
for (const line of lines) {
|
|
20
|
+
if (line.startsWith('diff --git')) {
|
|
21
|
+
const match = /^diff --git a\/.+ b\/(.+)$/.exec(line);
|
|
22
|
+
currentFile = match?.[1] ?? null;
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
if (line.startsWith('new file mode') && currentFile) {
|
|
26
|
+
newFiles.add(currentFile);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return newFiles;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Extracts added lines per file from a unified diff.
|
|
33
|
+
* Returns a map of file path → array of added line contents (without the leading `+`).
|
|
34
|
+
*/
|
|
35
|
+
export function extractAddedLinesPerFile(diffContent) {
|
|
36
|
+
const result = new Map();
|
|
37
|
+
const lines = diffContent.split('\n');
|
|
38
|
+
let currentFile = null;
|
|
39
|
+
let inHunk = false;
|
|
40
|
+
for (const line of lines) {
|
|
41
|
+
if (line.startsWith('diff --git')) {
|
|
42
|
+
const match = /^diff --git a\/.+ b\/(.+)$/.exec(line);
|
|
43
|
+
currentFile = match?.[1] ?? null;
|
|
44
|
+
inHunk = false;
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
if (line.startsWith('@@')) {
|
|
48
|
+
inHunk = true;
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
if (inHunk && currentFile && line.startsWith('+') && !line.startsWith('+++')) {
|
|
52
|
+
let arr = result.get(currentFile);
|
|
53
|
+
if (!arr) {
|
|
54
|
+
arr = [];
|
|
55
|
+
result.set(currentFile, arr);
|
|
56
|
+
}
|
|
57
|
+
arr.push(line.slice(1));
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return result;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Builds the `modifiedFileAdditions` map the cross-patch lint expects for
|
|
64
|
+
* a given unified diff. Exposed so callers that construct synthetic /
|
|
65
|
+
* projected `PatchQueueEntry` values (notably `re-export --files`
|
|
66
|
+
* and `export --order`) can populate the field identically to
|
|
67
|
+
* `buildPatchQueueContext`.
|
|
68
|
+
*
|
|
69
|
+
* Matches buildPatchQueueContext's algorithm exactly: skip paths that are
|
|
70
|
+
* created by the diff — those are already covered by the `newFiles` map,
|
|
71
|
+
* which carries full content rather than only the added lines.
|
|
72
|
+
*
|
|
73
|
+
* @param diff - Unified diff content
|
|
74
|
+
*/
|
|
75
|
+
export function buildModifiedFileAdditionsFromDiff(diff) {
|
|
76
|
+
const newFilePaths = detectNewFilesInDiff(diff);
|
|
77
|
+
const result = new Map();
|
|
78
|
+
for (const [file, lines] of extractAddedLinesPerFile(diff)) {
|
|
79
|
+
if (!newFilePaths.has(file))
|
|
80
|
+
result.set(file, lines.join('\n'));
|
|
81
|
+
}
|
|
82
|
+
return result;
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=patch-lint-diff.js.map
|
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
import type { PatchLintIssue } from '../types/commands/index.js';
|
|
2
2
|
import type { FireForgeConfig } from '../types/config.js';
|
|
3
3
|
import { type CommentStyle } from './license-headers.js';
|
|
4
|
+
export { buildPatchQueueContext, collectNewFileCreatorsByPath, type ExtractedSpecifier, extractImportSpecifiers, extractImportSpecifiersWithLines, findForwardImportIgnoreLines, FORWARD_IMPORT_IGNORE_MARKER, isForwardImportableFile, lintPatchQueue, lintPatchQueueDuplicateCreations, lintPatchQueueForwardImports, type PatchQueueContext, type PatchQueueEntry, } from './patch-lint-cross.js';
|
|
5
|
+
export { buildModifiedFileAdditionsFromDiff, detectNewFilesInDiff } from './patch-lint-diff.js';
|
|
4
6
|
/**
|
|
5
7
|
* Detects comment style from file extension for license header checks.
|
|
6
8
|
*/
|
|
7
9
|
export declare function commentStyleForFile(file: string): CommentStyle | null;
|
|
8
|
-
/**
|
|
9
|
-
* Extracts new-file paths from a unified diff by scanning for `new file mode` markers.
|
|
10
|
-
*/
|
|
11
|
-
export declare function detectNewFilesInDiff(diffContent: string): Set<string>;
|
|
12
10
|
/**
|
|
13
11
|
* Lints patched CSS files for introduced raw color values and non-tokenized
|
|
14
12
|
* custom properties.
|
|
@@ -6,6 +6,18 @@ import { verbose } from '../utils/logger.js';
|
|
|
6
6
|
import { hasRawCssColors, stripJsComments } from '../utils/regex.js';
|
|
7
7
|
import { loadFurnaceConfig } from './furnace-config.js';
|
|
8
8
|
import { getLicenseHeader, hasAnyLicenseHeader } from './license-headers.js';
|
|
9
|
+
import { detectNewFilesInDiff, extractAddedLinesPerFile } from './patch-lint-diff.js';
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
// Cross-patch lint re-exports
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
//
|
|
14
|
+
// The cross-patch lint infrastructure (queue context builder, duplicate-
|
|
15
|
+
// creation and forward-import rules, ignore marker) lives in
|
|
16
|
+
// `patch-lint-cross.ts` so the per-patch and cross-patch rule bodies can
|
|
17
|
+
// each stay within the project's per-file line budget. Re-export the
|
|
18
|
+
// public surface so callers continue to import from a single module.
|
|
19
|
+
export { buildPatchQueueContext, collectNewFileCreatorsByPath, extractImportSpecifiers, extractImportSpecifiersWithLines, findForwardImportIgnoreLines, FORWARD_IMPORT_IGNORE_MARKER, isForwardImportableFile, lintPatchQueue, lintPatchQueueDuplicateCreations, lintPatchQueueForwardImports, } from './patch-lint-cross.js';
|
|
20
|
+
export { buildModifiedFileAdditionsFromDiff, detectNewFilesInDiff } from './patch-lint-diff.js';
|
|
9
21
|
// ---------------------------------------------------------------------------
|
|
10
22
|
// Helpers
|
|
11
23
|
// ---------------------------------------------------------------------------
|
|
@@ -29,56 +41,6 @@ export function commentStyleForFile(file) {
|
|
|
29
41
|
return 'js';
|
|
30
42
|
return null;
|
|
31
43
|
}
|
|
32
|
-
/**
|
|
33
|
-
* Extracts new-file paths from a unified diff by scanning for `new file mode` markers.
|
|
34
|
-
*/
|
|
35
|
-
export function detectNewFilesInDiff(diffContent) {
|
|
36
|
-
const newFiles = new Set();
|
|
37
|
-
const lines = diffContent.split('\n');
|
|
38
|
-
let currentFile = null;
|
|
39
|
-
for (const line of lines) {
|
|
40
|
-
if (line.startsWith('diff --git')) {
|
|
41
|
-
const match = /^diff --git a\/.+ b\/(.+)$/.exec(line);
|
|
42
|
-
currentFile = match?.[1] ?? null;
|
|
43
|
-
continue;
|
|
44
|
-
}
|
|
45
|
-
if (line.startsWith('new file mode') && currentFile) {
|
|
46
|
-
newFiles.add(currentFile);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
return newFiles;
|
|
50
|
-
}
|
|
51
|
-
/**
|
|
52
|
-
* Extracts added lines per file from a unified diff.
|
|
53
|
-
* Returns a map of file path → array of added line contents (without the leading `+`).
|
|
54
|
-
*/
|
|
55
|
-
function extractAddedLinesPerFile(diffContent) {
|
|
56
|
-
const result = new Map();
|
|
57
|
-
const lines = diffContent.split('\n');
|
|
58
|
-
let currentFile = null;
|
|
59
|
-
let inHunk = false;
|
|
60
|
-
for (const line of lines) {
|
|
61
|
-
if (line.startsWith('diff --git')) {
|
|
62
|
-
const match = /^diff --git a\/.+ b\/(.+)$/.exec(line);
|
|
63
|
-
currentFile = match?.[1] ?? null;
|
|
64
|
-
inHunk = false;
|
|
65
|
-
continue;
|
|
66
|
-
}
|
|
67
|
-
if (line.startsWith('@@')) {
|
|
68
|
-
inHunk = true;
|
|
69
|
-
continue;
|
|
70
|
-
}
|
|
71
|
-
if (inHunk && currentFile && line.startsWith('+') && !line.startsWith('+++')) {
|
|
72
|
-
let arr = result.get(currentFile);
|
|
73
|
-
if (!arr) {
|
|
74
|
-
arr = [];
|
|
75
|
-
result.set(currentFile, arr);
|
|
76
|
-
}
|
|
77
|
-
arr.push(line.slice(1));
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
return result;
|
|
81
|
-
}
|
|
82
44
|
// ---------------------------------------------------------------------------
|
|
83
45
|
// CSS lint
|
|
84
46
|
// ---------------------------------------------------------------------------
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* Filesystem-based lock for serializing patch directory mutations.
|
|
4
4
|
*/
|
|
5
5
|
import { join } from 'node:path';
|
|
6
|
+
import { FireForgeError } from '../errors/base.js';
|
|
6
7
|
import { PatchError } from '../errors/patch.js';
|
|
7
8
|
import { toError } from '../utils/errors.js';
|
|
8
9
|
import { withFileLock } from './file-lock.js';
|
|
@@ -20,7 +21,7 @@ export async function withPatchDirectoryLock(patchesDir, operation) {
|
|
|
20
21
|
onStaleLockMessage: (ageMs) => `Removing stale patch lock (age: ${Math.round(ageMs / 1000)}s). ` +
|
|
21
22
|
'A previous fireforge process may have crashed.',
|
|
22
23
|
}).catch((error) => {
|
|
23
|
-
if (error instanceof
|
|
24
|
+
if (error instanceof FireForgeError) {
|
|
24
25
|
throw error;
|
|
25
26
|
}
|
|
26
27
|
throw new PatchError(toError(error).message);
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Manifest I/O: load, save, and
|
|
2
|
+
* Manifest I/O: load, save, and mutating operations for patches.json.
|
|
3
|
+
*
|
|
4
|
+
* Mutations (add / remove / renumber) are intended to be the only sanctioned
|
|
5
|
+
* way to change on-disk manifest state. They run under the shared patch
|
|
6
|
+
* directory lock so concurrent commands cannot race each other into
|
|
7
|
+
* inconsistent manifests.
|
|
3
8
|
*/
|
|
9
|
+
import { FireForgeError } from '../errors/base.js';
|
|
4
10
|
import type { PatchesManifest, PatchMetadata } from '../types/commands/index.js';
|
|
5
11
|
/** Filename for the patches manifest */
|
|
6
12
|
export declare const PATCHES_MANIFEST = "patches.json";
|
|
@@ -34,3 +40,98 @@ export declare function savePatchesManifest(patchesDir: string, manifest: Patche
|
|
|
34
40
|
* @param removeFilenames - Optional filenames to remove in the same read-modify-write cycle
|
|
35
41
|
*/
|
|
36
42
|
export declare function addPatchToManifest(patchesDir: string, metadata: PatchMetadata, removeFilenames?: string[]): Promise<void>;
|
|
43
|
+
/**
|
|
44
|
+
* Removes a single patch entry from the manifest by filename. Leaves the
|
|
45
|
+
* ordinal gap in place — callers wanting to close the gap must use
|
|
46
|
+
* {@link renumberPatchesInManifest} explicitly. This matches the spec: delete
|
|
47
|
+
* is a row removal, not a resequencing.
|
|
48
|
+
*
|
|
49
|
+
* Not atomic with any on-disk patch file deletion; callers are expected to
|
|
50
|
+
* remove the .patch file separately under the same lock.
|
|
51
|
+
*
|
|
52
|
+
* @param patchesDir - Path to the patches directory
|
|
53
|
+
* @param filename - Filename of the patch to remove from the manifest
|
|
54
|
+
* @returns True when the manifest was written (i.e. an entry was removed),
|
|
55
|
+
* false when no matching entry existed
|
|
56
|
+
*/
|
|
57
|
+
export declare function removePatchFromManifest(patchesDir: string, filename: string): Promise<boolean>;
|
|
58
|
+
/**
|
|
59
|
+
* A single rename step in a {@link renumberPatchesInManifest} plan.
|
|
60
|
+
*/
|
|
61
|
+
export interface PatchRenameEntry {
|
|
62
|
+
/** New filename (e.g. `005-ui-sidebar.patch`). */
|
|
63
|
+
newFilename: string;
|
|
64
|
+
/** New numeric order — must match the prefix of `newFilename`. */
|
|
65
|
+
newOrder: number;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Renames patch files on disk and rewrites the corresponding manifest rows
|
|
69
|
+
* atomically-ish: file renames use a two-phase staging strategy (rename each
|
|
70
|
+
* entry to a unique temp filename first, then rename the temp to its final
|
|
71
|
+
* target) so cycles like `003 → 005` while `005` also moves do not collide.
|
|
72
|
+
*
|
|
73
|
+
* The manifest is rewritten once at the end with all new filenames and
|
|
74
|
+
* orders. Failure semantics:
|
|
75
|
+
*
|
|
76
|
+
* - **Phase 1 (stage)**: rolls back by renaming staged files back to
|
|
77
|
+
* their originals. Manifest is untouched. Best-effort — a rollback
|
|
78
|
+
* rename failure is warned but not re-thrown.
|
|
79
|
+
* - **Phase 2 (stage → final)**: rolls back to the pre-operation state
|
|
80
|
+
* by reversing every partial step: files already at their final
|
|
81
|
+
* names are renamed back to staging, and all staged files are then
|
|
82
|
+
* renamed back to their originals. The manifest is untouched. If
|
|
83
|
+
* the rollback itself fails midway, the thrown error is augmented
|
|
84
|
+
* with a description of the residue so the operator can inspect.
|
|
85
|
+
* - **Phase 3 (manifest write)**: by this point all files are on disk
|
|
86
|
+
* at their new names; a manifest write failure will roll the files
|
|
87
|
+
* back to their original names before re-throwing so the directory
|
|
88
|
+
* and manifest stay in agreement. A rollback failure at this stage
|
|
89
|
+
* is warned (manifest was never mutated) and the original error is
|
|
90
|
+
* re-thrown.
|
|
91
|
+
*
|
|
92
|
+
* Does not sort the rename map for the caller — the map is the authoritative
|
|
93
|
+
* plan. Entries not present in the map keep their existing filename and
|
|
94
|
+
* order.
|
|
95
|
+
*
|
|
96
|
+
* @param patchesDir - Path to the patches directory
|
|
97
|
+
* @param renameMap - Map from existing filename → new filename/order
|
|
98
|
+
*/
|
|
99
|
+
export declare function renumberPatchesInManifest(patchesDir: string, renameMap: Map<string, PatchRenameEntry>): Promise<void>;
|
|
100
|
+
/**
|
|
101
|
+
* Thrown when {@link removePatchFileAndManifest} cannot complete the file
|
|
102
|
+
* delete AND cannot restore the manifest row afterward, so the on-disk
|
|
103
|
+
* state and manifest state are known to disagree. Carries both the
|
|
104
|
+
* primary delete error and the rollback error so the caller (and the
|
|
105
|
+
* operator) can see the full failure chain instead of only the original
|
|
106
|
+
* error with a warning about the rollback buried in logs.
|
|
107
|
+
*
|
|
108
|
+
* Extends {@link FireForgeError} so the CLI top-level handler routes it
|
|
109
|
+
* through the rich-error formatter rather than the generic unexpected-error
|
|
110
|
+
* path; the dedicated `.name` is kept so programmatic callers and tests
|
|
111
|
+
* can still distinguish it with `instanceof PatchDeleteRollbackError`.
|
|
112
|
+
*/
|
|
113
|
+
export declare class PatchDeleteRollbackError extends FireForgeError {
|
|
114
|
+
readonly filename: string;
|
|
115
|
+
readonly deleteError: Error;
|
|
116
|
+
readonly rollbackError: Error;
|
|
117
|
+
readonly code: 6;
|
|
118
|
+
constructor(filename: string, deleteError: Error, rollbackError: Error);
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Deletes both a patch file on disk and its manifest row under the caller's
|
|
122
|
+
* lock. This is a convenience for the `patch delete` command; callers that
|
|
123
|
+
* need different ordering (e.g. deleting the file first without touching the
|
|
124
|
+
* manifest) should call the primitives separately.
|
|
125
|
+
*
|
|
126
|
+
* Failure semantics: if the manifest row was already removed and the
|
|
127
|
+
* file deletion then fails, the original manifest is restored best-effort
|
|
128
|
+
* and the delete error is re-thrown. If the restore itself also fails,
|
|
129
|
+
* a {@link PatchDeleteRollbackError} is thrown that carries both the
|
|
130
|
+
* delete error and the rollback error so neither is hidden behind a
|
|
131
|
+
* warning log. Callers can detect the compound failure with
|
|
132
|
+
* `instanceof PatchDeleteRollbackError`.
|
|
133
|
+
*
|
|
134
|
+
* @param patchesDir - Path to the patches directory
|
|
135
|
+
* @param filename - Patch filename to delete
|
|
136
|
+
*/
|
|
137
|
+
export declare function removePatchFileAndManifest(patchesDir: string, filename: string): Promise<void>;
|
|
@@ -1,10 +1,20 @@
|
|
|
1
1
|
// SPDX-License-Identifier: EUPL-1.2
|
|
2
2
|
/**
|
|
3
|
-
* Manifest I/O: load, save, and
|
|
3
|
+
* Manifest I/O: load, save, and mutating operations for patches.json.
|
|
4
|
+
*
|
|
5
|
+
* Mutations (add / remove / renumber) are intended to be the only sanctioned
|
|
6
|
+
* way to change on-disk manifest state. They run under the shared patch
|
|
7
|
+
* directory lock so concurrent commands cannot race each other into
|
|
8
|
+
* inconsistent manifests.
|
|
4
9
|
*/
|
|
10
|
+
import { randomUUID } from 'node:crypto';
|
|
11
|
+
import { rename } from 'node:fs/promises';
|
|
5
12
|
import { join } from 'node:path';
|
|
13
|
+
import { FireForgeError } from '../errors/base.js';
|
|
14
|
+
import { ExitCode } from '../errors/codes.js';
|
|
6
15
|
import { toError } from '../utils/errors.js';
|
|
7
|
-
import { pathExists, readJson, writeJson } from '../utils/fs.js';
|
|
16
|
+
import { pathExists, readJson, removeFile, writeJson } from '../utils/fs.js';
|
|
17
|
+
import { warn } from '../utils/logger.js';
|
|
8
18
|
import { validatePatchesManifest } from './patch-manifest-validate.js';
|
|
9
19
|
/** Filename for the patches manifest */
|
|
10
20
|
export const PATCHES_MANIFEST = 'patches.json';
|
|
@@ -74,4 +84,262 @@ export async function addPatchToManifest(patchesDir, metadata, removeFilenames)
|
|
|
74
84
|
manifest.patches.sort((a, b) => a.order - b.order);
|
|
75
85
|
await savePatchesManifest(patchesDir, manifest);
|
|
76
86
|
}
|
|
87
|
+
/**
|
|
88
|
+
* Removes a single patch entry from the manifest by filename. Leaves the
|
|
89
|
+
* ordinal gap in place — callers wanting to close the gap must use
|
|
90
|
+
* {@link renumberPatchesInManifest} explicitly. This matches the spec: delete
|
|
91
|
+
* is a row removal, not a resequencing.
|
|
92
|
+
*
|
|
93
|
+
* Not atomic with any on-disk patch file deletion; callers are expected to
|
|
94
|
+
* remove the .patch file separately under the same lock.
|
|
95
|
+
*
|
|
96
|
+
* @param patchesDir - Path to the patches directory
|
|
97
|
+
* @param filename - Filename of the patch to remove from the manifest
|
|
98
|
+
* @returns True when the manifest was written (i.e. an entry was removed),
|
|
99
|
+
* false when no matching entry existed
|
|
100
|
+
*/
|
|
101
|
+
export async function removePatchFromManifest(patchesDir, filename) {
|
|
102
|
+
const manifest = await loadPatchesManifest(patchesDir);
|
|
103
|
+
if (!manifest)
|
|
104
|
+
return false;
|
|
105
|
+
const originalLength = manifest.patches.length;
|
|
106
|
+
manifest.patches = manifest.patches.filter((p) => p.filename !== filename);
|
|
107
|
+
if (manifest.patches.length === originalLength) {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
await savePatchesManifest(patchesDir, manifest);
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Renames patch files on disk and rewrites the corresponding manifest rows
|
|
115
|
+
* atomically-ish: file renames use a two-phase staging strategy (rename each
|
|
116
|
+
* entry to a unique temp filename first, then rename the temp to its final
|
|
117
|
+
* target) so cycles like `003 → 005` while `005` also moves do not collide.
|
|
118
|
+
*
|
|
119
|
+
* The manifest is rewritten once at the end with all new filenames and
|
|
120
|
+
* orders. Failure semantics:
|
|
121
|
+
*
|
|
122
|
+
* - **Phase 1 (stage)**: rolls back by renaming staged files back to
|
|
123
|
+
* their originals. Manifest is untouched. Best-effort — a rollback
|
|
124
|
+
* rename failure is warned but not re-thrown.
|
|
125
|
+
* - **Phase 2 (stage → final)**: rolls back to the pre-operation state
|
|
126
|
+
* by reversing every partial step: files already at their final
|
|
127
|
+
* names are renamed back to staging, and all staged files are then
|
|
128
|
+
* renamed back to their originals. The manifest is untouched. If
|
|
129
|
+
* the rollback itself fails midway, the thrown error is augmented
|
|
130
|
+
* with a description of the residue so the operator can inspect.
|
|
131
|
+
* - **Phase 3 (manifest write)**: by this point all files are on disk
|
|
132
|
+
* at their new names; a manifest write failure will roll the files
|
|
133
|
+
* back to their original names before re-throwing so the directory
|
|
134
|
+
* and manifest stay in agreement. A rollback failure at this stage
|
|
135
|
+
* is warned (manifest was never mutated) and the original error is
|
|
136
|
+
* re-thrown.
|
|
137
|
+
*
|
|
138
|
+
* Does not sort the rename map for the caller — the map is the authoritative
|
|
139
|
+
* plan. Entries not present in the map keep their existing filename and
|
|
140
|
+
* order.
|
|
141
|
+
*
|
|
142
|
+
* @param patchesDir - Path to the patches directory
|
|
143
|
+
* @param renameMap - Map from existing filename → new filename/order
|
|
144
|
+
*/
|
|
145
|
+
export async function renumberPatchesInManifest(patchesDir, renameMap) {
|
|
146
|
+
if (renameMap.size === 0)
|
|
147
|
+
return;
|
|
148
|
+
const manifest = await loadPatchesManifest(patchesDir);
|
|
149
|
+
if (!manifest) {
|
|
150
|
+
throw new Error('Cannot renumber patches: patches.json is missing.');
|
|
151
|
+
}
|
|
152
|
+
// Phase 1: rename each old filename to a unique temp staging name so the
|
|
153
|
+
// later rename to the final target cannot collide with another entry
|
|
154
|
+
// currently occupying that slot.
|
|
155
|
+
const stagingId = randomUUID();
|
|
156
|
+
const stagedRenames = [];
|
|
157
|
+
try {
|
|
158
|
+
for (const [oldFilename, entry] of renameMap) {
|
|
159
|
+
const oldPath = join(patchesDir, oldFilename);
|
|
160
|
+
if (!(await pathExists(oldPath))) {
|
|
161
|
+
throw new Error(`Cannot renumber: patch file is missing on disk: ${oldFilename}`);
|
|
162
|
+
}
|
|
163
|
+
const stagedName = `.fireforge-renumber-${stagingId}-${oldFilename}`;
|
|
164
|
+
const stagedPath = join(patchesDir, stagedName);
|
|
165
|
+
await rename(oldPath, stagedPath);
|
|
166
|
+
stagedRenames.push({ from: oldFilename, staged: stagedName, toEntry: entry });
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
catch (error) {
|
|
170
|
+
// Roll back phase 1: put any already-staged files back.
|
|
171
|
+
for (const { from, staged } of stagedRenames) {
|
|
172
|
+
try {
|
|
173
|
+
await rename(join(patchesDir, staged), join(patchesDir, from));
|
|
174
|
+
}
|
|
175
|
+
catch (rollbackError) {
|
|
176
|
+
warn(`Rollback warning: could not restore ${from} from staging: ${toError(rollbackError).message}`);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
throw error;
|
|
180
|
+
}
|
|
181
|
+
// Phase 2: rename each staged file to its final target, tracking which
|
|
182
|
+
// entries have completed so we can reverse the partial state on failure.
|
|
183
|
+
const completedFinalRenames = [];
|
|
184
|
+
try {
|
|
185
|
+
for (const stagedEntry of stagedRenames) {
|
|
186
|
+
const { staged, toEntry } = stagedEntry;
|
|
187
|
+
const targetPath = join(patchesDir, toEntry.newFilename);
|
|
188
|
+
if (await pathExists(targetPath)) {
|
|
189
|
+
throw new Error(`Cannot renumber: target patch filename already exists on disk: ${toEntry.newFilename}`);
|
|
190
|
+
}
|
|
191
|
+
await rename(join(patchesDir, staged), targetPath);
|
|
192
|
+
completedFinalRenames.push(stagedEntry);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
catch (error) {
|
|
196
|
+
// Phase 2 rollback — reverse both the partial final-name moves and
|
|
197
|
+
// the phase-1 staging. This collapses the directory back to its
|
|
198
|
+
// pre-operation filenames so the manifest (which was never
|
|
199
|
+
// touched) remains consistent with what is on disk. If any
|
|
200
|
+
// individual rollback step itself fails, we warn with the residue
|
|
201
|
+
// filename so the operator can finish the cleanup by hand, but we
|
|
202
|
+
// still re-throw the original error so the caller sees the real
|
|
203
|
+
// cause.
|
|
204
|
+
const residue = [];
|
|
205
|
+
for (const completed of completedFinalRenames) {
|
|
206
|
+
try {
|
|
207
|
+
await rename(join(patchesDir, completed.toEntry.newFilename), join(patchesDir, completed.staged));
|
|
208
|
+
}
|
|
209
|
+
catch (rollbackError) {
|
|
210
|
+
residue.push(completed.toEntry.newFilename);
|
|
211
|
+
warn(`Rollback warning: could not revert ${completed.toEntry.newFilename} to staging: ${toError(rollbackError).message}`);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
for (const stagedEntry of stagedRenames) {
|
|
215
|
+
try {
|
|
216
|
+
await rename(join(patchesDir, stagedEntry.staged), join(patchesDir, stagedEntry.from));
|
|
217
|
+
}
|
|
218
|
+
catch (rollbackError) {
|
|
219
|
+
residue.push(stagedEntry.staged);
|
|
220
|
+
warn(`Rollback warning: could not restore ${stagedEntry.from} from staging: ${toError(rollbackError).message}`);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
if (residue.length > 0) {
|
|
224
|
+
warn(`Renumber phase 2 rollback left residue files (pattern: .fireforge-renumber-${stagingId}-*). ` +
|
|
225
|
+
`Inspect ${patchesDir} and remove or rename: ${residue.join(', ')}`);
|
|
226
|
+
}
|
|
227
|
+
throw error;
|
|
228
|
+
}
|
|
229
|
+
// Phase 3: rewrite the manifest rows. Any entry without a rename keeps its
|
|
230
|
+
// existing metadata; entries in the map get their filename + order
|
|
231
|
+
// updated. Sort by the new order so the manifest remains ordered.
|
|
232
|
+
const filenameUpdates = new Map();
|
|
233
|
+
for (const [oldFilename, entry] of renameMap) {
|
|
234
|
+
filenameUpdates.set(oldFilename, entry);
|
|
235
|
+
}
|
|
236
|
+
const updatedPatches = manifest.patches.map((p) => {
|
|
237
|
+
const update = filenameUpdates.get(p.filename);
|
|
238
|
+
if (!update)
|
|
239
|
+
return p;
|
|
240
|
+
return {
|
|
241
|
+
...p,
|
|
242
|
+
filename: update.newFilename,
|
|
243
|
+
order: update.newOrder,
|
|
244
|
+
};
|
|
245
|
+
});
|
|
246
|
+
updatedPatches.sort((a, b) => a.order - b.order || a.filename.localeCompare(b.filename));
|
|
247
|
+
try {
|
|
248
|
+
await savePatchesManifest(patchesDir, {
|
|
249
|
+
...manifest,
|
|
250
|
+
patches: updatedPatches,
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
catch (error) {
|
|
254
|
+
// Phase 3 rollback: reverse every completed rename. The manifest
|
|
255
|
+
// save failed before it could be persisted, so the on-disk state
|
|
256
|
+
// must match what the manifest still records. Best-effort: any
|
|
257
|
+
// individual step that fails gets warned; the original save
|
|
258
|
+
// error is always re-thrown so the caller sees the real cause.
|
|
259
|
+
for (const completed of completedFinalRenames) {
|
|
260
|
+
try {
|
|
261
|
+
await rename(join(patchesDir, completed.toEntry.newFilename), join(patchesDir, completed.from));
|
|
262
|
+
}
|
|
263
|
+
catch (rollbackError) {
|
|
264
|
+
warn(`Rollback warning: could not revert ${completed.toEntry.newFilename} → ${completed.from} after manifest save failed: ${toError(rollbackError).message}`);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
throw error;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Thrown when {@link removePatchFileAndManifest} cannot complete the file
|
|
272
|
+
* delete AND cannot restore the manifest row afterward, so the on-disk
|
|
273
|
+
* state and manifest state are known to disagree. Carries both the
|
|
274
|
+
* primary delete error and the rollback error so the caller (and the
|
|
275
|
+
* operator) can see the full failure chain instead of only the original
|
|
276
|
+
* error with a warning about the rollback buried in logs.
|
|
277
|
+
*
|
|
278
|
+
* Extends {@link FireForgeError} so the CLI top-level handler routes it
|
|
279
|
+
* through the rich-error formatter rather than the generic unexpected-error
|
|
280
|
+
* path; the dedicated `.name` is kept so programmatic callers and tests
|
|
281
|
+
* can still distinguish it with `instanceof PatchDeleteRollbackError`.
|
|
282
|
+
*/
|
|
283
|
+
export class PatchDeleteRollbackError extends FireForgeError {
|
|
284
|
+
filename;
|
|
285
|
+
deleteError;
|
|
286
|
+
rollbackError;
|
|
287
|
+
code = ExitCode.PATCH_ERROR;
|
|
288
|
+
constructor(filename, deleteError, rollbackError) {
|
|
289
|
+
super(`Failed to delete ${filename}, AND failed to restore patches.json afterward. ` +
|
|
290
|
+
`The patch directory is now inconsistent: the manifest no longer lists ${filename} ` +
|
|
291
|
+
`but the patch file may still exist on disk. ` +
|
|
292
|
+
`Delete error: ${deleteError.message}. ` +
|
|
293
|
+
`Manifest rollback error: ${rollbackError.message}. ` +
|
|
294
|
+
`Inspect ${filename} in the patches directory and either remove it or restore the manifest row by hand.`);
|
|
295
|
+
this.filename = filename;
|
|
296
|
+
this.deleteError = deleteError;
|
|
297
|
+
this.rollbackError = rollbackError;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Deletes both a patch file on disk and its manifest row under the caller's
|
|
302
|
+
* lock. This is a convenience for the `patch delete` command; callers that
|
|
303
|
+
* need different ordering (e.g. deleting the file first without touching the
|
|
304
|
+
* manifest) should call the primitives separately.
|
|
305
|
+
*
|
|
306
|
+
* Failure semantics: if the manifest row was already removed and the
|
|
307
|
+
* file deletion then fails, the original manifest is restored best-effort
|
|
308
|
+
* and the delete error is re-thrown. If the restore itself also fails,
|
|
309
|
+
* a {@link PatchDeleteRollbackError} is thrown that carries both the
|
|
310
|
+
* delete error and the rollback error so neither is hidden behind a
|
|
311
|
+
* warning log. Callers can detect the compound failure with
|
|
312
|
+
* `instanceof PatchDeleteRollbackError`.
|
|
313
|
+
*
|
|
314
|
+
* @param patchesDir - Path to the patches directory
|
|
315
|
+
* @param filename - Patch filename to delete
|
|
316
|
+
*/
|
|
317
|
+
export async function removePatchFileAndManifest(patchesDir, filename) {
|
|
318
|
+
const patchPath = join(patchesDir, filename);
|
|
319
|
+
const originalManifest = await loadPatchesManifest(patchesDir);
|
|
320
|
+
const removedFromManifest = await removePatchFromManifest(patchesDir, filename);
|
|
321
|
+
try {
|
|
322
|
+
if (await pathExists(patchPath)) {
|
|
323
|
+
await removeFile(patchPath);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
catch (error) {
|
|
327
|
+
const deleteError = toError(error);
|
|
328
|
+
if (removedFromManifest && originalManifest) {
|
|
329
|
+
try {
|
|
330
|
+
await savePatchesManifest(patchesDir, originalManifest);
|
|
331
|
+
}
|
|
332
|
+
catch (rollbackError) {
|
|
333
|
+
// Compound failure: both the delete and the rollback failed,
|
|
334
|
+
// so the directory is in a known-inconsistent state. Throw a
|
|
335
|
+
// dedicated error type that carries both causes so the
|
|
336
|
+
// operator's log shows the complete picture instead of the
|
|
337
|
+
// original delete error with a warning about the rollback
|
|
338
|
+
// buried in stderr.
|
|
339
|
+
throw new PatchDeleteRollbackError(filename, deleteError, toError(rollbackError));
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
throw deleteError;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
77
345
|
//# sourceMappingURL=patch-manifest-io.js.map
|
|
@@ -43,6 +43,6 @@ export declare function validatePatchIntegrity(patchesDir: string, engineDir: st
|
|
|
43
43
|
* manifest read-modify-write cycle.
|
|
44
44
|
* @param patchesDir - Path to the patches directory
|
|
45
45
|
* @param filenames - Patch filenames to update
|
|
46
|
-
* @param newVersion - Version string to set (e.g. "
|
|
46
|
+
* @param newVersion - Version string to set (e.g. "146.0esr")
|
|
47
47
|
*/
|
|
48
48
|
export declare function stampPatchVersions(patchesDir: string, filenames: string[], newVersion: string): Promise<void>;
|
|
@@ -103,7 +103,7 @@ export async function validatePatchIntegrity(patchesDir, engineDir) {
|
|
|
103
103
|
* manifest read-modify-write cycle.
|
|
104
104
|
* @param patchesDir - Path to the patches directory
|
|
105
105
|
* @param filenames - Patch filenames to update
|
|
106
|
-
* @param newVersion - Version string to set (e.g. "
|
|
106
|
+
* @param newVersion - Version string to set (e.g. "146.0esr")
|
|
107
107
|
*/
|
|
108
108
|
export async function stampPatchVersions(patchesDir, filenames, newVersion) {
|
|
109
109
|
const manifest = await loadPatchesManifest(patchesDir);
|
|
@@ -6,6 +6,6 @@
|
|
|
6
6
|
*/
|
|
7
7
|
export type { PatchManifestConsistencyIssue } from './patch-manifest-consistency.js';
|
|
8
8
|
export { rebuildPatchesManifest, validatePatchesManifestConsistency, } from './patch-manifest-consistency.js';
|
|
9
|
-
export { addPatchToManifest, loadPatchesManifest, PATCHES_MANIFEST, savePatchesManifest, } from './patch-manifest-io.js';
|
|
9
|
+
export { addPatchToManifest, loadPatchesManifest, PatchDeleteRollbackError, PATCHES_MANIFEST, type PatchRenameEntry, removePatchFileAndManifest, removePatchFromManifest, renumberPatchesInManifest, savePatchesManifest, } from './patch-manifest-io.js';
|
|
10
10
|
export { checkVersionCompatibility, findPatchesAffectingFile, getClaimedFiles, stampPatchVersions, validatePatchIntegrity, } from './patch-manifest-query.js';
|
|
11
11
|
export { validatePatchesManifest } from './patch-manifest-validate.js';
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* is an implementation detail.
|
|
7
7
|
*/
|
|
8
8
|
export { rebuildPatchesManifest, validatePatchesManifestConsistency, } from './patch-manifest-consistency.js';
|
|
9
|
-
export { addPatchToManifest, loadPatchesManifest, PATCHES_MANIFEST, savePatchesManifest, } from './patch-manifest-io.js';
|
|
9
|
+
export { addPatchToManifest, loadPatchesManifest, PatchDeleteRollbackError, PATCHES_MANIFEST, removePatchFileAndManifest, removePatchFromManifest, renumberPatchesInManifest, savePatchesManifest, } from './patch-manifest-io.js';
|
|
10
10
|
export { checkVersionCompatibility, findPatchesAffectingFile, getClaimedFiles, stampPatchVersions, validatePatchIntegrity, } from './patch-manifest-query.js';
|
|
11
11
|
export { validatePatchesManifest } from './patch-manifest-validate.js';
|
|
12
12
|
//# sourceMappingURL=patch-manifest.js.map
|
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
* Pure content transformation functions for patch operations.
|
|
3
3
|
* These operate on file content strings without filesystem side effects.
|
|
4
4
|
*/
|
|
5
|
+
/**
|
|
6
|
+
* Extracts the complete file content from a "new file" patch given a raw
|
|
7
|
+
* diff string already in memory. Callers with a patch file path should
|
|
8
|
+
* prefer {@link extractNewFileContent}; this helper exists for code paths
|
|
9
|
+
* that already hold the diff (e.g. the in-flight export planner) and do
|
|
10
|
+
* not want to round-trip through the filesystem.
|
|
11
|
+
*
|
|
12
|
+
* @param diff - Raw unified-diff content
|
|
13
|
+
* @param targetFile - Optional target file to scope extraction to
|
|
14
|
+
* @returns The file content that the patch would create
|
|
15
|
+
*/
|
|
16
|
+
export declare function extractNewFileContentFromDiff(diff: string, targetFile?: string): string;
|
|
5
17
|
/**
|
|
6
18
|
* Extracts the complete file content from a "new file" patch.
|
|
7
19
|
* When targetFile is provided, only extracts content for that file
|