@hominis/fireforge 0.27.3 → 0.28.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/dist/src/commands/source.js +3 -0
- package/dist/src/core/patch-artifact-normalize.d.ts +5 -5
- package/dist/src/core/patch-artifact-normalize.js +6 -6
- package/dist/src/core/patch-export-metadata.js +12 -45
- package/dist/src/core/patch-export-update.js +2 -2
- package/dist/src/core/patch-manifest-io.d.ts +21 -0
- package/dist/src/core/patch-manifest-io.js +57 -0
- package/dist/src/core/patch-manifest-query.js +13 -21
- package/dist/src/core/patch-manifest.d.ts +1 -1
- package/dist/src/core/patch-manifest.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.28.0
|
|
4
|
+
|
|
5
|
+
- Added the product-resolved Firefox source archive URL to `source set` output so pinned checksums can be verified against the exact archive target before download.
|
|
6
|
+
- Fixed re-export serialization so blank context lines keep their unified-diff context marker, preventing FireForge-generated patches from producing false patch-owned drift warnings during `verify`.
|
|
7
|
+
- Fixed partial `re-export` manifest writes so legacy source metadata is preserved on unselected patch rows unless `--stamp` or another source metadata update explicitly targets them.
|
|
8
|
+
- Added regression coverage for targeted and full stamped re-export round-trips with blank context lines.
|
|
9
|
+
|
|
3
10
|
## 0.27.3
|
|
4
11
|
|
|
5
12
|
- Fixed `firefox-devedition` source downloads so archive resolution uses `/pub/devedition/releases`.
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// SPDX-License-Identifier: EUPL-1.2
|
|
2
2
|
import { Option } from 'commander';
|
|
3
3
|
import { configExists, loadRawConfigDocument, validateConfig, withConfigFileLock, writeConfigDocument, } from '../core/config.js';
|
|
4
|
+
import { resolveArchive } from '../core/firefox-archive.js';
|
|
4
5
|
import { GeneralError, InvalidArgumentError } from '../errors/base.js';
|
|
5
6
|
import { info, intro, outro, success } from '../utils/logger.js';
|
|
6
7
|
import { isValidFirefoxProduct } from '../utils/validation.js';
|
|
@@ -57,8 +58,10 @@ export async function sourceSetCommand(projectRoot, options) {
|
|
|
57
58
|
await writeConfigDocument(projectRoot, updated);
|
|
58
59
|
return validated.firefox;
|
|
59
60
|
});
|
|
61
|
+
const archive = resolveArchive(written.version, written.product);
|
|
60
62
|
success(`Set firefox.version = ${written.version}`);
|
|
61
63
|
success(`Set firefox.product = ${written.product}`);
|
|
64
|
+
success(`Resolved source URL: ${archive.url}`);
|
|
62
65
|
if (written.sha256 !== undefined) {
|
|
63
66
|
success(`Set firefox.sha256 = ${written.sha256}`);
|
|
64
67
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Normalizes generated patch files
|
|
2
|
+
* Normalizes generated patch files before they are written to disk.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* physical line
|
|
7
|
-
*
|
|
4
|
+
* Kept as a narrow chokepoint for future artifact-level fixes. It must not
|
|
5
|
+
* rewrite hunk body lines: unified diffs encode a blank context line as a
|
|
6
|
+
* physical line containing the leading context marker (`" "`), and FireForge's
|
|
7
|
+
* verifier relies on that marker when replaying patch output.
|
|
8
8
|
*/
|
|
9
9
|
export declare function normalizePatchArtifact(content: string): string;
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
// SPDX-License-Identifier: EUPL-1.2
|
|
2
2
|
/**
|
|
3
|
-
* Normalizes generated patch files
|
|
3
|
+
* Normalizes generated patch files before they are written to disk.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* physical line
|
|
8
|
-
*
|
|
5
|
+
* Kept as a narrow chokepoint for future artifact-level fixes. It must not
|
|
6
|
+
* rewrite hunk body lines: unified diffs encode a blank context line as a
|
|
7
|
+
* physical line containing the leading context marker (`" "`), and FireForge's
|
|
8
|
+
* verifier relies on that marker when replaying patch output.
|
|
9
9
|
*/
|
|
10
10
|
export function normalizePatchArtifact(content) {
|
|
11
|
-
return content
|
|
11
|
+
return content;
|
|
12
12
|
}
|
|
13
13
|
//# sourceMappingURL=patch-artifact-normalize.js.map
|
|
@@ -3,27 +3,7 @@
|
|
|
3
3
|
* Manifest metadata mutation helpers for patch export commands.
|
|
4
4
|
*/
|
|
5
5
|
import { withPatchDirectoryLock } from './patch-apply.js';
|
|
6
|
-
import {
|
|
7
|
-
/**
|
|
8
|
-
* Merges `updates` onto `existing` and removes the listed optional fields.
|
|
9
|
-
*/
|
|
10
|
-
function applyMetadataUpdate(existing, updates, unset) {
|
|
11
|
-
const next = { ...existing, ...updates };
|
|
12
|
-
for (const field of unset) {
|
|
13
|
-
switch (field) {
|
|
14
|
-
case 'tier':
|
|
15
|
-
delete next.tier;
|
|
16
|
-
break;
|
|
17
|
-
case 'lintIgnore':
|
|
18
|
-
delete next.lintIgnore;
|
|
19
|
-
break;
|
|
20
|
-
case 'stagedDependencies':
|
|
21
|
-
delete next.stagedDependencies;
|
|
22
|
-
break;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
return next;
|
|
26
|
-
}
|
|
6
|
+
import { mutatePatchRowsInManifest } from './patch-manifest.js';
|
|
27
7
|
/**
|
|
28
8
|
* Updates metadata for a patch in the manifest.
|
|
29
9
|
*
|
|
@@ -34,17 +14,10 @@ function applyMetadataUpdate(existing, updates, unset) {
|
|
|
34
14
|
*/
|
|
35
15
|
export async function updatePatchMetadata(patchesDir, filename, updates, unsetFields = []) {
|
|
36
16
|
await withPatchDirectoryLock(patchesDir, async () => {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
if (patchIndex === -1)
|
|
42
|
-
return;
|
|
43
|
-
const existingPatch = manifest.patches[patchIndex];
|
|
44
|
-
if (existingPatch) {
|
|
45
|
-
manifest.patches[patchIndex] = applyMetadataUpdate(existingPatch, updates, unsetFields);
|
|
46
|
-
await savePatchesManifest(patchesDir, manifest);
|
|
47
|
-
}
|
|
17
|
+
await mutatePatchRowsInManifest(patchesDir, [filename], () => ({
|
|
18
|
+
set: updates,
|
|
19
|
+
unset: unsetFields,
|
|
20
|
+
}));
|
|
48
21
|
});
|
|
49
22
|
}
|
|
50
23
|
/**
|
|
@@ -53,20 +26,14 @@ export async function updatePatchMetadata(patchesDir, filename, updates, unsetFi
|
|
|
53
26
|
*/
|
|
54
27
|
export async function mutatePatchMetadata(patchesDir, filename, mutator) {
|
|
55
28
|
return await withPatchDirectoryLock(patchesDir, async () => {
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
return
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
const existingPatch = manifest.patches[patchIndex];
|
|
63
|
-
if (!existingPatch)
|
|
29
|
+
const result = await mutatePatchRowsInManifest(patchesDir, [filename], (existing) => {
|
|
30
|
+
const { set = {}, unset = [] } = mutator(existing);
|
|
31
|
+
return { set, unset };
|
|
32
|
+
});
|
|
33
|
+
const changed = result?.[0];
|
|
34
|
+
if (!changed)
|
|
64
35
|
return null;
|
|
65
|
-
|
|
66
|
-
const updatedPatch = applyMetadataUpdate(existingPatch, set, unset);
|
|
67
|
-
manifest.patches[patchIndex] = updatedPatch;
|
|
68
|
-
await savePatchesManifest(patchesDir, manifest);
|
|
69
|
-
return { before: existingPatch, after: updatedPatch };
|
|
36
|
+
return { before: changed.before, after: changed.after };
|
|
70
37
|
});
|
|
71
38
|
}
|
|
72
39
|
//# sourceMappingURL=patch-export-metadata.js.map
|
|
@@ -5,7 +5,7 @@ import { pathExists, readText, writeText } from '../utils/fs.js';
|
|
|
5
5
|
import { warn } from '../utils/logger.js';
|
|
6
6
|
import { withPatchDirectoryLock } from './patch-apply.js';
|
|
7
7
|
import { normalizePatchArtifact } from './patch-artifact-normalize.js';
|
|
8
|
-
import { loadPatchesManifest,
|
|
8
|
+
import { loadPatchesManifest, mutatePatchRowsInManifest } from './patch-manifest.js';
|
|
9
9
|
import { buildProjectedManifest, enforcePatchPolicy } from './patch-policy.js';
|
|
10
10
|
/**
|
|
11
11
|
* Updates a patch file body and its manifest row under the same patch
|
|
@@ -41,7 +41,7 @@ export async function updatePatchAndMetadata(patchesDir, filename, newContent, u
|
|
|
41
41
|
try {
|
|
42
42
|
await writeText(patchPath, normalizePatchArtifact(newContent));
|
|
43
43
|
patchWritten = true;
|
|
44
|
-
await
|
|
44
|
+
await mutatePatchRowsInManifest(patchesDir, [filename], () => ({ set: updates }));
|
|
45
45
|
}
|
|
46
46
|
catch (error) {
|
|
47
47
|
if (patchWritten) {
|
|
@@ -16,6 +16,20 @@ export interface LoadedManifestState {
|
|
|
16
16
|
manifest: PatchesManifest | null;
|
|
17
17
|
parseError: Error | undefined;
|
|
18
18
|
}
|
|
19
|
+
/** Field-level mutation returned by a row-scoped manifest mutator. */
|
|
20
|
+
export interface PatchManifestRowMutation {
|
|
21
|
+
/** Metadata fields to set on the selected manifest row. */
|
|
22
|
+
set?: Partial<PatchMetadata>;
|
|
23
|
+
/** Metadata fields to delete from the selected manifest row. */
|
|
24
|
+
unset?: ReadonlyArray<string>;
|
|
25
|
+
}
|
|
26
|
+
/** Result for one manifest row changed by {@link mutatePatchRowsInManifest}. */
|
|
27
|
+
export interface PatchManifestRowMutationResult {
|
|
28
|
+
/** Validated row before the mutation. */
|
|
29
|
+
before: PatchMetadata;
|
|
30
|
+
/** Validated row after the mutation. */
|
|
31
|
+
after: PatchMetadata;
|
|
32
|
+
}
|
|
19
33
|
/**
|
|
20
34
|
* Loads and validates the patches manifest, returning full state information.
|
|
21
35
|
* @param patchesDir - Path to the patches directory
|
|
@@ -33,6 +47,13 @@ export declare function loadPatchesManifest(patchesDir: string): Promise<Patches
|
|
|
33
47
|
* @param manifest - Manifest to save
|
|
34
48
|
*/
|
|
35
49
|
export declare function savePatchesManifest(patchesDir: string, manifest: PatchesManifest): Promise<void>;
|
|
50
|
+
/**
|
|
51
|
+
* Mutates selected manifest rows while preserving the raw JSON shape of every
|
|
52
|
+
* untouched row. This avoids serializing reader-only fallback fields, such as
|
|
53
|
+
* `sourceVersion` derived from legacy `sourceEsrVersion`, into unrelated patch
|
|
54
|
+
* entries during partial metadata updates.
|
|
55
|
+
*/
|
|
56
|
+
export declare function mutatePatchRowsInManifest(patchesDir: string, filenames: readonly string[], mutator: (existing: PatchMetadata, rawExisting: Readonly<Record<string, unknown>>) => PatchManifestRowMutation | null): Promise<PatchManifestRowMutationResult[] | null>;
|
|
36
57
|
/**
|
|
37
58
|
* Adds or updates a patch entry in the manifest.
|
|
38
59
|
* @param patchesDir - Path to the patches directory
|
|
@@ -15,6 +15,7 @@ import { ExitCode } from '../errors/codes.js';
|
|
|
15
15
|
import { toError } from '../utils/errors.js';
|
|
16
16
|
import { pathExists, readJson, removeFile, writeJson } from '../utils/fs.js';
|
|
17
17
|
import { warn } from '../utils/logger.js';
|
|
18
|
+
import { isArray, isObject } from '../utils/validation.js';
|
|
18
19
|
import { validatePatchesManifest } from './patch-manifest-validate.js';
|
|
19
20
|
/** Filename for the patches manifest */
|
|
20
21
|
export const PATCHES_MANIFEST = 'patches.json';
|
|
@@ -61,6 +62,62 @@ export async function savePatchesManifest(patchesDir, manifest) {
|
|
|
61
62
|
const manifestPath = join(patchesDir, PATCHES_MANIFEST);
|
|
62
63
|
await writeJson(manifestPath, manifest);
|
|
63
64
|
}
|
|
65
|
+
/**
|
|
66
|
+
* Mutates selected manifest rows while preserving the raw JSON shape of every
|
|
67
|
+
* untouched row. This avoids serializing reader-only fallback fields, such as
|
|
68
|
+
* `sourceVersion` derived from legacy `sourceEsrVersion`, into unrelated patch
|
|
69
|
+
* entries during partial metadata updates.
|
|
70
|
+
*/
|
|
71
|
+
export async function mutatePatchRowsInManifest(patchesDir, filenames, mutator) {
|
|
72
|
+
const manifestPath = join(patchesDir, PATCHES_MANIFEST);
|
|
73
|
+
if (!(await pathExists(manifestPath)))
|
|
74
|
+
return null;
|
|
75
|
+
const rawManifest = await readJson(manifestPath);
|
|
76
|
+
const beforeManifest = validatePatchesManifest(rawManifest);
|
|
77
|
+
if (!isObject(rawManifest) || !isArray(rawManifest['patches'])) {
|
|
78
|
+
throw new Error('patches.json must be a JSON object with a patches array');
|
|
79
|
+
}
|
|
80
|
+
const filenameSet = new Set(filenames);
|
|
81
|
+
if (filenameSet.size === 0)
|
|
82
|
+
return [];
|
|
83
|
+
const rawPatches = rawManifest['patches'].map((entry) => {
|
|
84
|
+
if (!isObject(entry)) {
|
|
85
|
+
throw new Error('patches.json patches entries must be objects');
|
|
86
|
+
}
|
|
87
|
+
return { ...entry };
|
|
88
|
+
});
|
|
89
|
+
const changedIndexes = [];
|
|
90
|
+
for (const [index, rawPatch] of rawPatches.entries()) {
|
|
91
|
+
const filename = rawPatch['filename'];
|
|
92
|
+
if (typeof filename !== 'string' || !filenameSet.has(filename))
|
|
93
|
+
continue;
|
|
94
|
+
const existing = beforeManifest.patches[index];
|
|
95
|
+
if (!existing)
|
|
96
|
+
continue;
|
|
97
|
+
const mutation = mutator(existing, rawPatch);
|
|
98
|
+
if (!mutation)
|
|
99
|
+
continue;
|
|
100
|
+
for (const [field, value] of Object.entries(mutation.set ?? {})) {
|
|
101
|
+
rawPatch[field] = value;
|
|
102
|
+
}
|
|
103
|
+
for (const field of mutation.unset ?? []) {
|
|
104
|
+
rawPatch[field] = undefined;
|
|
105
|
+
}
|
|
106
|
+
changedIndexes.push(index);
|
|
107
|
+
}
|
|
108
|
+
if (changedIndexes.length === 0)
|
|
109
|
+
return [];
|
|
110
|
+
const nextRawManifest = {
|
|
111
|
+
...rawManifest,
|
|
112
|
+
patches: rawPatches,
|
|
113
|
+
};
|
|
114
|
+
const afterManifest = validatePatchesManifest(nextRawManifest);
|
|
115
|
+
await writeJson(manifestPath, nextRawManifest);
|
|
116
|
+
return changedIndexes.map((index) => ({
|
|
117
|
+
before: beforeManifest.patches[index],
|
|
118
|
+
after: afterManifest.patches[index],
|
|
119
|
+
}));
|
|
120
|
+
}
|
|
64
121
|
/**
|
|
65
122
|
* Adds or updates a patch entry in the manifest.
|
|
66
123
|
* @param patchesDir - Path to the patches directory
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import { readText } from '../utils/fs.js';
|
|
6
6
|
import { fileExistsInHead } from './git-file-ops.js';
|
|
7
7
|
import { discoverPatches, getAllTargetFilesFromPatch } from './patch-files.js';
|
|
8
|
-
import { loadPatchesManifest,
|
|
8
|
+
import { loadPatchesManifest, mutatePatchRowsInManifest } from './patch-manifest-io.js';
|
|
9
9
|
import { isNewFileInPatch } from './patch-parse.js';
|
|
10
10
|
/**
|
|
11
11
|
* Gets all file paths claimed by patches other than the excluded one.
|
|
@@ -106,27 +106,19 @@ export async function validatePatchIntegrity(patchesDir, engineDir) {
|
|
|
106
106
|
* @param newVersion - Version string to set (e.g. "140.9.0esr")
|
|
107
107
|
*/
|
|
108
108
|
export async function stampPatchVersions(patchesDir, filenames, newVersion, newProduct) {
|
|
109
|
-
|
|
110
|
-
if (!manifest)
|
|
111
|
-
return;
|
|
112
|
-
const filenameSet = new Set(filenames);
|
|
113
|
-
let modified = false;
|
|
114
|
-
for (const patch of manifest.patches) {
|
|
115
|
-
if (!filenameSet.has(patch.filename))
|
|
116
|
-
continue;
|
|
109
|
+
await mutatePatchRowsInManifest(patchesDir, filenames, (patch, rawPatch) => {
|
|
117
110
|
if (patch.sourceEsrVersion !== newVersion ||
|
|
118
|
-
|
|
119
|
-
(newProduct !== undefined &&
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
111
|
+
rawPatch['sourceVersion'] !== newVersion ||
|
|
112
|
+
(newProduct !== undefined && rawPatch['sourceProduct'] !== newProduct)) {
|
|
113
|
+
return {
|
|
114
|
+
set: {
|
|
115
|
+
sourceEsrVersion: newVersion,
|
|
116
|
+
sourceVersion: newVersion,
|
|
117
|
+
...(newProduct !== undefined ? { sourceProduct: newProduct } : {}),
|
|
118
|
+
},
|
|
119
|
+
};
|
|
126
120
|
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
await savePatchesManifest(patchesDir, manifest);
|
|
130
|
-
}
|
|
121
|
+
return null;
|
|
122
|
+
});
|
|
131
123
|
}
|
|
132
124
|
//# sourceMappingURL=patch-manifest-query.js.map
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* is an implementation detail.
|
|
6
6
|
*/
|
|
7
7
|
export { rebuildPatchesManifest, validatePatchesManifestConsistency, } from './patch-manifest-consistency.js';
|
|
8
|
-
export { addPatchToManifest, loadPatchesManifest, PATCHES_MANIFEST, type PatchRenameEntry, removePatchFileAndManifest, renumberPatchesInManifest, savePatchesManifest, } from './patch-manifest-io.js';
|
|
8
|
+
export { addPatchToManifest, loadPatchesManifest, mutatePatchRowsInManifest, PATCHES_MANIFEST, type PatchManifestRowMutation, type PatchManifestRowMutationResult, type PatchRenameEntry, removePatchFileAndManifest, renumberPatchesInManifest, savePatchesManifest, } from './patch-manifest-io.js';
|
|
9
9
|
export { checkVersionCompatibility, findPatchesAffectingFile, getClaimedFiles, stampPatchVersions, validatePatchIntegrity, } from './patch-manifest-query.js';
|
|
10
10
|
export { resolvePatchIdentifier } from './patch-manifest-resolve.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, removePatchFileAndManifest, renumberPatchesInManifest, savePatchesManifest, } from './patch-manifest-io.js';
|
|
9
|
+
export { addPatchToManifest, loadPatchesManifest, mutatePatchRowsInManifest, PATCHES_MANIFEST, removePatchFileAndManifest, renumberPatchesInManifest, savePatchesManifest, } from './patch-manifest-io.js';
|
|
10
10
|
export { checkVersionCompatibility, findPatchesAffectingFile, getClaimedFiles, stampPatchVersions, validatePatchIntegrity, } from './patch-manifest-query.js';
|
|
11
11
|
export { resolvePatchIdentifier } from './patch-manifest-resolve.js';
|
|
12
12
|
export { validatePatchesManifest } from './patch-manifest-validate.js';
|