@hominis/fireforge 0.52.0 → 0.54.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 +45 -0
- package/dist/build-info.json +4 -4
- package/dist/src/commands/bootstrap.js +2 -3
- package/dist/src/commands/build-prelock.d.ts +3 -3
- package/dist/src/commands/build-prelock.js +3 -3
- package/dist/src/commands/build.js +4 -0
- package/dist/src/commands/declarations-report.d.ts +9 -3
- package/dist/src/commands/declarations-report.js +18 -11
- package/dist/src/commands/discard.js +3 -3
- package/dist/src/commands/doctor-furnace-config-sync.js +4 -4
- package/dist/src/commands/doctor-furnace.js +5 -5
- package/dist/src/commands/export-adopt-check.d.ts +5 -6
- package/dist/src/commands/export-adopt-check.js +5 -6
- package/dist/src/commands/export-flow.js +6 -9
- package/dist/src/commands/export-staged-registration.d.ts +4 -4
- package/dist/src/commands/export-staged-registration.js +4 -4
- package/dist/src/commands/forward-import-remedy.d.ts +2 -3
- package/dist/src/commands/forward-import-remedy.js +2 -3
- package/dist/src/commands/furnace/create-dry-run.d.ts +2 -2
- package/dist/src/commands/furnace/deploy-fragment-includers.d.ts +2 -2
- package/dist/src/commands/furnace/deploy-fragment-includers.js +2 -2
- package/dist/src/commands/furnace/index.js +2 -2
- package/dist/src/commands/lint.js +4 -4
- package/dist/src/commands/patch/compact.js +1 -1
- package/dist/src/commands/patch/lint-ignore-from.d.ts +3 -4
- package/dist/src/commands/patch/lint-ignore-from.js +3 -4
- package/dist/src/commands/patch/move-files-adopt.d.ts +33 -0
- package/dist/src/commands/patch/move-files-adopt.js +65 -0
- package/dist/src/commands/patch/move-files-create.js +41 -20
- package/dist/src/commands/patch/move-files-into.d.ts +18 -0
- package/dist/src/commands/patch/move-files-into.js +125 -60
- package/dist/src/commands/patch/move-files-retire.d.ts +39 -0
- package/dist/src/commands/patch/move-files-retire.js +91 -0
- package/dist/src/commands/patch/move-files.d.ts +3 -0
- package/dist/src/commands/patch/move-files.js +42 -12
- package/dist/src/commands/patch/move-size-tier.d.ts +27 -0
- package/dist/src/commands/patch/move-size-tier.js +66 -0
- package/dist/src/commands/patch/rename.js +1 -1
- package/dist/src/commands/patch/reorder-projection.d.ts +17 -0
- package/dist/src/commands/patch/reorder-projection.js +49 -0
- package/dist/src/commands/patch/reorder-shift.d.ts +30 -0
- package/dist/src/commands/patch/reorder-shift.js +307 -0
- package/dist/src/commands/patch/reorder.d.ts +9 -15
- package/dist/src/commands/patch/reorder.js +52 -50
- package/dist/src/commands/patch/split-plan.d.ts +31 -3
- package/dist/src/commands/patch/split-plan.js +36 -12
- package/dist/src/commands/patch/split-staged.d.ts +2 -2
- package/dist/src/commands/patch/split-staged.js +2 -2
- package/dist/src/commands/patch/split.js +18 -5
- package/dist/src/commands/patch/staged-dependency-entries.d.ts +9 -1
- package/dist/src/commands/patch/staged-dependency-entries.js +32 -4
- package/dist/src/commands/patch/staged-dependency-from.js +15 -9
- package/dist/src/commands/patch/staged-dependency-remove.d.ts +26 -4
- package/dist/src/commands/patch/staged-dependency-remove.js +60 -23
- package/dist/src/commands/patch/staged-dependency.js +35 -24
- package/dist/src/commands/patch/tier.js +2 -2
- package/dist/src/commands/patch-rename-map.d.ts +14 -3
- package/dist/src/commands/patch-rename-map.js +21 -3
- package/dist/src/commands/re-export-adjacent.d.ts +4 -5
- package/dist/src/commands/re-export-lint.d.ts +4 -3
- package/dist/src/commands/re-export-lint.js +4 -3
- package/dist/src/commands/re-export-options.js +8 -2
- package/dist/src/commands/re-export-preflight.js +1 -2
- package/dist/src/commands/re-export-purity-scope.d.ts +1 -1
- package/dist/src/commands/re-export-purity-scope.js +13 -7
- package/dist/src/commands/re-export-retired-declarations.d.ts +22 -0
- package/dist/src/commands/re-export-retired-declarations.js +113 -0
- package/dist/src/commands/re-export-single.js +2 -2
- package/dist/src/commands/re-export-stale-registrations.d.ts +27 -6
- package/dist/src/commands/re-export-stale-registrations.js +65 -21
- package/dist/src/commands/re-export.js +4 -0
- package/dist/src/commands/rebase/continue.js +3 -3
- package/dist/src/commands/register.js +5 -4
- package/dist/src/commands/run.js +11 -4
- package/dist/src/commands/status.js +1 -1
- package/dist/src/commands/test-build-furnace.d.ts +1 -2
- package/dist/src/commands/test-build-furnace.js +1 -2
- package/dist/src/commands/test-build-phase.d.ts +8 -0
- package/dist/src/commands/test-build-phase.js +42 -7
- package/dist/src/commands/test-control-restore.d.ts +40 -0
- package/dist/src/commands/test-control-restore.js +86 -0
- package/dist/src/commands/test-dispatch.js +68 -7
- package/dist/src/commands/test-multi-verdict.js +1 -0
- package/dist/src/commands/test-orchestrate.d.ts +6 -0
- package/dist/src/commands/test-orchestrate.js +32 -1
- package/dist/src/commands/test-perf-scope.d.ts +1 -1
- package/dist/src/commands/test-perf-scope.js +1 -1
- package/dist/src/commands/test-prelock.d.ts +18 -8
- package/dist/src/commands/test-prelock.js +42 -14
- package/dist/src/commands/test-register.js +4 -3
- package/dist/src/commands/test-results.d.ts +10 -1
- package/dist/src/commands/test-results.js +20 -3
- package/dist/src/commands/test-run.d.ts +6 -0
- package/dist/src/commands/test-run.js +23 -4
- package/dist/src/commands/test-shards.d.ts +7 -3
- package/dist/src/commands/test-shards.js +24 -7
- package/dist/src/commands/test-stale-gate.d.ts +6 -1
- package/dist/src/commands/test-stale-gate.js +40 -2
- package/dist/src/commands/test-verdict.d.ts +5 -3
- package/dist/src/commands/test.js +14 -2
- package/dist/src/commands/token.js +2 -2
- package/dist/src/commands/verify.js +1 -1
- package/dist/src/commands/watch.js +1 -1
- package/dist/src/commands/wire.js +1 -1
- package/dist/src/core/build-audit-resolve.d.ts +4 -5
- package/dist/src/core/build-audit-resolve.js +4 -5
- package/dist/src/core/build-audit-skip.d.ts +2 -2
- package/dist/src/core/build-audit.d.ts +3 -4
- package/dist/src/core/build-baseline-types.d.ts +24 -3
- package/dist/src/core/build-baseline-validate.d.ts +30 -1
- package/dist/src/core/build-baseline-validate.js +127 -35
- package/dist/src/core/build-baseline.d.ts +8 -2
- package/dist/src/core/build-baseline.js +25 -16
- package/dist/src/core/build-jar-escalation.d.ts +4 -4
- package/dist/src/core/build-jar-escalation.js +4 -4
- package/dist/src/core/build-prepare.js +4 -4
- package/dist/src/core/config-validate.js +4 -4
- package/dist/src/core/coverage-extend.d.ts +5 -3
- package/dist/src/core/coverage-extend.js +8 -3
- package/dist/src/core/coverage-records.d.ts +4 -5
- package/dist/src/core/coverage-records.js +4 -5
- package/dist/src/core/destructive.d.ts +2 -2
- package/dist/src/core/destructive.js +2 -2
- package/dist/src/core/discard-baseline.d.ts +2 -2
- package/dist/src/core/discard-baseline.js +3 -3
- package/dist/src/core/engine-session-lock.d.ts +1 -1
- package/dist/src/core/engine-session-lock.js +1 -1
- package/dist/src/core/file-lock-owner.d.ts +5 -5
- package/dist/src/core/file-lock-owner.js +5 -5
- package/dist/src/core/file-lock-queue.js +6 -8
- package/dist/src/core/file-lock-shared.d.ts +4 -4
- package/dist/src/core/file-lock-shared.js +4 -4
- package/dist/src/core/file-lock.js +7 -10
- package/dist/src/core/firefox.js +2 -2
- package/dist/src/core/furnace-apply-overwrite-warn.d.ts +1 -1
- package/dist/src/core/furnace-apply-overwrite-warn.js +1 -1
- package/dist/src/core/furnace-apply.js +3 -3
- package/dist/src/core/furnace-operation.js +5 -5
- package/dist/src/core/furnace-registration-jar-inplace.js +5 -6
- package/dist/src/core/furnace-registration-rename.d.ts +5 -5
- package/dist/src/core/furnace-registration-rename.js +5 -5
- package/dist/src/core/furnace-registration-validate.d.ts +2 -1
- package/dist/src/core/furnace-registration-validate.js +2 -1
- package/dist/src/core/furnace-registration.js +2 -2
- package/dist/src/core/git-base.js +4 -3
- package/dist/src/core/git-diff.js +1 -1
- package/dist/src/core/git.js +1 -1
- package/dist/src/core/harness-orphans.d.ts +6 -6
- package/dist/src/core/harness-orphans.js +6 -6
- package/dist/src/core/harness-port-reservation.d.ts +60 -0
- package/dist/src/core/harness-port-reservation.js +122 -0
- package/dist/src/core/history-log-file.d.ts +9 -9
- package/dist/src/core/history-log-file.js +15 -15
- package/dist/src/core/history-log.d.ts +3 -3
- package/dist/src/core/history-log.js +3 -3
- package/dist/src/core/launchable-build.d.ts +39 -0
- package/dist/src/core/launchable-build.js +85 -0
- package/dist/src/core/marionette-port.js +1 -3
- package/dist/src/core/moz-manifest-rules.js +3 -2
- package/dist/src/core/negative-control-compiled.d.ts +13 -3
- package/dist/src/core/negative-control-compiled.js +27 -5
- package/dist/src/core/negative-control-diff.js +31 -6
- package/dist/src/core/parser-fallback.d.ts +5 -5
- package/dist/src/core/parser-fallback.js +5 -5
- package/dist/src/core/patch-identifier-suggest.d.ts +6 -0
- package/dist/src/core/patch-identifier-suggest.js +11 -4
- package/dist/src/core/patch-lint-checkjs.d.ts +1 -1
- package/dist/src/core/patch-lint-checkjs.js +1 -1
- package/dist/src/core/patch-lint-css.js +2 -2
- package/dist/src/core/patch-lint-file-size.d.ts +5 -7
- package/dist/src/core/patch-lint-file-size.js +5 -7
- package/dist/src/core/patch-lint-ignore.d.ts +3 -3
- package/dist/src/core/patch-lint-ignore.js +3 -3
- package/dist/src/core/patch-lint-prettier.d.ts +4 -6
- package/dist/src/core/patch-lint-prettier.js +4 -6
- package/dist/src/core/patch-lint-projection.d.ts +1 -1
- package/dist/src/core/patch-lint-projection.js +1 -1
- package/dist/src/core/patch-lint-queue-types.d.ts +2 -2
- package/dist/src/core/patch-lint-queue-types.js +2 -2
- package/dist/src/core/patch-manifest-consistency.d.ts +2 -2
- package/dist/src/core/patch-manifest-consistency.js +2 -2
- package/dist/src/core/patch-manifest-io.d.ts +5 -1
- package/dist/src/core/patch-manifest-io.js +12 -10
- package/dist/src/core/patch-manifest-key-order.d.ts +3 -4
- package/dist/src/core/patch-manifest-key-order.js +3 -4
- package/dist/src/core/patch-parse.d.ts +3 -3
- package/dist/src/core/patch-parse.js +3 -3
- package/dist/src/core/patch-policy.d.ts +1 -1
- package/dist/src/core/patch-policy.js +1 -1
- package/dist/src/core/patch-source-metadata.d.ts +1 -1
- package/dist/src/core/patch-source-metadata.js +1 -1
- package/dist/src/core/patch-subset.d.ts +5 -3
- package/dist/src/core/patch-subset.js +8 -7
- package/dist/src/core/patch-transform.d.ts +2 -2
- package/dist/src/core/patch-transform.js +2 -2
- package/dist/src/core/rebase-session.js +4 -6
- package/dist/src/core/register-xpcshell-test.d.ts +28 -7
- package/dist/src/core/register-xpcshell-test.js +76 -27
- package/dist/src/core/run-log.d.ts +8 -8
- package/dist/src/core/run-log.js +8 -8
- package/dist/src/core/status-classify.d.ts +1 -2
- package/dist/src/core/status-classify.js +1 -2
- package/dist/src/core/test-harness-crash.d.ts +5 -3
- package/dist/src/core/test-harness-crash.js +13 -14
- package/dist/src/core/test-harness-infra.d.ts +32 -0
- package/dist/src/core/test-harness-infra.js +69 -0
- package/dist/src/core/test-harness-signature.d.ts +6 -0
- package/dist/src/core/test-stale-check.d.ts +38 -0
- package/dist/src/core/test-stale-check.js +82 -1
- package/dist/src/core/test-stall-triage.js +5 -7
- package/dist/src/core/token-category.js +2 -2
- package/dist/src/core/token-inventory.js +3 -3
- package/dist/src/core/token-manager.d.ts +2 -2
- package/dist/src/core/token-manager.js +5 -7
- package/dist/src/core/token-variant.d.ts +6 -6
- package/dist/src/core/token-variant.js +7 -7
- package/dist/src/core/typecheck-shim.js +1 -1
- package/dist/src/core/wire-destroy.d.ts +1 -1
- package/dist/src/core/wire-destroy.js +1 -1
- package/dist/src/core/wire-dom-fragment.js +1 -1
- package/dist/src/core/wire-init.d.ts +1 -1
- package/dist/src/core/wire-init.js +1 -1
- package/dist/src/core/wire-subscript.d.ts +1 -1
- package/dist/src/core/wire-subscript.js +1 -1
- package/dist/src/core/wire-utils.d.ts +2 -2
- package/dist/src/core/wire-utils.js +3 -3
- package/dist/src/errors/base.d.ts +1 -1
- package/dist/src/errors/base.js +1 -1
- package/dist/src/errors/rebase.d.ts +1 -1
- package/dist/src/errors/rebase.js +1 -1
- package/dist/src/types/commands/options.d.ts +38 -3
- package/dist/src/types/config.d.ts +5 -7
- package/dist/src/utils/options.js +2 -2
- package/dist/src/utils/process.js +2 -2
- package/dist/src/utils/regex.d.ts +2 -2
- package/dist/src/utils/regex.js +3 -3
- package/dist/src/utils/sleep.d.ts +5 -6
- package/dist/src/utils/sleep.js +5 -6
- package/dist/src/utils/validation.js +2 -2
- package/docs/configuration.md +28 -23
- package/docs/exit-codes.md +8 -11
- package/docs/furnace.md +3 -3
- package/docs/lifecycle-invariants.md +36 -45
- package/docs/machine-output.md +41 -24
- package/docs/patch-workflow.md +140 -29
- package/docs/run-logs.md +8 -9
- package/docs/testing.md +114 -48
- package/package.json +1 -1
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* `fireforge patch reorder --shift <n> --from <order> [--through <order>]`
|
|
4
|
+
* renumbers a run of patches up by `n` in one gap-checked transaction, so
|
|
5
|
+
* an insert has room.
|
|
6
|
+
*
|
|
7
|
+
* Opening room for four inserts after 103 meant moving 104 to 128 up by
|
|
8
|
+
* four. `patch rename --order` moves one patch, and under
|
|
9
|
+
* `patchPolicy.allowGaps: false` every single step refused on the gap it
|
|
10
|
+
* left, so the only way through was one `patch split --after` per insert.
|
|
11
|
+
* The shift moves the whole run at once (the retired and reserved slots
|
|
12
|
+
* inside it move with it), and under `allowGaps: false` records the orders
|
|
13
|
+
* it opens as `patches.json#reserved` slots, so the queue stays valid and
|
|
14
|
+
* the inserts that follow fill them.
|
|
15
|
+
*
|
|
16
|
+
* Same contract as `patch compact`: a projected policy check, a projected
|
|
17
|
+
* cross-patch lint (reporting only what the shift itself adds), a
|
|
18
|
+
* confirmation, and a commit under the patch-directory lock that refuses
|
|
19
|
+
* if the plan changed while it waited.
|
|
20
|
+
*/
|
|
21
|
+
import { loadConfig } from '../../core/config.js';
|
|
22
|
+
import { confirmDestructive } from '../../core/destructive.js';
|
|
23
|
+
import { appendHistoryBestEffort } from '../../core/history-log.js';
|
|
24
|
+
import { buildPatchQueueContext, formatPatchLintIssue, lintPatchQueue, } from '../../core/patch-lint.js';
|
|
25
|
+
import { computeProjectedLintRegressions } from '../../core/patch-lint-projection.js';
|
|
26
|
+
import { withPatchDirectoryLock } from '../../core/patch-lock.js';
|
|
27
|
+
import { loadPatchesManifest, loadPatchesManifestForWrite, renumberPatchesInManifest, savePatchesManifest, } from '../../core/patch-manifest.js';
|
|
28
|
+
import { formatPatchOrder } from '../../core/patch-parse.js';
|
|
29
|
+
import { applyRenameMapToManifest, enforcePatchPolicy } from '../../core/patch-policy.js';
|
|
30
|
+
import { GeneralError, InvalidArgumentError } from '../../errors/base.js';
|
|
31
|
+
import { toError } from '../../utils/errors.js';
|
|
32
|
+
import { info, intro, outro, warn } from '../../utils/logger.js';
|
|
33
|
+
import { resolveWaitLockSeconds } from '../../utils/options.js';
|
|
34
|
+
import { proceedAfterDecision } from '../destructive-decision.js';
|
|
35
|
+
import { getSortedRenameEntries, renameMapsEqual, writeRenameMapFile, } from '../patch-rename-map.js';
|
|
36
|
+
import { requirePatchQueue } from './patch-context.js';
|
|
37
|
+
import { projectReorder, rebuildFilenameForOrder } from './reorder-projection.js';
|
|
38
|
+
/** Owner label recorded on the reservations a shift opens. */
|
|
39
|
+
const SHIFT_RESERVATION_OWNER = 'patch reorder --shift';
|
|
40
|
+
/** What occupies an order: a patch, a retired slot or a reservation. */
|
|
41
|
+
function occupantAt(manifest, order) {
|
|
42
|
+
const patch = manifest.patches.find((entry) => entry.order === order);
|
|
43
|
+
if (patch !== undefined)
|
|
44
|
+
return patch.filename;
|
|
45
|
+
const retired = manifest.retired?.find((slot) => slot.order === order);
|
|
46
|
+
if (retired !== undefined)
|
|
47
|
+
return `the retired slot of ${retired.filename}`;
|
|
48
|
+
const reserved = manifest.reserved?.find((slot) => slot.order === order);
|
|
49
|
+
if (reserved !== undefined)
|
|
50
|
+
return `the reservation held by ${reserved.owner}`;
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
/** The category of whatever occupies `order`. */
|
|
54
|
+
function categoryAt(manifest, order) {
|
|
55
|
+
return (manifest.patches.find((entry) => entry.order === order)?.category ??
|
|
56
|
+
manifest.retired?.find((slot) => slot.order === order)?.category ??
|
|
57
|
+
manifest.reserved?.find((slot) => slot.order === order)?.category);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* The last order of the run that starts at `from`: consecutive occupied
|
|
61
|
+
* orders (patches and slots) of the first occupant's category.
|
|
62
|
+
*/
|
|
63
|
+
function defaultThrough(manifest, from) {
|
|
64
|
+
const category = categoryAt(manifest, from);
|
|
65
|
+
let through = from;
|
|
66
|
+
while (occupantAt(manifest, through + 1) !== undefined &&
|
|
67
|
+
categoryAt(manifest, through + 1) === category) {
|
|
68
|
+
through++;
|
|
69
|
+
}
|
|
70
|
+
return through;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Plans a shift against `manifest`, refusing a run with no patch, a
|
|
74
|
+
* `--through` before `--from`, and a shift that lands on an occupied order
|
|
75
|
+
* past the run.
|
|
76
|
+
*
|
|
77
|
+
* @param manifest - The queue
|
|
78
|
+
* @param config - Project configuration (`patchPolicy.allowGaps`)
|
|
79
|
+
* @param options - `shift`, `from` and optional `through`
|
|
80
|
+
*/
|
|
81
|
+
function planShift(manifest, config, options) {
|
|
82
|
+
const { shift, from } = options;
|
|
83
|
+
if (occupantAt(manifest, from) === undefined) {
|
|
84
|
+
throw new InvalidArgumentError(`No patch or slot holds order ${formatPatchOrder(from)}; --from names the first order of the run to shift.`, '--from');
|
|
85
|
+
}
|
|
86
|
+
const through = options.through ?? defaultThrough(manifest, from);
|
|
87
|
+
if (through < from) {
|
|
88
|
+
throw new InvalidArgumentError(`--through ${through} is before --from ${from}.`, '--through');
|
|
89
|
+
}
|
|
90
|
+
const inRun = (order) => order >= from && order <= through;
|
|
91
|
+
const members = manifest.patches.filter((patch) => inRun(patch.order));
|
|
92
|
+
if (members.length === 0) {
|
|
93
|
+
throw new InvalidArgumentError(`No patch sits between orders ${formatPatchOrder(from)} and ${formatPatchOrder(through)}; nothing to shift.`, '--from');
|
|
94
|
+
}
|
|
95
|
+
for (let order = through + 1; order <= through + shift; order++) {
|
|
96
|
+
const occupant = occupantAt(manifest, order);
|
|
97
|
+
if (occupant !== undefined) {
|
|
98
|
+
throw new InvalidArgumentError(`--shift ${shift} would move the run ${formatPatchOrder(from)}-${formatPatchOrder(through)} ` +
|
|
99
|
+
`onto order ${formatPatchOrder(order)}, which ${occupant} holds. Extend the run with ` +
|
|
100
|
+
'--through, or shift by fewer orders.', '--shift');
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
const renameMap = new Map();
|
|
104
|
+
for (const patch of members) {
|
|
105
|
+
const newOrder = patch.order + shift;
|
|
106
|
+
renameMap.set(patch.filename, {
|
|
107
|
+
newOrder,
|
|
108
|
+
newFilename: rebuildFilenameForOrder(patch, newOrder),
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
const moveSlot = (slot) => inRun(slot.order) ? { ...slot, order: slot.order + shift } : slot;
|
|
112
|
+
const retired = (manifest.retired ?? []).map(moveSlot);
|
|
113
|
+
const reserved = (manifest.reserved ?? []).map(moveSlot);
|
|
114
|
+
// Every order below the shifted run's new start is free after the shift
|
|
115
|
+
// (the collision check above saw to the ones past the old run), so under
|
|
116
|
+
// `allowGaps: false` each one is held until an insert fills it.
|
|
117
|
+
const opened = [];
|
|
118
|
+
const category = categoryAt(manifest, from);
|
|
119
|
+
if (config.patchPolicy?.allowGaps === false && category !== undefined) {
|
|
120
|
+
const reservedAt = new Date().toISOString();
|
|
121
|
+
for (let order = from; order < from + shift; order++) {
|
|
122
|
+
opened.push(order);
|
|
123
|
+
reserved.push({
|
|
124
|
+
order,
|
|
125
|
+
category,
|
|
126
|
+
owner: SHIFT_RESERVATION_OWNER,
|
|
127
|
+
reservedAt,
|
|
128
|
+
reason: `opened by patch reorder --shift ${shift} --from ${from}`,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
const byOrder = (a, b) => a.order - b.order;
|
|
133
|
+
return {
|
|
134
|
+
shift,
|
|
135
|
+
from,
|
|
136
|
+
through,
|
|
137
|
+
renameMap,
|
|
138
|
+
retired: retired.sort(byOrder),
|
|
139
|
+
reserved: reserved.sort(byOrder),
|
|
140
|
+
opened,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
/** The manifest a shift leaves: renamed rows with the plan's slot lists. */
|
|
144
|
+
function projectShiftManifest(manifest, plan) {
|
|
145
|
+
const renamed = applyRenameMapToManifest(manifest, plan.renameMap);
|
|
146
|
+
return {
|
|
147
|
+
version: renamed.version,
|
|
148
|
+
patches: renamed.patches,
|
|
149
|
+
...(plan.retired.length > 0 ? { retired: plan.retired } : {}),
|
|
150
|
+
...(plan.reserved.length > 0 ? { reserved: plan.reserved } : {}),
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
/** True when two plans make the same renames and leave the same slot orders. */
|
|
154
|
+
function shiftPlansEqual(left, right) {
|
|
155
|
+
const orders = (slots) => slots.map((slot) => slot.order).join(',');
|
|
156
|
+
return (renameMapsEqual(left.renameMap, right.renameMap) &&
|
|
157
|
+
orders(left.retired) === orders(right.retired) &&
|
|
158
|
+
orders(left.reserved) === orders(right.reserved));
|
|
159
|
+
}
|
|
160
|
+
/** Cross-patch lint errors the shift itself introduces. */
|
|
161
|
+
async function projectShiftLint(patchesDir, config, renameMap) {
|
|
162
|
+
const baseCtx = await buildPatchQueueContext(patchesDir, config);
|
|
163
|
+
const isError = (issue) => issue.severity === 'error';
|
|
164
|
+
const regressions = computeProjectedLintRegressions(lintPatchQueue(baseCtx).filter(isError),
|
|
165
|
+
// The projection keeps the baseline's queue-wide lint settings, so the
|
|
166
|
+
// two sides compare symmetric rule sets.
|
|
167
|
+
lintPatchQueue({ ...baseCtx, ...projectReorder(baseCtx, renameMap) }).filter(isError));
|
|
168
|
+
return regressions.length === 0
|
|
169
|
+
? null
|
|
170
|
+
: {
|
|
171
|
+
reason: `shift would introduce ${regressions.length} cross-patch lint error(s)`,
|
|
172
|
+
details: regressions.map(formatPatchLintIssue),
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
/** Lines describing a plan, for the dry run and the confirmation prompt. */
|
|
176
|
+
function describeShift(manifest, plan) {
|
|
177
|
+
const lines = [
|
|
178
|
+
`shift orders ${formatPatchOrder(plan.from)}-${formatPatchOrder(plan.through)} up by ${plan.shift}`,
|
|
179
|
+
`${plan.renameMap.size} patch(es) would be renamed:`,
|
|
180
|
+
...getSortedRenameEntries(plan.renameMap).map(([from, entry]) => ` ${from} → ${entry.newFilename} (order ${entry.newOrder})`),
|
|
181
|
+
];
|
|
182
|
+
const inRun = (order) => order >= plan.from && order <= plan.through;
|
|
183
|
+
for (const slot of manifest.retired ?? []) {
|
|
184
|
+
if (inRun(slot.order)) {
|
|
185
|
+
lines.push(`retired slot ${formatPatchOrder(slot.order)} (${slot.filename}) moves to ${formatPatchOrder(slot.order + plan.shift)}`);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
for (const slot of manifest.reserved ?? []) {
|
|
189
|
+
if (inRun(slot.order)) {
|
|
190
|
+
lines.push(`reservation ${formatPatchOrder(slot.order)} (held by ${slot.owner}) moves to ` +
|
|
191
|
+
`${formatPatchOrder(slot.order + plan.shift)}; tell its holder the new order`);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
if (plan.opened.length > 0) {
|
|
195
|
+
lines.push(`opened orders ${plan.opened.map((order) => formatPatchOrder(order)).join(', ')} are ` +
|
|
196
|
+
'reserved (patchPolicy.allowGaps: false); "fireforge export --order <n>" or ' +
|
|
197
|
+
'"patch split --order <n>" fills them, "patch reserve <n> --release" drops one');
|
|
198
|
+
}
|
|
199
|
+
return lines;
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Commits a confirmed shift under the patch-directory lock: re-plans
|
|
203
|
+
* against the current manifest (refusing a changed plan), renumbers, then
|
|
204
|
+
* writes the plan's slot lists. A failed slot write inverts the renumber.
|
|
205
|
+
*/
|
|
206
|
+
async function commitShift(patchesDir, config, plan, options) {
|
|
207
|
+
await withPatchDirectoryLock(patchesDir, async () => {
|
|
208
|
+
const current = await loadPatchesManifest(patchesDir);
|
|
209
|
+
if (!current)
|
|
210
|
+
throw new GeneralError('Manifest disappeared while waiting for lock.');
|
|
211
|
+
const currentPlan = planShift(current, config, {
|
|
212
|
+
shift: plan.shift,
|
|
213
|
+
from: plan.from,
|
|
214
|
+
through: plan.through,
|
|
215
|
+
});
|
|
216
|
+
if (!shiftPlansEqual(plan, currentPlan)) {
|
|
217
|
+
throw new GeneralError('Patch queue changed while waiting for confirmation. Re-run reorder --shift to recompute the plan.');
|
|
218
|
+
}
|
|
219
|
+
enforcePatchPolicy({
|
|
220
|
+
config,
|
|
221
|
+
manifest: projectShiftManifest(current, plan),
|
|
222
|
+
command: 'patch reorder --shift',
|
|
223
|
+
forceUnsafe: options.forceUnsafe === true,
|
|
224
|
+
});
|
|
225
|
+
await renumberPatchesInManifest(patchesDir, plan.renameMap);
|
|
226
|
+
try {
|
|
227
|
+
const renumbered = await loadPatchesManifestForWrite(patchesDir);
|
|
228
|
+
if (!renumbered)
|
|
229
|
+
throw new GeneralError('Manifest disappeared during reorder --shift.');
|
|
230
|
+
delete renumbered.retired;
|
|
231
|
+
delete renumbered.reserved;
|
|
232
|
+
await savePatchesManifest(patchesDir, {
|
|
233
|
+
...renumbered,
|
|
234
|
+
...(plan.retired.length > 0 ? { retired: plan.retired } : {}),
|
|
235
|
+
...(plan.reserved.length > 0 ? { reserved: plan.reserved } : {}),
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
catch (error) {
|
|
239
|
+
const inverse = new Map([...plan.renameMap].map(([oldFilename, entry]) => [
|
|
240
|
+
entry.newFilename,
|
|
241
|
+
{ newFilename: oldFilename, newOrder: entry.newOrder - plan.shift },
|
|
242
|
+
]));
|
|
243
|
+
try {
|
|
244
|
+
await renumberPatchesInManifest(patchesDir, inverse);
|
|
245
|
+
await savePatchesManifest(patchesDir, current);
|
|
246
|
+
}
|
|
247
|
+
catch (rollbackError) {
|
|
248
|
+
warn(`Rollback warning: could not invert the shift: ${toError(rollbackError).message}`);
|
|
249
|
+
}
|
|
250
|
+
throw error;
|
|
251
|
+
}
|
|
252
|
+
await appendHistoryBestEffort(patchesDir, {
|
|
253
|
+
operation: 'patch-reorder-shift',
|
|
254
|
+
args: {
|
|
255
|
+
shift: plan.shift,
|
|
256
|
+
from: plan.from,
|
|
257
|
+
through: plan.through,
|
|
258
|
+
renames: getSortedRenameEntries(plan.renameMap).map(([from, entry]) => ({
|
|
259
|
+
from,
|
|
260
|
+
to: entry.newFilename,
|
|
261
|
+
order: entry.newOrder,
|
|
262
|
+
})),
|
|
263
|
+
...(plan.opened.length > 0 ? { reservedOpened: plan.opened } : {}),
|
|
264
|
+
},
|
|
265
|
+
...(options.yes === true ? { yes: true } : {}),
|
|
266
|
+
...(options.forceUnsafe === true ? { unsafeOverride: true } : {}),
|
|
267
|
+
result: 'ok',
|
|
268
|
+
}, 'patch reorder --shift committed');
|
|
269
|
+
}, { waitLockSeconds: resolveWaitLockSeconds(options.waitLock), command: 'patch reorder' });
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Runs `patch reorder --shift <n> --from <order> [--through <order>]`.
|
|
273
|
+
*
|
|
274
|
+
* @param projectRoot - Project root directory
|
|
275
|
+
* @param options - Reorder options with `shift` and `from` set
|
|
276
|
+
*/
|
|
277
|
+
export async function patchReorderShiftCommand(projectRoot, options) {
|
|
278
|
+
intro(options.dryRun ? 'FireForge patch reorder --shift (dry run)' : 'FireForge patch reorder --shift');
|
|
279
|
+
const config = await loadConfig(projectRoot);
|
|
280
|
+
const { paths, manifest } = await requirePatchQueue(projectRoot);
|
|
281
|
+
const plan = planShift(manifest, config, options);
|
|
282
|
+
enforcePatchPolicy({
|
|
283
|
+
config,
|
|
284
|
+
manifest: projectShiftManifest(manifest, plan),
|
|
285
|
+
command: 'patch reorder --shift',
|
|
286
|
+
forceUnsafe: options.forceUnsafe === true,
|
|
287
|
+
});
|
|
288
|
+
const conflicts = await projectShiftLint(paths.patches, config, plan.renameMap);
|
|
289
|
+
const decision = await confirmDestructive({
|
|
290
|
+
operation: 'patch-reorder-shift',
|
|
291
|
+
title: `Shift ${plan.renameMap.size} patch(es) from order ${formatPatchOrder(plan.from)} up by ${plan.shift}`,
|
|
292
|
+
summary: describeShift(manifest, plan),
|
|
293
|
+
yes: options.yes === true,
|
|
294
|
+
dryRun: options.dryRun === true,
|
|
295
|
+
unsafeOverride: options.forceUnsafe === true,
|
|
296
|
+
conflicts,
|
|
297
|
+
});
|
|
298
|
+
if (!proceedAfterDecision(decision, 'Reorder cancelled'))
|
|
299
|
+
return;
|
|
300
|
+
await commitShift(paths.patches, config, plan, options);
|
|
301
|
+
if (options.mapOut !== undefined) {
|
|
302
|
+
const written = await writeRenameMapFile(options.mapOut, plan.renameMap);
|
|
303
|
+
info(`Wrote the old-to-new filename map (${plan.renameMap.size} rename(s)) to ${written}`);
|
|
304
|
+
}
|
|
305
|
+
info(`Shifted ${plan.renameMap.size} patch(es) up by ${plan.shift}.`);
|
|
306
|
+
outro('Reorder complete');
|
|
307
|
+
}
|
|
@@ -6,31 +6,25 @@
|
|
|
6
6
|
* renumbered to make room. Pre-flights the projected order through
|
|
7
7
|
* `lintPatchQueue` so reorders that would introduce a forward-import fail
|
|
8
8
|
* before any bytes move.
|
|
9
|
+
*
|
|
10
|
+
* `fireforge patch reorder --shift <n> --from <order> [--through <order>]`
|
|
11
|
+
* renumbers a whole run instead (see reorder-shift.ts). Every mode takes
|
|
12
|
+
* `--map-out <file>` for the committed old-to-new filename map.
|
|
9
13
|
*/
|
|
10
14
|
import { Command } from 'commander';
|
|
11
|
-
import { type PatchQueueContext } from '../../core/patch-lint.js';
|
|
12
|
-
import { type PatchRenameEntry } from '../../core/patch-manifest.js';
|
|
13
15
|
import type { CommandContext } from '../../types/cli.js';
|
|
14
|
-
import type {
|
|
15
|
-
/** Builds a new patch filename by replacing the numeric prefix with `newOrder`. */
|
|
16
|
-
export declare function rebuildFilenameForOrder(existing: PatchMetadata, newOrder: number): string;
|
|
17
|
-
/**
|
|
18
|
-
* Applies a rename map to a {@link PatchQueueContext} so cross-patch lint
|
|
19
|
-
* can run against the projected state without touching disk. Exported for
|
|
20
|
-
* `patch rename --order`, which projects the same single-row move shape.
|
|
21
|
-
*/
|
|
22
|
-
export declare function projectReorder(base: PatchQueueContext, renameMap: Map<string, PatchRenameEntry>): PatchQueueContext;
|
|
16
|
+
import type { PatchReorderOptions } from '../../types/commands/index.js';
|
|
23
17
|
/**
|
|
24
18
|
* Runs the `patch reorder` command: computes a rename map moving the
|
|
25
19
|
* target patch to the requested slot, projects the new order through
|
|
26
20
|
* cross-patch lint, confirms, and then renames under the patch directory
|
|
27
|
-
* lock.
|
|
21
|
+
* lock. With `--shift` it renumbers a run instead (see reorder-shift.ts).
|
|
28
22
|
*
|
|
29
23
|
* @param projectRoot - Project root directory
|
|
30
|
-
* @param identifier - Patch filename or ordinal number to move
|
|
31
|
-
* @param options - Command options (mutually exclusive --to/--before/--after)
|
|
24
|
+
* @param identifier - Patch filename or ordinal number to move (none with `--shift`)
|
|
25
|
+
* @param options - Command options (mutually exclusive --to/--before/--after/--shift)
|
|
32
26
|
*/
|
|
33
|
-
export declare function patchReorderCommand(projectRoot: string, identifier: string, options?: PatchReorderOptions): Promise<void>;
|
|
27
|
+
export declare function patchReorderCommand(projectRoot: string, identifier: string | undefined, options?: PatchReorderOptions): Promise<void>;
|
|
34
28
|
/**
|
|
35
29
|
* Registers the `patch reorder` subcommand on the `patch` parent.
|
|
36
30
|
*
|
|
@@ -7,6 +7,10 @@
|
|
|
7
7
|
* renumbered to make room. Pre-flights the projected order through
|
|
8
8
|
* `lintPatchQueue` so reorders that would introduce a forward-import fail
|
|
9
9
|
* before any bytes move.
|
|
10
|
+
*
|
|
11
|
+
* `fireforge patch reorder --shift <n> --from <order> [--through <order>]`
|
|
12
|
+
* renumbers a whole run instead (see reorder-shift.ts). Every mode takes
|
|
13
|
+
* `--map-out <file>` for the committed old-to-new filename map.
|
|
10
14
|
*/
|
|
11
15
|
import { Command, Option } from 'commander';
|
|
12
16
|
import { loadConfig } from '../../core/config.js';
|
|
@@ -14,27 +18,17 @@ import { confirmDestructive, } from '../../core/destructive.js';
|
|
|
14
18
|
import { appendHistoryBestEffort } from '../../core/history-log.js';
|
|
15
19
|
import { buildPatchQueueContext, formatPatchLintIssue, lintPatchQueue, } from '../../core/patch-lint.js';
|
|
16
20
|
import { withPatchDirectoryLock } from '../../core/patch-lock.js';
|
|
17
|
-
import { loadPatchesManifest, renumberPatchesInManifest, resolvePatchIdentifier,
|
|
21
|
+
import { loadPatchesManifest, renumberPatchesInManifest, resolvePatchIdentifier, } from '../../core/patch-manifest.js';
|
|
18
22
|
import { applyRenameMapToManifest, enforcePatchPolicy } from '../../core/patch-policy.js';
|
|
19
23
|
import { GeneralError, InvalidArgumentError } from '../../errors/base.js';
|
|
20
24
|
import { info, intro, outro } from '../../utils/logger.js';
|
|
21
25
|
import { addWaitLockOption, commanderArgParser, pickDefined, resolveWaitLockSeconds, } from '../../utils/options.js';
|
|
22
26
|
import { parsePositiveIntegerFlag } from '../../utils/validation.js';
|
|
23
27
|
import { proceedAfterDecision } from '../destructive-decision.js';
|
|
24
|
-
import { getSortedRenameEntries, renameMapsEqual } from '../patch-rename-map.js';
|
|
28
|
+
import { getSortedRenameEntries, renameMapsEqual, writeRenameMapFile, } from '../patch-rename-map.js';
|
|
25
29
|
import { requirePatchQueue, requirePatchTarget } from './patch-context.js';
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
return String(value).padStart(width, '0');
|
|
29
|
-
}
|
|
30
|
-
/** Builds a new patch filename by replacing the numeric prefix with `newOrder`. */
|
|
31
|
-
export function rebuildFilenameForOrder(existing, newOrder) {
|
|
32
|
-
const currentPrefixMatch = /^(\d+)-/.exec(existing.filename);
|
|
33
|
-
const currentPrefix = currentPrefixMatch?.[1] ?? '001';
|
|
34
|
-
const width = Math.max(3, currentPrefix.length, String(newOrder).length);
|
|
35
|
-
const rest = existing.filename.replace(/^\d+-/, '');
|
|
36
|
-
return `${padOrder(newOrder, width)}-${rest}`;
|
|
37
|
-
}
|
|
30
|
+
import { projectReorder, rebuildFilenameForOrder } from './reorder-projection.js';
|
|
31
|
+
import { patchReorderShiftCommand } from './reorder-shift.js';
|
|
38
32
|
/**
|
|
39
33
|
* Computes a rename map that moves `target` to `destinationOrder` with a
|
|
40
34
|
* minimal shift: only the contiguous run of patches whose current order
|
|
@@ -100,35 +94,6 @@ function computeRenameMap(manifestPatches, target, destinationOrder) {
|
|
|
100
94
|
});
|
|
101
95
|
return renames;
|
|
102
96
|
}
|
|
103
|
-
/**
|
|
104
|
-
* Applies a rename map to a {@link PatchQueueContext} so cross-patch lint
|
|
105
|
-
* can run against the projected state without touching disk. Exported for
|
|
106
|
-
* `patch rename --order`, which projects the same single-row move shape.
|
|
107
|
-
*/
|
|
108
|
-
export function projectReorder(base, renameMap) {
|
|
109
|
-
const ownerLookup = (oldFilename) => renameMap.get(oldFilename)?.newFilename;
|
|
110
|
-
const projectedEntries = base.entries.map((entry) => {
|
|
111
|
-
// Project staged-dependency owner references through the rename map on
|
|
112
|
-
// every entry. Owners point at other patches' filenames, so a
|
|
113
|
-
// projection that skips non-renamed entries would lint against stale
|
|
114
|
-
// owners and report false forward-import regressions.
|
|
115
|
-
const metadata = entry.metadata
|
|
116
|
-
? rewriteStagedDependencyOwners(entry.metadata, ownerLookup)
|
|
117
|
-
: entry.metadata;
|
|
118
|
-
const rename = renameMap.get(entry.filename);
|
|
119
|
-
if (!rename) {
|
|
120
|
-
return metadata === entry.metadata ? entry : { ...entry, metadata };
|
|
121
|
-
}
|
|
122
|
-
return {
|
|
123
|
-
...entry,
|
|
124
|
-
metadata,
|
|
125
|
-
filename: rename.newFilename,
|
|
126
|
-
order: rename.newOrder,
|
|
127
|
-
};
|
|
128
|
-
});
|
|
129
|
-
projectedEntries.sort((a, b) => a.order - b.order || a.filename.localeCompare(b.filename));
|
|
130
|
-
return { entries: projectedEntries };
|
|
131
|
-
}
|
|
132
97
|
/**
|
|
133
98
|
* Resolves `--to <N>`, `--before <anchor>`, and `--after <anchor>` into a
|
|
134
99
|
* concrete destination order and, for the anchor variants, the anchor
|
|
@@ -186,7 +151,7 @@ function resolveDestination(target, manifestPatches, options) {
|
|
|
186
151
|
*/
|
|
187
152
|
async function commitReorderPlan(commitOptions) {
|
|
188
153
|
const { patchesDir, target, renameMap, anchorFilename, options, config, buildHistoryEntry } = commitOptions;
|
|
189
|
-
|
|
154
|
+
return withPatchDirectoryLock(patchesDir, async () => {
|
|
190
155
|
const currentManifest = await loadPatchesManifest(patchesDir);
|
|
191
156
|
if (!currentManifest || currentManifest.patches.length === 0) {
|
|
192
157
|
throw new GeneralError('Patch queue changed while waiting for confirmation. Re-run reorder.');
|
|
@@ -239,19 +204,45 @@ async function commitReorderPlan(commitOptions) {
|
|
|
239
204
|
// succeeded and is not reversible, so surfacing it as a command
|
|
240
205
|
// failure would mislead.
|
|
241
206
|
await appendHistoryBestEffort(patchesDir, buildHistoryEntry(currentRenameMap), `patch reorder committed`);
|
|
207
|
+
return currentRenameMap;
|
|
242
208
|
}, { waitLockSeconds: resolveWaitLockSeconds(options.waitLock), command: 'patch reorder' });
|
|
243
209
|
}
|
|
210
|
+
/**
|
|
211
|
+
* Validates the `--shift` flag set and runs the shift, or returns false
|
|
212
|
+
* when no shift flag was given.
|
|
213
|
+
*/
|
|
214
|
+
async function runShiftMode(projectRoot, identifier, options) {
|
|
215
|
+
const { shift, from } = options;
|
|
216
|
+
if (shift === undefined && from === undefined && options.through === undefined)
|
|
217
|
+
return false;
|
|
218
|
+
if (shift === undefined || from === undefined) {
|
|
219
|
+
throw new InvalidArgumentError('--shift <n> and --from <order> go together (--through <order> is optional).', shift === undefined ? '--shift' : '--from');
|
|
220
|
+
}
|
|
221
|
+
if (identifier !== undefined ||
|
|
222
|
+
options.to !== undefined ||
|
|
223
|
+
options.before !== undefined ||
|
|
224
|
+
options.after !== undefined) {
|
|
225
|
+
throw new InvalidArgumentError('--shift renumbers a run of orders and takes no patch name, --to, --before or --after.', '--shift');
|
|
226
|
+
}
|
|
227
|
+
await patchReorderShiftCommand(projectRoot, { ...options, shift, from });
|
|
228
|
+
return true;
|
|
229
|
+
}
|
|
244
230
|
/**
|
|
245
231
|
* Runs the `patch reorder` command: computes a rename map moving the
|
|
246
232
|
* target patch to the requested slot, projects the new order through
|
|
247
233
|
* cross-patch lint, confirms, and then renames under the patch directory
|
|
248
|
-
* lock.
|
|
234
|
+
* lock. With `--shift` it renumbers a run instead (see reorder-shift.ts).
|
|
249
235
|
*
|
|
250
236
|
* @param projectRoot - Project root directory
|
|
251
|
-
* @param identifier - Patch filename or ordinal number to move
|
|
252
|
-
* @param options - Command options (mutually exclusive --to/--before/--after)
|
|
237
|
+
* @param identifier - Patch filename or ordinal number to move (none with `--shift`)
|
|
238
|
+
* @param options - Command options (mutually exclusive --to/--before/--after/--shift)
|
|
253
239
|
*/
|
|
254
240
|
export async function patchReorderCommand(projectRoot, identifier, options = {}) {
|
|
241
|
+
if (await runShiftMode(projectRoot, identifier, options))
|
|
242
|
+
return;
|
|
243
|
+
if (identifier === undefined) {
|
|
244
|
+
throw new InvalidArgumentError('Name the patch to move, or renumber a run with --shift <n> --from <order>.', 'patch reorder');
|
|
245
|
+
}
|
|
255
246
|
intro(options.dryRun ? 'FireForge patch reorder (dry run)' : 'FireForge patch reorder');
|
|
256
247
|
const specifiedTargets = [
|
|
257
248
|
options.to !== undefined,
|
|
@@ -338,7 +329,7 @@ export async function patchReorderCommand(projectRoot, identifier, options = {})
|
|
|
338
329
|
result: 'ok',
|
|
339
330
|
};
|
|
340
331
|
};
|
|
341
|
-
await commitReorderPlan({
|
|
332
|
+
const committed = await commitReorderPlan({
|
|
342
333
|
patchesDir: paths.patches,
|
|
343
334
|
target,
|
|
344
335
|
renameMap,
|
|
@@ -347,6 +338,10 @@ export async function patchReorderCommand(projectRoot, identifier, options = {})
|
|
|
347
338
|
config,
|
|
348
339
|
buildHistoryEntry,
|
|
349
340
|
});
|
|
341
|
+
if (options.mapOut !== undefined) {
|
|
342
|
+
const written = await writeRenameMapFile(options.mapOut, committed);
|
|
343
|
+
info(`Wrote the old-to-new filename map (${committed.size} rename(s)) to ${written}`);
|
|
344
|
+
}
|
|
350
345
|
info(`Reordered ${renameMap.size} patch(es).`);
|
|
351
346
|
outro('Reorder complete');
|
|
352
347
|
}
|
|
@@ -359,11 +354,18 @@ export async function patchReorderCommand(projectRoot, identifier, options = {})
|
|
|
359
354
|
export function registerPatchReorder(parent, context) {
|
|
360
355
|
const { getProjectRoot, withErrorHandling } = context;
|
|
361
356
|
const command = parent
|
|
362
|
-
.command('reorder
|
|
363
|
-
.description('Move a patch to a different position in the queue
|
|
357
|
+
.command('reorder [name]')
|
|
358
|
+
.description('Move a patch to a different position in the queue, or shift a run of patches up with ' +
|
|
359
|
+
'--shift <n> --from <order> (destructive)')
|
|
364
360
|
.addOption(new Option('--to <order>', 'Destination ordinal').argParser(commanderArgParser((v) => parsePositiveIntegerFlag('--to', v))))
|
|
365
361
|
.option('--before <anchor>', 'Place the patch immediately before <anchor>')
|
|
366
362
|
.option('--after <anchor>', 'Place the patch immediately after <anchor>')
|
|
363
|
+
.addOption(new Option('--shift <n>', 'Renumber the run starting at --from up by <n> orders (no patch name); under ' +
|
|
364
|
+
'patchPolicy.allowGaps: false the opened orders are reserved').argParser(commanderArgParser((v) => parsePositiveIntegerFlag('--shift', v))))
|
|
365
|
+
.addOption(new Option('--from <order>', 'First order of the run --shift moves').argParser(commanderArgParser((v) => parsePositiveIntegerFlag('--from', v))))
|
|
366
|
+
.addOption(new Option('--through <order>', 'Last order of the run --shift moves (default: the end of the run of consecutive ' +
|
|
367
|
+
"orders of --from's category)").argParser(commanderArgParser((v) => parsePositiveIntegerFlag('--through', v))))
|
|
368
|
+
.option('--map-out <file>', 'Write the committed old-to-new filename map as JSON')
|
|
367
369
|
.option('--dry-run', 'Show what would happen without writing')
|
|
368
370
|
.option('-y, --yes', 'Skip confirmation prompt (required for non-TTY)')
|
|
369
371
|
.option('--force-unsafe', 'Bypass the refusal when the projected order introduces a lint error');
|
|
@@ -45,7 +45,33 @@ export interface SplitPlan {
|
|
|
45
45
|
* unused. Populated by {@link runProjectedSplitLint}.
|
|
46
46
|
*/
|
|
47
47
|
stagedMaintenance: SplitStagedMaintenance;
|
|
48
|
+
/**
|
|
49
|
+
* The command running the plan, named in the `reason` of every
|
|
50
|
+
* declaration it writes. Absent: `patch split`.
|
|
51
|
+
*/
|
|
52
|
+
operation?: MoveOperation;
|
|
53
|
+
/**
|
|
54
|
+
* New engine files no patch owned that `move-files --scan-file` adopts
|
|
55
|
+
* into the new patch in the same transaction. Already part of
|
|
56
|
+
* `movedFiles`; listed apart only for the summary and the commit's
|
|
57
|
+
* still-unowned check.
|
|
58
|
+
*/
|
|
59
|
+
adoptedFiles?: string[];
|
|
48
60
|
}
|
|
61
|
+
/** The commands that move files between patches and declare the edges they open. */
|
|
62
|
+
export type MoveOperation = 'patch split' | 'patch move-files';
|
|
63
|
+
/**
|
|
64
|
+
* The `reason` a move records on a forward declaration it writes, so the
|
|
65
|
+
* declaration explains itself in `patches.json` and `staged-dependency
|
|
66
|
+
* --add --from` accepts it without a hand-written `--reason`.
|
|
67
|
+
*
|
|
68
|
+
* @param source - Patch the files left
|
|
69
|
+
* @param destination - Patch the files moved into
|
|
70
|
+
* @param operation - The command that moved them
|
|
71
|
+
*/
|
|
72
|
+
export declare function moveDeclarationReason(source: string, destination: string, operation: MoveOperation): string;
|
|
73
|
+
/** The {@link moveDeclarationReason} of a split plan. */
|
|
74
|
+
export declare function splitDeclarationReason(plan: SplitPlan): string;
|
|
49
75
|
/**
|
|
50
76
|
* Refuses the split unless the source patch currently owns every requested
|
|
51
77
|
* file. Splitting out a file the source does not own would produce a new patch
|
|
@@ -90,8 +116,10 @@ export declare function buildNewPatchMetadata(plan: SplitPlan, config: FireForge
|
|
|
90
116
|
export declare function buildSplitSummary(plan: SplitPlan): string[];
|
|
91
117
|
/**
|
|
92
118
|
* Maps a declaration the split projection proposes back onto the queue as
|
|
93
|
-
* it stands: renumbered patches take their current names
|
|
94
|
-
*
|
|
95
|
-
*
|
|
119
|
+
* it stands: renumbered patches take their current names. An owner that is
|
|
120
|
+
* the patch the split would create keeps the planned filename, so the
|
|
121
|
+
* report declares the owner the rerun creates (`staged-dependency --add`
|
|
122
|
+
* accepts an owner the queue does not hold yet, and the move then lands
|
|
123
|
+
* with the declaration already right).
|
|
96
124
|
*/
|
|
97
125
|
export declare function splitDeclarationRenamer(plan: SplitPlan): DeclarationRenamer;
|