@hominis/fireforge 0.30.1 → 0.32.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 +36 -0
- package/README.md +22 -0
- package/dist/src/commands/export-all.js +9 -16
- package/dist/src/commands/export-flow.d.ts +6 -0
- package/dist/src/commands/export-flow.js +6 -1
- package/dist/src/commands/export-placement-gate.d.ts +38 -0
- package/dist/src/commands/export-placement-gate.js +105 -0
- package/dist/src/commands/export-shared.d.ts +28 -0
- package/dist/src/commands/export-shared.js +46 -1
- package/dist/src/commands/export.js +52 -113
- package/dist/src/commands/furnace/chrome-doc-templates.d.ts +0 -13
- package/dist/src/commands/furnace/chrome-doc-templates.js +1 -1
- package/dist/src/commands/furnace/create-dry-run.d.ts +1 -1
- package/dist/src/commands/furnace/create.d.ts +1 -2
- package/dist/src/commands/furnace/deploy.js +36 -114
- package/dist/src/commands/furnace/refresh.js +52 -32
- package/dist/src/commands/furnace/sync.js +2 -0
- package/dist/src/commands/import.js +108 -73
- package/dist/src/commands/lint-per-patch.d.ts +3 -1
- package/dist/src/commands/lint-per-patch.js +265 -74
- package/dist/src/commands/lint.d.ts +1 -58
- package/dist/src/commands/lint.js +193 -88
- package/dist/src/commands/patch/compact.d.ts +5 -2
- package/dist/src/commands/patch/compact.js +85 -25
- package/dist/src/commands/patch/delete.js +17 -17
- package/dist/src/commands/patch/index.js +2 -0
- package/dist/src/commands/patch/lint-ignore.js +3 -16
- package/dist/src/commands/patch/move-files.js +2 -0
- package/dist/src/commands/patch/patch-context.d.ts +41 -0
- package/dist/src/commands/patch/patch-context.js +53 -0
- package/dist/src/commands/patch/rename.js +10 -15
- package/dist/src/commands/patch/reorder.d.ts +0 -2
- package/dist/src/commands/patch/reorder.js +18 -19
- package/dist/src/commands/patch/split-plan.d.ts +66 -0
- package/dist/src/commands/patch/split-plan.js +178 -0
- package/dist/src/commands/patch/split.d.ts +30 -0
- package/dist/src/commands/patch/split.js +283 -0
- package/dist/src/commands/patch/staged-dependency.d.ts +1 -7
- package/dist/src/commands/patch/staged-dependency.js +4 -17
- package/dist/src/commands/patch/tier.js +4 -17
- package/dist/src/commands/re-export-files.js +4 -1
- package/dist/src/commands/re-export-scan.js +8 -1
- package/dist/src/commands/re-export.js +8 -1
- package/dist/src/commands/rebase/summary.d.ts +1 -5
- package/dist/src/commands/rebase/summary.js +1 -1
- package/dist/src/commands/status-output.js +77 -68
- package/dist/src/commands/test-diagnose.d.ts +23 -0
- package/dist/src/commands/test-diagnose.js +210 -0
- package/dist/src/commands/test-run.d.ts +68 -0
- package/dist/src/commands/test-run.js +97 -0
- package/dist/src/commands/test.js +214 -263
- package/dist/src/commands/token.js +15 -1
- package/dist/src/commands/wire.js +109 -78
- package/dist/src/core/build-audit.d.ts +1 -1
- package/dist/src/core/build-audit.js +2 -46
- package/dist/src/core/build-baseline-types.d.ts +38 -0
- package/dist/src/core/build-baseline-types.js +10 -0
- package/dist/src/core/build-baseline.d.ts +1 -31
- package/dist/src/core/build-prepare.d.ts +1 -1
- package/dist/src/core/build-prepare.js +2 -45
- package/dist/src/core/config-paths.d.ts +0 -8
- package/dist/src/core/config-paths.js +4 -4
- package/dist/src/core/config-state.d.ts +0 -6
- package/dist/src/core/config-state.js +1 -1
- package/dist/src/core/config-validate-patch-policy.js +12 -13
- package/dist/src/core/config-validate.js +74 -28
- package/dist/src/core/engine-changes.d.ts +24 -0
- package/dist/src/core/engine-changes.js +64 -0
- package/dist/src/core/firefox-cache.d.ts +0 -5
- package/dist/src/core/firefox-cache.js +1 -1
- package/dist/src/core/firefox-download.d.ts +0 -6
- package/dist/src/core/firefox-download.js +1 -1
- package/dist/src/core/furnace-apply-helpers.d.ts +1 -8
- package/dist/src/core/furnace-apply-helpers.js +11 -20
- package/dist/src/core/furnace-apply.d.ts +1 -1
- package/dist/src/core/furnace-apply.js +1 -1
- package/dist/src/core/furnace-checksum-utils.d.ts +7 -0
- package/dist/src/core/furnace-checksum-utils.js +15 -0
- package/dist/src/core/furnace-config-validate.d.ts +31 -0
- package/dist/src/core/furnace-config-validate.js +133 -0
- package/dist/src/core/furnace-config.d.ts +4 -32
- package/dist/src/core/furnace-config.js +15 -111
- package/dist/src/core/furnace-constants.d.ts +0 -10
- package/dist/src/core/furnace-constants.js +2 -2
- package/dist/src/core/furnace-css-fragments.d.ts +79 -0
- package/dist/src/core/furnace-css-fragments.js +243 -0
- package/dist/src/core/furnace-jsconfig.d.ts +63 -0
- package/dist/src/core/furnace-jsconfig.js +191 -0
- package/dist/src/core/furnace-validate-helpers.d.ts +16 -14
- package/dist/src/core/furnace-validate-helpers.js +40 -1
- package/dist/src/core/furnace-validate-registration.js +16 -1
- package/dist/src/core/furnace-validate.js +54 -2
- package/dist/src/core/git-base.d.ts +15 -0
- package/dist/src/core/git-base.js +32 -0
- package/dist/src/core/git-diff.d.ts +8 -0
- package/dist/src/core/git-diff.js +224 -59
- package/dist/src/core/git-file-ops.d.ts +39 -12
- package/dist/src/core/git-file-ops.js +84 -3
- package/dist/src/core/lint-cache.d.ts +0 -13
- package/dist/src/core/lint-cache.js +5 -5
- package/dist/src/core/mach.d.ts +22 -1
- package/dist/src/core/mach.js +27 -2
- package/dist/src/core/manifest-register.d.ts +5 -16
- package/dist/src/core/manifest-register.js +3 -1
- package/dist/src/core/patch-lint-checkjs.d.ts +75 -21
- package/dist/src/core/patch-lint-checkjs.js +263 -71
- package/dist/src/core/patch-lint-css.d.ts +23 -0
- package/dist/src/core/patch-lint-css.js +172 -0
- package/dist/src/core/patch-lint-jsdoc.js +63 -4
- package/dist/src/core/patch-lint-observer.d.ts +37 -0
- package/dist/src/core/patch-lint-observer.js +168 -0
- package/dist/src/core/patch-lint.d.ts +34 -11
- package/dist/src/core/patch-lint.js +24 -161
- package/dist/src/core/patch-manifest-io.d.ts +16 -0
- package/dist/src/core/patch-manifest-io.js +44 -2
- package/dist/src/core/patch-manifest-validate.d.ts +1 -8
- package/dist/src/core/patch-manifest-validate.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-policy.d.ts +0 -4
- package/dist/src/core/patch-policy.js +10 -4
- package/dist/src/core/register-browser-content.d.ts +1 -1
- package/dist/src/core/register-module.d.ts +1 -1
- package/dist/src/core/register-result.d.ts +21 -0
- package/dist/src/core/register-result.js +9 -0
- package/dist/src/core/register-shared-css.d.ts +1 -1
- package/dist/src/core/register-test-manifest.d.ts +1 -1
- package/dist/src/core/test-harness-crash.d.ts +61 -0
- package/dist/src/core/test-harness-crash.js +140 -0
- package/dist/src/core/test-stale-check.d.ts +1 -1
- package/dist/src/core/test-stale-check.js +2 -46
- package/dist/src/core/test-xpcshell-retry.d.ts +9 -2
- package/dist/src/core/test-xpcshell-retry.js +10 -3
- package/dist/src/core/token-dark-mode.js +14 -26
- package/dist/src/core/token-manager.d.ts +4 -0
- package/dist/src/core/token-manager.js +70 -16
- package/dist/src/core/typecheck-shim.d.ts +3 -22
- package/dist/src/core/typecheck-shim.js +69 -7
- package/dist/src/core/wire-utils.js +37 -44
- package/dist/src/types/commands/index.d.ts +1 -1
- package/dist/src/types/commands/options.d.ts +122 -0
- package/dist/src/types/config.d.ts +11 -2
- package/dist/src/types/furnace.d.ts +12 -1
- package/dist/src/utils/elapsed.d.ts +0 -2
- package/dist/src/utils/elapsed.js +1 -1
- package/dist/src/utils/fs.d.ts +0 -5
- package/dist/src/utils/fs.js +1 -1
- package/dist/src/utils/regex.d.ts +0 -6
- package/dist/src/utils/regex.js +3 -3
- package/dist/src/utils/validation.d.ts +0 -8
- package/dist/src/utils/validation.js +2 -2
- package/package.json +6 -4
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* `fireforge patch split <source> --files <p...> --name <n>` — moves files
|
|
4
|
+
* out of an existing patch into a brand-new patch as one transaction
|
|
5
|
+
* (field report A4).
|
|
6
|
+
*
|
|
7
|
+
* Before this command, splitting a patch whose files have inbound
|
|
8
|
+
* forward-imports required a precise manual order: re-point each
|
|
9
|
+
* staged-dependency owner at a not-yet-created patch, shrink the source
|
|
10
|
+
* via `re-export --files --allow-shrink`, then `export --order` — any
|
|
11
|
+
* other order refused or needed `--force-unsafe`. Split performs the
|
|
12
|
+
* shrink, the new-patch creation, and the dependent owner rewrites under
|
|
13
|
+
* one patch-directory lock with rollback, validating only the final
|
|
14
|
+
* projection.
|
|
15
|
+
*
|
|
16
|
+
* Preconditions match `re-export`: the engine worktree must currently
|
|
17
|
+
* reflect both patches' content, because both bodies are regenerated from
|
|
18
|
+
* the worktree.
|
|
19
|
+
*/
|
|
20
|
+
import { join } from 'node:path';
|
|
21
|
+
import { getProjectPaths, loadConfig } from '../../core/config.js';
|
|
22
|
+
import { appendHistory, confirmDestructive } from '../../core/destructive.js';
|
|
23
|
+
import { normalizePatchArtifact } from '../../core/patch-artifact-normalize.js';
|
|
24
|
+
import { formatPatchNotFoundError } from '../../core/patch-identifier-suggest.js';
|
|
25
|
+
import { withPatchDirectoryLock } from '../../core/patch-lock.js';
|
|
26
|
+
import { loadPatchesManifest, renumberPatchesInManifest, resolvePatchIdentifier, savePatchesManifest, validatePatchesManifest, } from '../../core/patch-manifest.js';
|
|
27
|
+
import { enforcePatchPolicy } from '../../core/patch-policy.js';
|
|
28
|
+
import { GeneralError, InvalidArgumentError } from '../../errors/base.js';
|
|
29
|
+
import { toError } from '../../utils/errors.js';
|
|
30
|
+
import { readText, removeFile, writeText } from '../../utils/fs.js';
|
|
31
|
+
import { info, intro, outro, success, warn } from '../../utils/logger.js';
|
|
32
|
+
import { pickDefined } from '../../utils/options.js';
|
|
33
|
+
import { placementPlansEqual, resolvePlacementPlan } from '../export-flow.js';
|
|
34
|
+
import { runPatchLint } from '../export-shared.js';
|
|
35
|
+
import { assertSourceOwnsFiles, buildNewPatchMetadata, buildSplitDiff, buildSplitSummary, findOwnerRewriteHolders, projectSplitManifest, rewriteSplitOwners, runProjectedSplitLint, } from './split-plan.js';
|
|
36
|
+
/**
|
|
37
|
+
* Commits a confirmed split under the patch directory lock: renumber →
|
|
38
|
+
* write new patch body → write shrunken source body → single manifest
|
|
39
|
+
* rewrite (new row + shrunken source row + owner rewrites). On any
|
|
40
|
+
* failure the steps are rolled back in reverse order.
|
|
41
|
+
*/
|
|
42
|
+
async function commitPatchSplit(patchesDir, plan, newMetadata, options) {
|
|
43
|
+
await withPatchDirectoryLock(patchesDir, async () => {
|
|
44
|
+
const manifest = await loadPatchesManifest(patchesDir);
|
|
45
|
+
if (!manifest)
|
|
46
|
+
throw new GeneralError('Manifest disappeared while waiting for lock.');
|
|
47
|
+
const current = manifest.patches.find((p) => p.filename === plan.source.filename);
|
|
48
|
+
if (!current || current.filesAffected.join('\n') !== plan.source.filesAffected.join('\n')) {
|
|
49
|
+
throw new InvalidArgumentError('Patch queue changed while waiting for split confirmation. Re-run the command.', 'patch split');
|
|
50
|
+
}
|
|
51
|
+
const currentPlacement = await resolvePlacementPlan(patchesDir, plan.placementOptions, plan.category, plan.name);
|
|
52
|
+
if (!placementPlansEqual(currentPlacement, plan.placement)) {
|
|
53
|
+
throw new InvalidArgumentError('Patch queue changed while waiting for split confirmation. Re-run the command.', 'patch split');
|
|
54
|
+
}
|
|
55
|
+
const movedSet = new Set(plan.movedFiles);
|
|
56
|
+
const effectiveSourceFilename = plan.placement.renameMap.get(plan.source.filename)?.newFilename ?? plan.source.filename;
|
|
57
|
+
const newPatchPath = join(patchesDir, plan.placement.newFilename);
|
|
58
|
+
const sourcePathBefore = join(patchesDir, plan.source.filename);
|
|
59
|
+
const sourcePathAfter = join(patchesDir, effectiveSourceFilename);
|
|
60
|
+
const originalSourceBody = await readText(sourcePathBefore);
|
|
61
|
+
let renumberApplied = false;
|
|
62
|
+
let newPatchWritten = false;
|
|
63
|
+
let sourceRewritten = false;
|
|
64
|
+
try {
|
|
65
|
+
if (plan.placement.renameMap.size > 0) {
|
|
66
|
+
await renumberPatchesInManifest(patchesDir, plan.placement.renameMap);
|
|
67
|
+
renumberApplied = true;
|
|
68
|
+
}
|
|
69
|
+
await writeText(newPatchPath, normalizePatchArtifact(plan.movedDiff));
|
|
70
|
+
newPatchWritten = true;
|
|
71
|
+
await writeText(sourcePathAfter, normalizePatchArtifact(plan.remainingDiff));
|
|
72
|
+
sourceRewritten = true;
|
|
73
|
+
const fresh = await loadPatchesManifest(patchesDir);
|
|
74
|
+
if (!fresh)
|
|
75
|
+
throw new GeneralError('Manifest disappeared during split commit.');
|
|
76
|
+
const updatedPatches = fresh.patches.map((patch) => {
|
|
77
|
+
const withOwners = rewriteSplitOwners(patch, effectiveSourceFilename, movedSet, plan.placement.newFilename);
|
|
78
|
+
if (patch.filename !== effectiveSourceFilename)
|
|
79
|
+
return withOwners;
|
|
80
|
+
return { ...withOwners, filesAffected: plan.remainingFiles };
|
|
81
|
+
});
|
|
82
|
+
updatedPatches.push(newMetadata);
|
|
83
|
+
updatedPatches.sort((a, b) => a.order - b.order || a.filename.localeCompare(b.filename));
|
|
84
|
+
const updated = validatePatchesManifest({ ...fresh, patches: updatedPatches });
|
|
85
|
+
await savePatchesManifest(patchesDir, updated);
|
|
86
|
+
try {
|
|
87
|
+
await appendHistory(patchesDir, {
|
|
88
|
+
operation: 'patch-split',
|
|
89
|
+
args: {
|
|
90
|
+
source: effectiveSourceFilename,
|
|
91
|
+
newFilename: plan.placement.newFilename,
|
|
92
|
+
order: plan.placement.insertionOrder,
|
|
93
|
+
files: plan.movedFiles,
|
|
94
|
+
ownerRewrites: plan.ownerRewrites,
|
|
95
|
+
renames: [...plan.placement.renameMap.entries()].map(([from, entry]) => ({
|
|
96
|
+
from,
|
|
97
|
+
to: entry.newFilename,
|
|
98
|
+
})),
|
|
99
|
+
},
|
|
100
|
+
...(options.yes === true ? { yes: true } : {}),
|
|
101
|
+
...(options.forceUnsafe === true ? { unsafeOverride: true } : {}),
|
|
102
|
+
result: 'ok',
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
catch (historyError) {
|
|
106
|
+
warn(`History log append failed after patch split committed: ${toError(historyError).message}`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
// Reverse-order rollback; each step warns on its own failure so the
|
|
111
|
+
// original error stays visible.
|
|
112
|
+
if (sourceRewritten) {
|
|
113
|
+
try {
|
|
114
|
+
await writeText(sourcePathAfter, originalSourceBody);
|
|
115
|
+
}
|
|
116
|
+
catch (rollbackError) {
|
|
117
|
+
warn(`Rollback warning: could not restore source body: ${toError(rollbackError).message}`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
if (newPatchWritten) {
|
|
121
|
+
try {
|
|
122
|
+
await removeFile(newPatchPath);
|
|
123
|
+
}
|
|
124
|
+
catch (rollbackError) {
|
|
125
|
+
warn(`Rollback warning: could not remove new patch file: ${toError(rollbackError).message}`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (renumberApplied) {
|
|
129
|
+
const inverseMap = new Map([...plan.placement.renameMap.entries()].map(([oldFilename, entry]) => [
|
|
130
|
+
entry.newFilename,
|
|
131
|
+
{
|
|
132
|
+
newOrder: parseInt(oldFilename.split('-')[0] ?? '0', 10),
|
|
133
|
+
newFilename: oldFilename,
|
|
134
|
+
},
|
|
135
|
+
]));
|
|
136
|
+
try {
|
|
137
|
+
await renumberPatchesInManifest(patchesDir, inverseMap);
|
|
138
|
+
}
|
|
139
|
+
catch (rollbackError) {
|
|
140
|
+
warn(`Rollback warning: could not invert renumber: ${toError(rollbackError).message}`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
try {
|
|
144
|
+
await savePatchesManifest(patchesDir, manifest);
|
|
145
|
+
}
|
|
146
|
+
catch (rollbackError) {
|
|
147
|
+
warn(`Rollback warning: could not restore manifest: ${toError(rollbackError).message}`);
|
|
148
|
+
}
|
|
149
|
+
throw error;
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Runs the `patch split` command: plans the split, lints the projection,
|
|
155
|
+
* confirms, and commits transactionally.
|
|
156
|
+
*/
|
|
157
|
+
export async function patchSplitCommand(projectRoot, sourceId, options) {
|
|
158
|
+
intro(options.dryRun ? 'FireForge patch split (dry run)' : 'FireForge patch split');
|
|
159
|
+
const paths = getProjectPaths(projectRoot);
|
|
160
|
+
const config = await loadConfig(projectRoot);
|
|
161
|
+
const manifest = await loadPatchesManifest(paths.patches);
|
|
162
|
+
if (!manifest || manifest.patches.length === 0) {
|
|
163
|
+
throw new GeneralError('No patches in manifest.');
|
|
164
|
+
}
|
|
165
|
+
const source = resolvePatchIdentifier(sourceId, manifest.patches);
|
|
166
|
+
if (!source) {
|
|
167
|
+
throw new InvalidArgumentError(formatPatchNotFoundError(sourceId, manifest.patches), 'patch split');
|
|
168
|
+
}
|
|
169
|
+
const movedFiles = [...new Set(options.files.map((f) => f.trim()).filter(Boolean))].sort();
|
|
170
|
+
if (movedFiles.length === 0) {
|
|
171
|
+
throw new InvalidArgumentError('patch split requires at least one --files path.', '--files');
|
|
172
|
+
}
|
|
173
|
+
assertSourceOwnsFiles(source, movedFiles);
|
|
174
|
+
const movedSet = new Set(movedFiles);
|
|
175
|
+
const remainingFiles = source.filesAffected.filter((f) => !movedSet.has(f));
|
|
176
|
+
if (remainingFiles.length === 0) {
|
|
177
|
+
throw new InvalidArgumentError(`Splitting every file out of ${source.filename} would leave it empty. ` +
|
|
178
|
+
'Use "fireforge patch rename" / "fireforge patch reorder" to repurpose or move the whole patch instead.', '--files');
|
|
179
|
+
}
|
|
180
|
+
const movedDiff = await buildSplitDiff(paths.engine, movedFiles, 'moved', source.filename);
|
|
181
|
+
const remainingDiff = await buildSplitDiff(paths.engine, remainingFiles, 'remaining', source.filename);
|
|
182
|
+
const category = options.category ?? source.category;
|
|
183
|
+
const placementOptions = pickDefined({
|
|
184
|
+
order: options.order,
|
|
185
|
+
before: options.before,
|
|
186
|
+
after: options.after ??
|
|
187
|
+
(options.order === undefined && options.before === undefined ? source.filename : undefined),
|
|
188
|
+
});
|
|
189
|
+
const placement = await resolvePlacementPlan(paths.patches, placementOptions, category, options.name);
|
|
190
|
+
const plan = {
|
|
191
|
+
source,
|
|
192
|
+
movedFiles,
|
|
193
|
+
remainingFiles,
|
|
194
|
+
movedDiff,
|
|
195
|
+
remainingDiff,
|
|
196
|
+
placement,
|
|
197
|
+
placementOptions,
|
|
198
|
+
category,
|
|
199
|
+
name: options.name,
|
|
200
|
+
description: options.description ?? '',
|
|
201
|
+
ownerRewrites: findOwnerRewriteHolders(manifest.patches, source.filename, movedSet),
|
|
202
|
+
};
|
|
203
|
+
// Per-patch lint both projected bodies, threading the source patch's
|
|
204
|
+
// tier/lintIgnore so an intentional-advisory patch can still split.
|
|
205
|
+
const ignoreChecks = source.lintIgnore ? new Set(source.lintIgnore) : undefined;
|
|
206
|
+
await runPatchLint(paths.engine, remainingFiles, remainingDiff, config, options.skipLint, undefined, ignoreChecks, source.tier);
|
|
207
|
+
await runPatchLint(paths.engine, movedFiles, movedDiff, config, options.skipLint, undefined, ignoreChecks, source.tier);
|
|
208
|
+
const conflicts = await runProjectedSplitLint(paths.patches, plan);
|
|
209
|
+
const newMetadata = buildNewPatchMetadata(plan, config);
|
|
210
|
+
enforcePatchPolicy({
|
|
211
|
+
config,
|
|
212
|
+
manifest: projectSplitManifest(manifest, plan, newMetadata),
|
|
213
|
+
command: 'patch split',
|
|
214
|
+
forceUnsafe: options.forceUnsafe === true,
|
|
215
|
+
});
|
|
216
|
+
const decision = await confirmDestructive({
|
|
217
|
+
operation: 'patch-split',
|
|
218
|
+
title: `Split ${plan.movedFiles.length} file(s) out of ${source.filename} into ${placement.newFilename}`,
|
|
219
|
+
summary: buildSplitSummary(plan),
|
|
220
|
+
yes: options.yes === true,
|
|
221
|
+
dryRun: options.dryRun === true,
|
|
222
|
+
unsafeOverride: options.forceUnsafe === true,
|
|
223
|
+
conflicts,
|
|
224
|
+
});
|
|
225
|
+
if (decision === 'dry-run') {
|
|
226
|
+
outro('Dry run complete — no changes made');
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
if (decision === 'cancelled') {
|
|
230
|
+
outro('Split cancelled');
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
await commitPatchSplit(paths.patches, plan, newMetadata, options);
|
|
234
|
+
success(`Split ${source.filename}: ${placement.newFilename} now owns ${plan.movedFiles.length} file(s)`);
|
|
235
|
+
if (plan.ownerRewrites.length > 0) {
|
|
236
|
+
info(`Re-pointed staged-dependency owners in: ${plan.ownerRewrites.join(', ')}`);
|
|
237
|
+
}
|
|
238
|
+
outro('Split complete');
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Registers the `patch split` subcommand on the `patch` parent.
|
|
242
|
+
*/
|
|
243
|
+
export function registerPatchSplit(parent, context) {
|
|
244
|
+
const { getProjectRoot, withErrorHandling } = context;
|
|
245
|
+
parent
|
|
246
|
+
.command('split <source>')
|
|
247
|
+
.description('Move files out of a patch into a new patch as one transaction (shrink + create + staged-dependency owner rewrites)')
|
|
248
|
+
.requiredOption('--files <path...>', 'Engine-relative files to move from the source patch to the new patch')
|
|
249
|
+
.requiredOption('--name <name>', 'Name for the new patch')
|
|
250
|
+
.option('--category <category>', "Category for the new patch (default: the source patch's)")
|
|
251
|
+
.option('--description <desc>', 'Description for the new patch')
|
|
252
|
+
.option('--order <n>', 'Exact sparse order for the new patch', (raw) => {
|
|
253
|
+
const n = Number.parseInt(raw, 10);
|
|
254
|
+
if (!Number.isInteger(n) || n <= 0) {
|
|
255
|
+
throw new InvalidArgumentError(`--order must be a positive integer, got "${raw}".`, '--order');
|
|
256
|
+
}
|
|
257
|
+
return n;
|
|
258
|
+
})
|
|
259
|
+
.option('--before <patch>', 'Place the new patch before this patch')
|
|
260
|
+
.option('--after <patch>', 'Place the new patch after this patch (default: the source patch)')
|
|
261
|
+
.option('--dry-run', 'Show what would happen without writing')
|
|
262
|
+
.option('-y, --yes', 'Skip confirmation prompt (required for non-TTY)')
|
|
263
|
+
.option('--force-unsafe', 'Bypass projected-lint refusals')
|
|
264
|
+
.option('--skip-lint', 'Skip per-patch lint of the projected bodies')
|
|
265
|
+
.action(withErrorHandling(async (sourceId, options) => {
|
|
266
|
+
await patchSplitCommand(getProjectRoot(), sourceId, {
|
|
267
|
+
files: options.files,
|
|
268
|
+
name: options.name,
|
|
269
|
+
...pickDefined({
|
|
270
|
+
category: options.category,
|
|
271
|
+
description: options.description,
|
|
272
|
+
order: options.order,
|
|
273
|
+
before: options.before,
|
|
274
|
+
after: options.after,
|
|
275
|
+
dryRun: options.dryRun,
|
|
276
|
+
yes: options.yes,
|
|
277
|
+
forceUnsafe: options.forceUnsafe,
|
|
278
|
+
skipLint: options.skipLint,
|
|
279
|
+
}),
|
|
280
|
+
});
|
|
281
|
+
}));
|
|
282
|
+
}
|
|
283
|
+
//# sourceMappingURL=split.js.map
|
|
@@ -4,12 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { Command } from 'commander';
|
|
6
6
|
import type { CommandContext } from '../../types/cli.js';
|
|
7
|
-
import type { PatchStagedDependencyOptions
|
|
8
|
-
type StagedDependencyMode = 'add' | 'remove' | 'clear';
|
|
9
|
-
/**
|
|
10
|
-
* Renders a one-line summary of a staged-dependency metadata change.
|
|
11
|
-
*/
|
|
12
|
-
export declare function describeStagedDependencyChange(before: readonly PatchStagedForwardImport[], after: readonly PatchStagedForwardImport[], mode: StagedDependencyMode, dependency: PatchStagedForwardImport | undefined): string;
|
|
7
|
+
import type { PatchStagedDependencyOptions } from '../../types/commands/index.js';
|
|
13
8
|
/**
|
|
14
9
|
* Runs the metadata-only staged-dependency mutation command.
|
|
15
10
|
*
|
|
@@ -25,4 +20,3 @@ export declare function patchStagedDependencyCommand(projectRoot: string, identi
|
|
|
25
20
|
* @param context - Shared CLI registration context
|
|
26
21
|
*/
|
|
27
22
|
export declare function registerPatchStagedDependency(parent: Command, context: CommandContext): void;
|
|
28
|
-
export {};
|
|
@@ -3,15 +3,12 @@
|
|
|
3
3
|
* `fireforge patch staged-dependency <name>` — edits
|
|
4
4
|
* PatchMetadata.stagedDependencies without rewriting the .patch body.
|
|
5
5
|
*/
|
|
6
|
-
import { getProjectPaths } from '../../core/config.js';
|
|
7
6
|
import { appendHistory } from '../../core/destructive.js';
|
|
8
7
|
import { mutatePatchMetadata } from '../../core/patch-export.js';
|
|
9
|
-
import { formatPatchNotFoundError } from '../../core/patch-identifier-suggest.js';
|
|
10
|
-
import { loadPatchesManifest, resolvePatchIdentifier } from '../../core/patch-manifest.js';
|
|
11
8
|
import { GeneralError, InvalidArgumentError } from '../../errors/base.js';
|
|
12
9
|
import { toError } from '../../utils/errors.js';
|
|
13
|
-
import { pathExists } from '../../utils/fs.js';
|
|
14
10
|
import { info, intro, outro, warn } from '../../utils/logger.js';
|
|
11
|
+
import { requirePatchQueue, requirePatchTarget } from './patch-context.js';
|
|
15
12
|
function modeFromOptions(options) {
|
|
16
13
|
const adding = options.add === true;
|
|
17
14
|
const removing = options.remove === true;
|
|
@@ -69,7 +66,7 @@ function applyMode(existing, mode, dependency) {
|
|
|
69
66
|
/**
|
|
70
67
|
* Renders a one-line summary of a staged-dependency metadata change.
|
|
71
68
|
*/
|
|
72
|
-
|
|
69
|
+
function describeStagedDependencyChange(before, after, mode, dependency) {
|
|
73
70
|
if (mode === 'clear') {
|
|
74
71
|
return before.length === 0
|
|
75
72
|
? 'stagedDependencies was already empty — no change'
|
|
@@ -98,18 +95,8 @@ export async function patchStagedDependencyCommand(projectRoot, identifier, opti
|
|
|
98
95
|
intro(isDryRun ? 'FireForge patch staged-dependency (dry run)' : 'FireForge patch staged-dependency');
|
|
99
96
|
const mode = modeFromOptions(options);
|
|
100
97
|
const dependency = mode === 'clear' ? undefined : requireForwardImportOptions(options, mode);
|
|
101
|
-
const paths =
|
|
102
|
-
|
|
103
|
-
throw new GeneralError('Patches directory not found.');
|
|
104
|
-
}
|
|
105
|
-
const manifest = await loadPatchesManifest(paths.patches);
|
|
106
|
-
if (!manifest || manifest.patches.length === 0) {
|
|
107
|
-
throw new GeneralError('No patches in manifest.');
|
|
108
|
-
}
|
|
109
|
-
const target = resolvePatchIdentifier(identifier, manifest.patches);
|
|
110
|
-
if (!target) {
|
|
111
|
-
throw new InvalidArgumentError(formatPatchNotFoundError(identifier, manifest.patches), identifier);
|
|
112
|
-
}
|
|
98
|
+
const { paths, manifest } = await requirePatchQueue(projectRoot);
|
|
99
|
+
const target = requirePatchTarget(identifier, manifest.patches);
|
|
113
100
|
const existing = target.stagedDependencies?.forwardImports ?? [];
|
|
114
101
|
const projected = applyMode(existing, mode, dependency);
|
|
115
102
|
const summary = describeStagedDependencyChange(existing, projected, mode, dependency);
|
|
@@ -15,16 +15,13 @@
|
|
|
15
15
|
* `--clear` must be supplied per invocation.
|
|
16
16
|
*/
|
|
17
17
|
import { Option } from 'commander';
|
|
18
|
-
import { getProjectPaths } from '../../core/config.js';
|
|
19
18
|
import { appendHistory } from '../../core/destructive.js';
|
|
20
19
|
import { updatePatchMetadata } from '../../core/patch-export.js';
|
|
21
|
-
import {
|
|
22
|
-
import { loadPatchesManifest, resolvePatchIdentifier } from '../../core/patch-manifest.js';
|
|
23
|
-
import { GeneralError, InvalidArgumentError } from '../../errors/base.js';
|
|
20
|
+
import { InvalidArgumentError } from '../../errors/base.js';
|
|
24
21
|
import { toError } from '../../utils/errors.js';
|
|
25
|
-
import { pathExists } from '../../utils/fs.js';
|
|
26
22
|
import { info, intro, outro, warn } from '../../utils/logger.js';
|
|
27
23
|
import { pickDefined } from '../../utils/options.js';
|
|
24
|
+
import { requirePatchQueue, requirePatchTarget } from './patch-context.js';
|
|
28
25
|
/**
|
|
29
26
|
* Runs the `patch tier` command: updates `PatchMetadata.tier` on the
|
|
30
27
|
* named patch (or clears the field) and writes the manifest.
|
|
@@ -47,18 +44,8 @@ export async function patchTierCommand(projectRoot, identifier, options = {}) {
|
|
|
47
44
|
if (!setting && !clearing) {
|
|
48
45
|
throw new InvalidArgumentError('Specify --tier <tier> to set the override, or --clear to remove it.', 'patch tier');
|
|
49
46
|
}
|
|
50
|
-
const paths =
|
|
51
|
-
|
|
52
|
-
throw new GeneralError('Patches directory not found.');
|
|
53
|
-
}
|
|
54
|
-
const manifest = await loadPatchesManifest(paths.patches);
|
|
55
|
-
if (!manifest || manifest.patches.length === 0) {
|
|
56
|
-
throw new GeneralError('No patches in manifest.');
|
|
57
|
-
}
|
|
58
|
-
const target = resolvePatchIdentifier(identifier, manifest.patches);
|
|
59
|
-
if (!target) {
|
|
60
|
-
throw new InvalidArgumentError(formatPatchNotFoundError(identifier, manifest.patches), identifier);
|
|
61
|
-
}
|
|
47
|
+
const { paths, manifest } = await requirePatchQueue(projectRoot);
|
|
48
|
+
const target = requirePatchTarget(identifier, manifest.patches);
|
|
62
49
|
const before = target.tier;
|
|
63
50
|
const after = setting ? options.tier : undefined;
|
|
64
51
|
if (before === after) {
|
|
@@ -198,7 +198,10 @@ export async function reExportFilesInPlace(paths, selectedPatches, options, conf
|
|
|
198
198
|
// standard re-export path).
|
|
199
199
|
const { effectiveTier, effectiveLintIgnore, flagIgnoreSet } = resolveEffectiveTierAndLintIgnore(target, options);
|
|
200
200
|
const ignoreChecks = effectiveLintIgnore ? new Set(effectiveLintIgnore) : undefined;
|
|
201
|
-
await
|
|
201
|
+
const patchQueueCtx = (await pathExists(paths.patches))
|
|
202
|
+
? await buildPatchQueueContext(paths.patches)
|
|
203
|
+
: undefined;
|
|
204
|
+
await runPatchLint(paths.engine, actualProjectedFiles, projectedDiff, config, options.skipLint, patchQueueCtx, ignoreChecks, effectiveTier);
|
|
202
205
|
const conflicts = await runProjectedCrossPatchLint(paths.patches, target.filename, projectedDiff);
|
|
203
206
|
const filesUpdates = buildFilesModeMetadataUpdates(actualProjectedFiles, options, effectiveLintIgnore, flagIgnoreSet);
|
|
204
207
|
const manifest = await loadPatchesManifest(paths.patches);
|
|
@@ -47,9 +47,16 @@ async function scanPatchFiles(args) {
|
|
|
47
47
|
for (const f of [...modifiedFiles, ...untrackedFiles])
|
|
48
48
|
discoveredFiles.add(f);
|
|
49
49
|
}
|
|
50
|
+
// Git pathspecs recurse, so a claimed file in a shallow directory would
|
|
51
|
+
// sweep entire subtrees into the candidate set — with several patches
|
|
52
|
+
// sharing a parent directory, every unmanaged file in the tree gets
|
|
53
|
+
// offered to whichever patch is scanned first. Constrain the broad scan
|
|
54
|
+
// to the patch's exact directory footprint; deeper paths need an
|
|
55
|
+
// explicit --scan-file / --scan-files assignment.
|
|
56
|
+
const parentDirSet = new Set(parentDirs);
|
|
50
57
|
const currentSet = new Set(currentFilesAffected);
|
|
51
58
|
const added = [...discoveredFiles]
|
|
52
|
-
.filter((f) => !currentSet.has(f) && !claimedByOthers.has(f))
|
|
59
|
+
.filter((f) => parentDirSet.has(dirname(f)) && !currentSet.has(f) && !claimedByOthers.has(f))
|
|
53
60
|
.sort();
|
|
54
61
|
const removed = await findRemovedFiles(currentFilesAffected, engineDir);
|
|
55
62
|
return reportScanResult(currentFilesAffected, patchFilename, isDryRun, added, removed);
|
|
@@ -7,6 +7,7 @@ import { isGitRepository } from '../core/git.js';
|
|
|
7
7
|
import { getDiffForFilesAgainstHead } from '../core/git-diff.js';
|
|
8
8
|
import { getModifiedFilesInDir, getUntrackedFilesInDir } from '../core/git-status.js';
|
|
9
9
|
import { updatePatchAndMetadata } from '../core/patch-export.js';
|
|
10
|
+
import { buildPatchQueueContext } from '../core/patch-lint.js';
|
|
10
11
|
import { getClaimedFiles, loadPatchesManifest, resolvePatchIdentifier, stampPatchVersions, } from '../core/patch-manifest.js';
|
|
11
12
|
import { buildProjectedManifest, enforcePatchPolicy } from '../core/patch-policy.js';
|
|
12
13
|
import { GeneralError, InvalidArgumentError } from '../errors/base.js';
|
|
@@ -185,7 +186,13 @@ async function reExportSinglePatch(patch, paths, manifest, options, isDryRun, co
|
|
|
185
186
|
command: 're-export',
|
|
186
187
|
forceUnsafe: options.forceUnsafe === true,
|
|
187
188
|
});
|
|
188
|
-
|
|
189
|
+
// Pass the whole-queue context so checkJs resolves cross-patch
|
|
190
|
+
// `resource:///` imports against the real owning sources (report scope
|
|
191
|
+
// stays this patch — see runPatchLint).
|
|
192
|
+
const patchQueueCtx = (await pathExists(paths.patches))
|
|
193
|
+
? await buildPatchQueueContext(paths.patches)
|
|
194
|
+
: undefined;
|
|
195
|
+
await runPatchLint(paths.engine, existingFiles, diffContent, config, options.skipLint, patchQueueCtx, ignoreChecks, effectiveTier);
|
|
189
196
|
if (isDryRun) {
|
|
190
197
|
info(`[dry-run] ${patch.filename}: ${existingFiles.length} file(s)`);
|
|
191
198
|
if (effectiveTier !== undefined && effectiveTier !== patch.tier) {
|
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Rebase summary and status label formatting.
|
|
3
3
|
*/
|
|
4
|
-
import type {
|
|
5
|
-
/**
|
|
6
|
-
* Formats a status label for a rebase patch entry.
|
|
7
|
-
*/
|
|
8
|
-
export declare function statusLabel(status: RebasePatchEntry['status'], fuzzFactor?: number): string;
|
|
4
|
+
import type { RebaseSession } from '../../core/rebase-session.js';
|
|
9
5
|
/**
|
|
10
6
|
* Prints the rebase summary table.
|
|
11
7
|
*/
|
|
@@ -6,7 +6,7 @@ import { info } from '../../utils/logger.js';
|
|
|
6
6
|
/**
|
|
7
7
|
* Formats a status label for a rebase patch entry.
|
|
8
8
|
*/
|
|
9
|
-
|
|
9
|
+
function statusLabel(status, fuzzFactor) {
|
|
10
10
|
switch (status) {
|
|
11
11
|
case 'applied-clean':
|
|
12
12
|
return 'applied cleanly';
|
|
@@ -105,82 +105,91 @@ export async function renderUnmanagedOnly(unmanagedFiles, totalModified, project
|
|
|
105
105
|
? 'No unmanaged changes'
|
|
106
106
|
: `${unmanagedFiles.length} unmanaged change${unmanagedFiles.length === 1 ? '' : 's'}`);
|
|
107
107
|
}
|
|
108
|
+
/** Renders the cross-patch ownership conflict section. */
|
|
109
|
+
function renderConflictSection(conflict) {
|
|
110
|
+
warn('Cross-patch ownership conflicts (same file claimed by multiple patches):');
|
|
111
|
+
printStatusGroups(conflict);
|
|
112
|
+
for (const entry of conflict) {
|
|
113
|
+
if (entry.claimedBy && entry.claimedBy.length > 0) {
|
|
114
|
+
info(` ${entry.file} — claimed by ${entry.claimedBy.join(', ')}`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
info('Run "fireforge status --ownership" for the full conflict table, then repartition with "fireforge re-export --files <paths> <patch>".');
|
|
118
|
+
}
|
|
108
119
|
/** Renders the default classified status buckets. */
|
|
109
120
|
export async function renderDefaultStatus(totalModified, buckets, projectRoot, binaryName) {
|
|
110
121
|
const { conflict, unmanaged, patchBacked, patchOwnedDrift, branding, furnace } = buckets;
|
|
111
122
|
info(`${totalModified} modified file${totalModified === 1 ? '' : 's'}:\n`);
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
123
|
+
// Sections render in this fixed order, separated by a blank line
|
|
124
|
+
// whenever an earlier section already printed (the pre-refactor code
|
|
125
|
+
// expressed the same rule as per-section "any earlier bucket
|
|
126
|
+
// non-empty" conditions).
|
|
127
|
+
const sections = [
|
|
128
|
+
{
|
|
129
|
+
files: conflict,
|
|
130
|
+
label: 'conflict',
|
|
131
|
+
render: () => {
|
|
132
|
+
renderConflictSection(conflict);
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
files: unmanaged,
|
|
137
|
+
label: 'unmanaged',
|
|
138
|
+
render: async () => {
|
|
139
|
+
warn('Unmanaged changes:');
|
|
140
|
+
printStatusGroups(unmanaged);
|
|
141
|
+
await printUnregisteredWarnings(unmanaged, projectRoot, binaryName);
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
files: patchBacked,
|
|
146
|
+
label: 'patch-backed',
|
|
147
|
+
render: () => {
|
|
148
|
+
warn('Patch-backed materialized changes:');
|
|
149
|
+
printStatusGroups(patchBacked);
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
files: patchOwnedDrift,
|
|
154
|
+
label: 'patch-owned drift',
|
|
155
|
+
render: () => {
|
|
156
|
+
warn('Patch-owned drift:');
|
|
157
|
+
printStatusGroups(patchOwnedDrift);
|
|
158
|
+
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.');
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
files: branding,
|
|
163
|
+
label: 'branding',
|
|
164
|
+
render: () => {
|
|
165
|
+
warn('Tool-managed branding changes:');
|
|
166
|
+
printStatusGroups(branding);
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
files: furnace,
|
|
171
|
+
label: 'furnace',
|
|
172
|
+
render: () => {
|
|
173
|
+
warn('Furnace-managed component changes:');
|
|
174
|
+
printStatusGroups(furnace);
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
];
|
|
178
|
+
let printedAny = false;
|
|
179
|
+
for (const section of sections) {
|
|
180
|
+
if (section.files.length === 0)
|
|
181
|
+
continue;
|
|
182
|
+
if (printedAny)
|
|
131
183
|
info('');
|
|
132
|
-
|
|
133
|
-
|
|
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);
|
|
184
|
+
await section.render();
|
|
185
|
+
printedAny = true;
|
|
162
186
|
}
|
|
163
|
-
if (
|
|
164
|
-
unmanaged.length === 0 &&
|
|
165
|
-
patchBacked.length === 0 &&
|
|
166
|
-
patchOwnedDrift.length === 0 &&
|
|
167
|
-
branding.length === 0 &&
|
|
168
|
-
furnace.length === 0) {
|
|
187
|
+
if (!printedAny) {
|
|
169
188
|
info('No changes');
|
|
170
189
|
}
|
|
171
|
-
const parts =
|
|
172
|
-
|
|
173
|
-
|
|
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`);
|
|
190
|
+
const parts = sections
|
|
191
|
+
.filter((section) => section.files.length > 0)
|
|
192
|
+
.map((section) => `${section.files.length} ${section.label}`);
|
|
184
193
|
outro(parts.join(', '));
|
|
185
194
|
}
|
|
186
195
|
//# sourceMappingURL=status-output.js.map
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Failure diagnosis for `fireforge test`: maps captured mach output to
|
|
3
|
+
* actionable operator messages (unknown test paths, stale build artifacts,
|
|
4
|
+
* fork-module registration, xpcshell appdir, harness symlinks, mochitest
|
|
5
|
+
* branding interactions), and applies harness-run verdicts from
|
|
6
|
+
* `test-harness-crash.ts` for single and sharded invocations. Split out of
|
|
7
|
+
* `test.ts` to keep both files within the per-file line budget.
|
|
8
|
+
*/
|
|
9
|
+
import { type PostRebuildFailureContext } from '../core/test-harness-output.js';
|
|
10
|
+
import type { TestRunOutcome } from './test-run.js';
|
|
11
|
+
/**
|
|
12
|
+
* Applies the harness-run verdict for a single (non-sharded) invocation:
|
|
13
|
+
* exhausted harness-crash retries and silent zero-TEST-START runs are
|
|
14
|
+
* harness problems with their own messages; everything else flows into
|
|
15
|
+
* the regular non-zero-exit diagnosis chain.
|
|
16
|
+
*/
|
|
17
|
+
export declare function finalizeSingleRunOutcome(outcome: TestRunOutcome, normalizedPaths: string[], binaryName: string, postRebuildContext: PostRebuildFailureContext | undefined): void;
|
|
18
|
+
/**
|
|
19
|
+
* Shard-mode adapter over {@link handleNonZeroTestExit}: produces the
|
|
20
|
+
* diagnosis text as a string (to warn per shard) instead of throwing, so
|
|
21
|
+
* later shards still run and the aggregate error stays singular.
|
|
22
|
+
*/
|
|
23
|
+
export declare function diagnoseShardOutcome(outcome: TestRunOutcome, path: string, binaryName: string, postRebuildContext: PostRebuildFailureContext | undefined): string | undefined;
|