@hominis/fireforge 0.27.1 → 0.27.3
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 +12 -0
- package/README.md +5 -5
- package/dist/src/cli.js +5 -1
- package/dist/src/commands/build.js +61 -1
- package/dist/src/commands/doctor-working-tree.js +5 -1
- package/dist/src/commands/download.js +178 -112
- package/dist/src/commands/export-all.js +3 -2
- package/dist/src/commands/export-flow.d.ts +2 -0
- package/dist/src/commands/export-flow.js +2 -0
- package/dist/src/commands/export.js +5 -4
- package/dist/src/commands/import.js +2 -1
- package/dist/src/commands/re-export.js +6 -6
- package/dist/src/commands/rebase/continue.js +2 -0
- package/dist/src/commands/rebase/index.d.ts +2 -2
- package/dist/src/commands/rebase/index.js +9 -4
- package/dist/src/commands/rebase/patch-loop.js +5 -5
- package/dist/src/commands/rebase/summary.js +7 -2
- package/dist/src/commands/resolve.js +2 -1
- package/dist/src/commands/status-output.d.ts +13 -0
- package/dist/src/commands/status-output.js +186 -0
- package/dist/src/commands/status.js +4 -247
- package/dist/src/commands/verify.js +32 -16
- package/dist/src/core/build-prepare.js +12 -4
- package/dist/src/core/firefox-archive.js +7 -3
- package/dist/src/core/firefox-cache.d.ts +1 -1
- package/dist/src/core/firefox-cache.js +12 -5
- package/dist/src/core/firefox.js +1 -1
- package/dist/src/core/git.js +7 -2
- package/dist/src/core/ownership-table.d.ts +3 -1
- package/dist/src/core/ownership-table.js +31 -7
- package/dist/src/core/patch-export.d.ts +4 -0
- package/dist/src/core/patch-export.js +4 -0
- package/dist/src/core/patch-manifest-consistency.d.ts +1 -1
- package/dist/src/core/patch-manifest-consistency.js +4 -2
- package/dist/src/core/patch-manifest-query.d.ts +4 -3
- package/dist/src/core/patch-manifest-query.js +12 -4
- package/dist/src/core/patch-manifest-validate.js +22 -4
- package/dist/src/core/patch-source-metadata.d.ts +8 -0
- package/dist/src/core/patch-source-metadata.js +17 -0
- package/dist/src/core/rebase-session.d.ts +8 -3
- package/dist/src/core/rebase-session.js +1 -1
- package/dist/src/core/status-classify.d.ts +4 -1
- package/dist/src/core/status-classify.js +4 -5
- package/dist/src/errors/download.d.ts +11 -0
- package/dist/src/errors/download.js +33 -1
- package/dist/src/types/commands/patches.d.ts +9 -1
- package/package.json +1 -1
|
@@ -370,13 +370,13 @@ export async function reExportCommand(projectRoot, patches, options) {
|
|
|
370
370
|
// which we refuse to version-stamp through.
|
|
371
371
|
const shouldStamp = options.stamp === true && !isDryRun && reExported > 0 && reExported === selectedPatches.length;
|
|
372
372
|
if (shouldStamp) {
|
|
373
|
-
await stampPatchVersions(paths.patches, reExportedFilenames, config.firefox.version);
|
|
373
|
+
await stampPatchVersions(paths.patches, reExportedFilenames, config.firefox.version, config.firefox.product);
|
|
374
374
|
}
|
|
375
375
|
if (isDryRun) {
|
|
376
376
|
progress.stop('Dry run complete');
|
|
377
377
|
success(`[dry-run] Would re-export ${reExported} of ${selectedPatches.length} patch(es)`);
|
|
378
378
|
if (options.stamp === true) {
|
|
379
|
-
info(`[dry-run] Would stamp
|
|
379
|
+
info(`[dry-run] Would stamp sourceVersion=${config.firefox.version} (${config.firefox.product}) on ${reExported} patch(es)`);
|
|
380
380
|
}
|
|
381
381
|
outro('Dry run complete');
|
|
382
382
|
}
|
|
@@ -384,7 +384,7 @@ export async function reExportCommand(projectRoot, patches, options) {
|
|
|
384
384
|
progress.stop('Re-export complete');
|
|
385
385
|
success(`Re-exported ${reExported} of ${selectedPatches.length} patch(es)`);
|
|
386
386
|
if (shouldStamp) {
|
|
387
|
-
success(`Stamped
|
|
387
|
+
success(`Stamped sourceVersion=${config.firefox.version} (${config.firefox.product}) on ${reExportedFilenames.length} patch(es)`);
|
|
388
388
|
}
|
|
389
389
|
else if (options.stamp === true && reExported !== selectedPatches.length) {
|
|
390
390
|
warn('--stamp was requested but some patches failed or were skipped; refusing to stamp a partial set.');
|
|
@@ -397,8 +397,8 @@ export function registerReExport(program, { getProjectRoot, withErrorHandling })
|
|
|
397
397
|
program
|
|
398
398
|
.command('re-export [patches...]')
|
|
399
399
|
.description('Refresh existing patch bodies (and filesAffected with --scan) from the current engine ' +
|
|
400
|
-
'state. Does NOT change
|
|
401
|
-
'
|
|
400
|
+
'state. Does NOT change sourceVersion/sourceProduct by default — use --stamp or run ' +
|
|
401
|
+
'rebase for source metadata stamping.')
|
|
402
402
|
.option('-a, --all', 'Re-export all patches')
|
|
403
403
|
.option('-s, --scan', 'Scan directories for new/removed files and update filesAffected')
|
|
404
404
|
.option('--scan-file <path>', 'With --scan, add this explicit engine-relative file to one target patch without collecting adjacent files. Repeatable.', (value, prev) => [...prev, value], [])
|
|
@@ -411,7 +411,7 @@ export function registerReExport(program, { getProjectRoot, withErrorHandling })
|
|
|
411
411
|
.option('--allow-shrink', 'Allow --files to remove paths currently owned by the patch. Required before --yes can bypass the shrink confirmation.')
|
|
412
412
|
.option('-y, --yes', 'Skip confirmation prompts (required for non-TTY destructive writes)')
|
|
413
413
|
.option('--force-unsafe', 'Bypass cross-patch lint refusal when --files shrinks a patch')
|
|
414
|
-
.option('--stamp', "After every selected patch refreshes cleanly, stamp each re-exported patch's
|
|
414
|
+
.option('--stamp', "After every selected patch refreshes cleanly, stamp each re-exported patch's sourceVersion/sourceProduct in patches.json to firefox.version/firefox.product from fireforge.json. No effect on a partial run.")
|
|
415
415
|
.addOption(new Option('--tier <tier>', 'Force a tier override on the selected patch (only "branding" recognised). Mutually exclusive with --all.').choices(['branding']))
|
|
416
416
|
.option('--lint-ignore <check-id>', 'Append a lint check ID to the patch\'s PatchMetadata.lintIgnore (union, de-duped, repeatable). Mutually exclusive with --all. Use "fireforge patch lint-ignore" for --remove / --clear.', (value, prev) => [...prev, value], [])
|
|
417
417
|
.action(withErrorHandling(async (patches, options) => {
|
|
@@ -73,6 +73,8 @@ export async function handleContinue(projectRoot, maxFuzz) {
|
|
|
73
73
|
// v0.14.0 resolve.ts fix.
|
|
74
74
|
await updatePatchAndMetadata(paths.patches, currentPatch.filename, diffContent, {
|
|
75
75
|
sourceEsrVersion: session.toVersion,
|
|
76
|
+
sourceVersion: session.toVersion,
|
|
77
|
+
...(session.toProduct !== undefined ? { sourceProduct: session.toProduct } : {}),
|
|
76
78
|
});
|
|
77
79
|
}
|
|
78
80
|
finally {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `fireforge rebase` — semi-automated
|
|
2
|
+
* `fireforge rebase` — semi-automated Firefox source version upgrade.
|
|
3
3
|
*
|
|
4
4
|
* Orchestrates the full patch-rebase workflow:
|
|
5
5
|
* 1. Reset engine to baseline
|
|
@@ -13,7 +13,7 @@ import { Command } from 'commander';
|
|
|
13
13
|
import type { CommandContext } from '../../types/cli.js';
|
|
14
14
|
import type { RebaseOptions } from '../../types/commands/index.js';
|
|
15
15
|
/**
|
|
16
|
-
* Runs the rebase command to orchestrate
|
|
16
|
+
* Runs the rebase command to orchestrate a Firefox source version upgrade.
|
|
17
17
|
* @param projectRoot - Root directory of the project
|
|
18
18
|
* @param options - Rebase options
|
|
19
19
|
*/
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// SPDX-License-Identifier: EUPL-1.2
|
|
2
2
|
/**
|
|
3
|
-
* `fireforge rebase` — semi-automated
|
|
3
|
+
* `fireforge rebase` — semi-automated Firefox source version upgrade.
|
|
4
4
|
*
|
|
5
5
|
* Orchestrates the full patch-rebase workflow:
|
|
6
6
|
* 1. Reset engine to baseline
|
|
@@ -15,6 +15,7 @@ import { getFurnacePaths, updateFurnaceState } from '../../core/furnace-config.j
|
|
|
15
15
|
import { getHead, isGitRepository, isMissingHeadError, resetChanges } from '../../core/git.js';
|
|
16
16
|
import { discoverPatches } from '../../core/patch-files.js';
|
|
17
17
|
import { loadPatchesManifest } from '../../core/patch-manifest.js';
|
|
18
|
+
import { getPatchSourceProduct, getPatchSourceVersion } from '../../core/patch-source-metadata.js';
|
|
18
19
|
import { hasActiveRebaseSession, saveRebaseSession } from '../../core/rebase-session.js';
|
|
19
20
|
import { GeneralError } from '../../errors/base.js';
|
|
20
21
|
import { RebaseSessionExistsError } from '../../errors/rebase.js';
|
|
@@ -65,9 +66,11 @@ async function handleFreshStart(projectRoot, options) {
|
|
|
65
66
|
throw new GeneralError('No patches found in manifest. Nothing to rebase.');
|
|
66
67
|
}
|
|
67
68
|
// Determine the "from" version from the patches
|
|
68
|
-
const patchVersions = new Set(manifest.patches.map((p) => p
|
|
69
|
+
const patchVersions = new Set(manifest.patches.map((p) => getPatchSourceVersion(p)));
|
|
70
|
+
const patchProducts = new Set(manifest.patches.map((p) => getPatchSourceProduct(p)).filter(Boolean));
|
|
69
71
|
const sortedVersions = [...patchVersions].sort();
|
|
70
72
|
const fromVersion = sortedVersions[0] ?? currentVersion;
|
|
73
|
+
const fromProduct = [...patchProducts].sort()[0] ?? config.firefox.product;
|
|
71
74
|
if (patchVersions.size === 1 && fromVersion === currentVersion) {
|
|
72
75
|
info('All patches already match the current Firefox version. Nothing to rebase.');
|
|
73
76
|
outro('Rebase not needed');
|
|
@@ -110,6 +113,8 @@ async function handleFreshStart(projectRoot, options) {
|
|
|
110
113
|
const allPatches = await discoverPatches(paths.patches);
|
|
111
114
|
const session = {
|
|
112
115
|
startedAt: new Date().toISOString(),
|
|
116
|
+
fromProduct,
|
|
117
|
+
toProduct: config.firefox.product,
|
|
113
118
|
fromVersion,
|
|
114
119
|
toVersion: currentVersion,
|
|
115
120
|
preRebaseCommit,
|
|
@@ -125,7 +130,7 @@ async function handleFreshStart(projectRoot, options) {
|
|
|
125
130
|
}
|
|
126
131
|
// ── Public API ──
|
|
127
132
|
/**
|
|
128
|
-
* Runs the rebase command to orchestrate
|
|
133
|
+
* Runs the rebase command to orchestrate a Firefox source version upgrade.
|
|
129
134
|
* @param projectRoot - Root directory of the project
|
|
130
135
|
* @param options - Rebase options
|
|
131
136
|
*/
|
|
@@ -142,7 +147,7 @@ export async function rebaseCommand(projectRoot, options = {}) {
|
|
|
142
147
|
export function registerRebase(program, { getProjectRoot, withErrorHandling }) {
|
|
143
148
|
program
|
|
144
149
|
.command('rebase')
|
|
145
|
-
.description('Semi-automated
|
|
150
|
+
.description('Semi-automated Firefox source version upgrade — apply patches with fuzz and re-export')
|
|
146
151
|
.option('--continue', 'Resume after manually resolving a failed patch')
|
|
147
152
|
.option('--abort', 'Cancel the rebase and restore engine to pre-rebase state')
|
|
148
153
|
.option('--dry-run', 'Show what would happen without modifying anything')
|
|
@@ -143,16 +143,16 @@ export async function runPatchLoop(projectRoot, session, paths, maxFuzz) {
|
|
|
143
143
|
.filter((p) => p.status === 'applied-clean' || p.status === 'applied-fuzz' || p.status === 'resolved')
|
|
144
144
|
.map((p) => p.filename);
|
|
145
145
|
if (appliedFilenames.length > 0) {
|
|
146
|
-
await stampPatchVersions(paths.patches, appliedFilenames, session.toVersion);
|
|
146
|
+
await stampPatchVersions(paths.patches, appliedFilenames, session.toVersion, session.toProduct);
|
|
147
147
|
}
|
|
148
148
|
// Stamp every Furnace override's `baseVersion` to match the rebased
|
|
149
|
-
// Firefox version. Before this stamp, a successful
|
|
149
|
+
// Firefox source version. Before this stamp, a successful source bump left
|
|
150
150
|
// overrides in a doctor-failing drift state (each override still
|
|
151
|
-
// claimed the pre-rebase
|
|
151
|
+
// claimed the pre-rebase source as its baseline) and every subsequent
|
|
152
152
|
// `fireforge doctor` failed `Furnace component validation`. The
|
|
153
153
|
// stamp is unconditional per the helper's contract: rebase already
|
|
154
154
|
// succeeded on the patch side, so the operator is committing to the
|
|
155
|
-
// new
|
|
155
|
+
// new source baseline; per-component health checking stays with
|
|
156
156
|
// `fireforge furnace validate` / `doctor --repair-furnace`.
|
|
157
157
|
try {
|
|
158
158
|
const overridesStamped = await stampFurnaceOverrideBaseVersions(projectRoot, session.toVersion);
|
|
@@ -176,7 +176,7 @@ export async function runPatchLoop(projectRoot, session, paths, maxFuzz) {
|
|
|
176
176
|
return next;
|
|
177
177
|
});
|
|
178
178
|
info('');
|
|
179
|
-
success(`All patches re-exported with
|
|
179
|
+
success(`All patches re-exported with sourceVersion=${session.toVersion}`);
|
|
180
180
|
outro('Rebase complete!');
|
|
181
181
|
}
|
|
182
182
|
async function reExportAppliedPatches(session, paths) {
|
|
@@ -27,7 +27,11 @@ export function statusLabel(status, fuzzFactor) {
|
|
|
27
27
|
*/
|
|
28
28
|
export function printSummary(session) {
|
|
29
29
|
info('');
|
|
30
|
-
|
|
30
|
+
const from = session.fromProduct
|
|
31
|
+
? `${session.fromProduct} ${session.fromVersion}`
|
|
32
|
+
: session.fromVersion;
|
|
33
|
+
const to = session.toProduct ? `${session.toProduct} ${session.toVersion}` : session.toVersion;
|
|
34
|
+
info(`Source Rebase Summary: ${from} → ${to}`);
|
|
31
35
|
info('='.repeat(55));
|
|
32
36
|
for (const patch of session.patches) {
|
|
33
37
|
const label = statusLabel(patch.status, patch.fuzzFactor);
|
|
@@ -37,7 +41,8 @@ export function printSummary(session) {
|
|
|
37
41
|
const fuzz = session.patches.filter((p) => p.status === 'applied-fuzz').length;
|
|
38
42
|
const resolved = session.patches.filter((p) => p.status === 'resolved').length;
|
|
39
43
|
const failed = session.patches.filter((p) => p.status === 'failed').length;
|
|
44
|
+
const total = session.patches.length;
|
|
40
45
|
info('');
|
|
41
|
-
info(`Results: ${clean} clean, ${fuzz} fuzz-applied, ${resolved} manually resolved, ${failed} failed`);
|
|
46
|
+
info(`Results: ${total} total: ${clean} clean, ${fuzz} fuzz-applied, ${resolved} manually resolved, ${failed} failed`);
|
|
42
47
|
}
|
|
43
48
|
//# sourceMappingURL=summary.js.map
|
|
@@ -8,6 +8,7 @@ import { stageFiles, unstageFiles } from '../core/git-file-ops.js';
|
|
|
8
8
|
import { extractAffectedFiles } from '../core/patch-apply.js';
|
|
9
9
|
import { updatePatchAndMetadata } from '../core/patch-export.js';
|
|
10
10
|
import { loadPatchesManifest } from '../core/patch-manifest.js';
|
|
11
|
+
import { buildPatchSourceMetadata } from '../core/patch-source-metadata.js';
|
|
11
12
|
import { GeneralError, ResolutionError } from '../errors/base.js';
|
|
12
13
|
import { toError } from '../utils/errors.js';
|
|
13
14
|
import { pathExists } from '../utils/fs.js';
|
|
@@ -132,7 +133,7 @@ export async function resolveCommand(projectRoot, options = {}) {
|
|
|
132
133
|
const config = await loadConfig(projectRoot);
|
|
133
134
|
await updatePatchAndMetadata(paths.patches, patchFilename, diffContent, {
|
|
134
135
|
filesAffected: diffFilesAffected,
|
|
135
|
-
|
|
136
|
+
...buildPatchSourceMetadata(config.firefox),
|
|
136
137
|
});
|
|
137
138
|
// Cleanup: Clear pendingResolution from state.json transactionally so
|
|
138
139
|
// we don't clobber concurrent updates to unrelated keys (e.g. another
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ClassifiedFile } from '../core/status-classify.js';
|
|
2
|
+
export interface ClassifiedBuckets {
|
|
3
|
+
conflict: ClassifiedFile[];
|
|
4
|
+
unmanaged: ClassifiedFile[];
|
|
5
|
+
patchBacked: ClassifiedFile[];
|
|
6
|
+
patchOwnedDrift: ClassifiedFile[];
|
|
7
|
+
branding: ClassifiedFile[];
|
|
8
|
+
furnace: ClassifiedFile[];
|
|
9
|
+
}
|
|
10
|
+
/** Renders the unmanaged-only status view and registration hints. */
|
|
11
|
+
export declare function renderUnmanagedOnly(unmanagedFiles: ClassifiedFile[], totalModified: number, projectRoot: string, binaryName: string): Promise<void>;
|
|
12
|
+
/** Renders the default classified status buckets. */
|
|
13
|
+
export declare function renderDefaultStatus(totalModified: number, buckets: ClassifiedBuckets, projectRoot: string, binaryName: string): Promise<void>;
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
import { isFileRegistered, matchesRegistrablePattern } from '../core/manifest-rules.js';
|
|
3
|
+
import { GeneralError } from '../errors/base.js';
|
|
4
|
+
import { info, outro, warn } from '../utils/logger.js';
|
|
5
|
+
const STATUS_DESCRIPTIONS = {
|
|
6
|
+
M: 'modified',
|
|
7
|
+
A: 'added',
|
|
8
|
+
D: 'deleted',
|
|
9
|
+
R: 'renamed',
|
|
10
|
+
C: 'copied',
|
|
11
|
+
U: 'unmerged',
|
|
12
|
+
'?': 'untracked',
|
|
13
|
+
'!': 'ignored',
|
|
14
|
+
};
|
|
15
|
+
function getStatusDescription(code) {
|
|
16
|
+
return STATUS_DESCRIPTIONS[code] ?? 'changed';
|
|
17
|
+
}
|
|
18
|
+
function getPrimaryStatusCode(status) {
|
|
19
|
+
if (status.includes('?'))
|
|
20
|
+
return '?';
|
|
21
|
+
if (status.includes('!'))
|
|
22
|
+
return '!';
|
|
23
|
+
for (const code of status) {
|
|
24
|
+
if (code !== ' ')
|
|
25
|
+
return code;
|
|
26
|
+
}
|
|
27
|
+
return status;
|
|
28
|
+
}
|
|
29
|
+
function isNewFileStatus(status) {
|
|
30
|
+
const code = getPrimaryStatusCode(status);
|
|
31
|
+
return code === '?' || code === 'A';
|
|
32
|
+
}
|
|
33
|
+
function groupFilesByStatus(files) {
|
|
34
|
+
const grouped = new Map();
|
|
35
|
+
for (const { status, file } of files) {
|
|
36
|
+
const code = getPrimaryStatusCode(status);
|
|
37
|
+
const existing = grouped.get(code) ?? [];
|
|
38
|
+
existing.push(file);
|
|
39
|
+
grouped.set(code, existing);
|
|
40
|
+
}
|
|
41
|
+
return grouped;
|
|
42
|
+
}
|
|
43
|
+
function printStatusGroups(files) {
|
|
44
|
+
const grouped = groupFilesByStatus(files);
|
|
45
|
+
for (const [status, fileList] of grouped) {
|
|
46
|
+
warn(`${getStatusDescription(status)}:`);
|
|
47
|
+
for (const file of fileList)
|
|
48
|
+
info(` ${file}`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
async function printUnregisteredWarnings(files, projectRoot, binaryName) {
|
|
52
|
+
const newFiles = files.filter((f) => isNewFileStatus(f.status));
|
|
53
|
+
if (newFiles.length === 0)
|
|
54
|
+
return;
|
|
55
|
+
const registrableFiles = newFiles.filter((f) => matchesRegistrablePattern(f.file, binaryName));
|
|
56
|
+
const registrationChecks = await Promise.all(registrableFiles.map(async (f) => {
|
|
57
|
+
try {
|
|
58
|
+
return {
|
|
59
|
+
file: f.file,
|
|
60
|
+
registered: await isFileRegistered(projectRoot, f.file),
|
|
61
|
+
manifestMissing: false,
|
|
62
|
+
manifestMissingMessage: undefined,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
catch (err) {
|
|
66
|
+
if (err instanceof GeneralError && /^Manifest not found:/i.test(err.message)) {
|
|
67
|
+
return {
|
|
68
|
+
file: f.file,
|
|
69
|
+
registered: false,
|
|
70
|
+
manifestMissing: true,
|
|
71
|
+
manifestMissingMessage: err.message,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
throw err;
|
|
75
|
+
}
|
|
76
|
+
}));
|
|
77
|
+
const unregistered = registrationChecks.filter((f) => !f.registered && !f.manifestMissing);
|
|
78
|
+
const manifestMissing = registrationChecks.filter((f) => f.manifestMissing);
|
|
79
|
+
if (unregistered.length > 0) {
|
|
80
|
+
info('');
|
|
81
|
+
warn('Potentially unregistered files:');
|
|
82
|
+
for (const f of unregistered)
|
|
83
|
+
info(` ${f.file} — run 'fireforge register ${f.file}'`);
|
|
84
|
+
}
|
|
85
|
+
if (manifestMissing.length > 0) {
|
|
86
|
+
info('');
|
|
87
|
+
warn('Files whose registration manifest does not exist yet:');
|
|
88
|
+
for (const f of manifestMissing) {
|
|
89
|
+
info(` ${f.file} — ${f.manifestMissingMessage}`);
|
|
90
|
+
info(` Create the parent manifest, then run 'fireforge register ${f.file}'.`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/** Renders the unmanaged-only status view and registration hints. */
|
|
95
|
+
export async function renderUnmanagedOnly(unmanagedFiles, totalModified, projectRoot, binaryName) {
|
|
96
|
+
info(`${unmanagedFiles.length} unmanaged file${unmanagedFiles.length === 1 ? '' : 's'} (${totalModified} total modified):\n`);
|
|
97
|
+
if (unmanagedFiles.length > 0) {
|
|
98
|
+
printStatusGroups(unmanagedFiles);
|
|
99
|
+
await printUnregisteredWarnings(unmanagedFiles, projectRoot, binaryName);
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
info('No unmanaged changes');
|
|
103
|
+
}
|
|
104
|
+
outro(unmanagedFiles.length === 0
|
|
105
|
+
? 'No unmanaged changes'
|
|
106
|
+
: `${unmanagedFiles.length} unmanaged change${unmanagedFiles.length === 1 ? '' : 's'}`);
|
|
107
|
+
}
|
|
108
|
+
/** Renders the default classified status buckets. */
|
|
109
|
+
export async function renderDefaultStatus(totalModified, buckets, projectRoot, binaryName) {
|
|
110
|
+
const { conflict, unmanaged, patchBacked, patchOwnedDrift, branding, furnace } = buckets;
|
|
111
|
+
info(`${totalModified} modified file${totalModified === 1 ? '' : 's'}:\n`);
|
|
112
|
+
if (conflict.length > 0) {
|
|
113
|
+
warn('Cross-patch ownership conflicts (same file claimed by multiple patches):');
|
|
114
|
+
printStatusGroups(conflict);
|
|
115
|
+
for (const entry of conflict) {
|
|
116
|
+
if (entry.claimedBy && entry.claimedBy.length > 0) {
|
|
117
|
+
info(` ${entry.file} — claimed by ${entry.claimedBy.join(', ')}`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
info('Run "fireforge status --ownership" for the full conflict table, then repartition with "fireforge re-export --files <paths> <patch>".');
|
|
121
|
+
}
|
|
122
|
+
if (unmanaged.length > 0) {
|
|
123
|
+
if (conflict.length > 0)
|
|
124
|
+
info('');
|
|
125
|
+
warn('Unmanaged changes:');
|
|
126
|
+
printStatusGroups(unmanaged);
|
|
127
|
+
await printUnregisteredWarnings(unmanaged, projectRoot, binaryName);
|
|
128
|
+
}
|
|
129
|
+
if (patchBacked.length > 0) {
|
|
130
|
+
if (conflict.length > 0 || unmanaged.length > 0)
|
|
131
|
+
info('');
|
|
132
|
+
warn('Patch-backed materialized changes:');
|
|
133
|
+
printStatusGroups(patchBacked);
|
|
134
|
+
}
|
|
135
|
+
if (patchOwnedDrift.length > 0) {
|
|
136
|
+
if (conflict.length > 0 || unmanaged.length > 0 || patchBacked.length > 0)
|
|
137
|
+
info('');
|
|
138
|
+
warn('Patch-owned drift:');
|
|
139
|
+
printStatusGroups(patchOwnedDrift);
|
|
140
|
+
info('These files are claimed by exactly one patch, but engine/ no longer matches that patch output. Re-export the owning patch after reviewing the manual resolution.');
|
|
141
|
+
}
|
|
142
|
+
if (branding.length > 0) {
|
|
143
|
+
if (conflict.length > 0 ||
|
|
144
|
+
unmanaged.length > 0 ||
|
|
145
|
+
patchBacked.length > 0 ||
|
|
146
|
+
patchOwnedDrift.length > 0) {
|
|
147
|
+
info('');
|
|
148
|
+
}
|
|
149
|
+
warn('Tool-managed branding changes:');
|
|
150
|
+
printStatusGroups(branding);
|
|
151
|
+
}
|
|
152
|
+
if (furnace.length > 0) {
|
|
153
|
+
if (conflict.length > 0 ||
|
|
154
|
+
unmanaged.length > 0 ||
|
|
155
|
+
patchBacked.length > 0 ||
|
|
156
|
+
patchOwnedDrift.length > 0 ||
|
|
157
|
+
branding.length > 0) {
|
|
158
|
+
info('');
|
|
159
|
+
}
|
|
160
|
+
warn('Furnace-managed component changes:');
|
|
161
|
+
printStatusGroups(furnace);
|
|
162
|
+
}
|
|
163
|
+
if (conflict.length === 0 &&
|
|
164
|
+
unmanaged.length === 0 &&
|
|
165
|
+
patchBacked.length === 0 &&
|
|
166
|
+
patchOwnedDrift.length === 0 &&
|
|
167
|
+
branding.length === 0 &&
|
|
168
|
+
furnace.length === 0) {
|
|
169
|
+
info('No changes');
|
|
170
|
+
}
|
|
171
|
+
const parts = [];
|
|
172
|
+
if (conflict.length > 0)
|
|
173
|
+
parts.push(`${conflict.length} conflict`);
|
|
174
|
+
if (unmanaged.length > 0)
|
|
175
|
+
parts.push(`${unmanaged.length} unmanaged`);
|
|
176
|
+
if (patchBacked.length > 0)
|
|
177
|
+
parts.push(`${patchBacked.length} patch-backed`);
|
|
178
|
+
if (patchOwnedDrift.length > 0)
|
|
179
|
+
parts.push(`${patchOwnedDrift.length} patch-owned drift`);
|
|
180
|
+
if (branding.length > 0)
|
|
181
|
+
parts.push(`${branding.length} branding`);
|
|
182
|
+
if (furnace.length > 0)
|
|
183
|
+
parts.push(`${furnace.length} furnace`);
|
|
184
|
+
outro(parts.join(', '));
|
|
185
|
+
}
|
|
186
|
+
//# sourceMappingURL=status-output.js.map
|