@hominis/fireforge 0.51.0 → 0.53.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 +46 -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.js +13 -9
- 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-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/status-output.js +5 -1
- 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 +10 -0
- package/dist/src/commands/test-build-phase.js +51 -16
- 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 +83 -13
- package/dist/src/commands/test-multi-verdict.js +1 -0
- package/dist/src/commands/test-orchestrate.d.ts +11 -0
- package/dist/src/commands/test-orchestrate.js +33 -2
- 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 +7 -8
- package/dist/src/commands/test-prelock.js +15 -12
- package/dist/src/commands/test-register.js +2 -2
- 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 +47 -2
- package/dist/src/commands/test-verdict.d.ts +5 -3
- package/dist/src/commands/test.js +14 -1
- 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 +37 -3
- package/dist/src/core/build-baseline-validate.d.ts +30 -1
- package/dist/src/core/build-baseline-validate.js +134 -33
- package/dist/src/core/build-baseline.d.ts +8 -2
- package/dist/src/core/build-baseline.js +32 -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-config-validate-exceptions.d.ts +4 -3
- package/dist/src/core/furnace-config-validate-exceptions.js +26 -6
- 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/furnace-validate-accessibility.d.ts +1 -1
- package/dist/src/core/furnace-validate-accessibility.js +22 -11
- package/dist/src/core/furnace-validate-exceptions.d.ts +2 -1
- package/dist/src/core/furnace-validate-exceptions.js +31 -13
- 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 +22 -3
- package/dist/src/core/negative-control-compiled.js +41 -5
- 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-run-completeness.d.ts +12 -5
- package/dist/src/core/test-run-completeness.js +15 -8
- package/dist/src/core/test-stale-check.d.ts +48 -1
- package/dist/src/core/test-stale-check.js +97 -2
- 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.d.ts +2 -1
- package/dist/src/core/typecheck-shim.js +29 -5
- 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 +26 -3
- package/dist/src/types/config.d.ts +5 -7
- package/dist/src/types/furnace.d.ts +9 -0
- 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 +40 -26
- package/docs/exit-codes.md +8 -11
- package/docs/furnace.md +21 -3
- package/docs/lifecycle-invariants.md +36 -45
- package/docs/machine-output.md +43 -22
- package/docs/patch-workflow.md +129 -24
- package/docs/run-logs.md +8 -9
- package/docs/testing.md +118 -44
- package/package.json +1 -1
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* `patch move-files --scan-file <path>`: adopt new engine files into the
|
|
4
|
+
* move's destination in the same transaction.
|
|
5
|
+
*
|
|
6
|
+
* Splitting a widget across patches moved existing files and adopted the
|
|
7
|
+
* seam modules that appeared beside them. Adopting first (`re-export
|
|
8
|
+
* --scan --scan-file`) grew the source past its own `large-patch-lines`
|
|
9
|
+
* ceiling, which refused although the move that followed would have
|
|
10
|
+
* shrunk it again. Adopting straight into the destination of the move
|
|
11
|
+
* means no patch ever carries the intermediate body: both sides are linted
|
|
12
|
+
* on what the transaction leaves behind.
|
|
13
|
+
*/
|
|
14
|
+
import { join } from 'node:path';
|
|
15
|
+
import { InvalidArgumentError } from '../../errors/base.js';
|
|
16
|
+
import { pathExists } from '../../utils/fs.js';
|
|
17
|
+
import { normalizeEngineRelativeInput } from '../re-export-scan.js';
|
|
18
|
+
/**
|
|
19
|
+
* Validates `--scan-file` paths: each must be engine-relative, exist in
|
|
20
|
+
* `engine/`, be owned by no patch and not be one of the moved files.
|
|
21
|
+
* Returns the sorted, de-duplicated list.
|
|
22
|
+
*
|
|
23
|
+
* @param rawFiles - The `--scan-file` values
|
|
24
|
+
* @param patches - Manifest rows
|
|
25
|
+
* @param enginePath - Engine directory
|
|
26
|
+
* @param movedFiles - The `--file` paths of the same move
|
|
27
|
+
*/
|
|
28
|
+
export async function resolveAdoptedFiles(rawFiles, patches, enginePath, movedFiles) {
|
|
29
|
+
const files = [
|
|
30
|
+
...new Set((rawFiles ?? []).map((raw) => normalizeEngineRelativeInput(raw, '--scan-file'))),
|
|
31
|
+
].sort();
|
|
32
|
+
const moved = new Set(movedFiles);
|
|
33
|
+
for (const file of files) {
|
|
34
|
+
if (moved.has(file)) {
|
|
35
|
+
throw new InvalidArgumentError(`${file} is passed with both --file and --scan-file. --file moves a file the source owns; ` +
|
|
36
|
+
'--scan-file adopts a file no patch owns yet.', '--scan-file');
|
|
37
|
+
}
|
|
38
|
+
const owner = patches.find((patch) => patch.filesAffected.includes(file));
|
|
39
|
+
if (owner !== undefined) {
|
|
40
|
+
throw new InvalidArgumentError(`--scan-file path is already owned by ${owner.filename}: ${file}. ` +
|
|
41
|
+
'--scan-file adopts files no patch owns; move an owned file with --file from its owner.', '--scan-file');
|
|
42
|
+
}
|
|
43
|
+
if (!(await pathExists(join(enginePath, file)))) {
|
|
44
|
+
throw new InvalidArgumentError(`--scan-file path not found in engine/: ${file}. --scan-file adopts a file the worktree ` +
|
|
45
|
+
'already carries, so it needs content to diff.', '--scan-file');
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return files;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Re-checks under the patch-directory lock that no patch claimed an
|
|
52
|
+
* adopted file while the move waited for confirmation.
|
|
53
|
+
*
|
|
54
|
+
* @param patches - Manifest rows read under the lock
|
|
55
|
+
* @param adoptedFiles - Files the move adopts
|
|
56
|
+
* @param command - Command name for the error
|
|
57
|
+
*/
|
|
58
|
+
export function assertAdoptedStillUnowned(patches, adoptedFiles, command) {
|
|
59
|
+
for (const file of adoptedFiles ?? []) {
|
|
60
|
+
const owner = patches.find((patch) => patch.filesAffected.includes(file));
|
|
61
|
+
if (owner !== undefined) {
|
|
62
|
+
throw new InvalidArgumentError(`Patch queue changed while waiting for confirmation: ${owner.filename} now owns ${file}. Re-run the command.`, command);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -24,10 +24,35 @@ import { normalizePatchDisplayName } from '../../utils/validation.js';
|
|
|
24
24
|
import { withDeclarationsReport } from '../declarations-report.js';
|
|
25
25
|
import { proceedAfterDecision } from '../destructive-decision.js';
|
|
26
26
|
import { resolvePlacementPlan } from '../export-flow.js';
|
|
27
|
-
import {
|
|
27
|
+
import { resolveAdoptedFiles } from './move-files-adopt.js';
|
|
28
|
+
import { runMoveSideLint } from './move-size-tier.js';
|
|
28
29
|
import { commitPatchSplit, reportSplitStagedChanges } from './split.js';
|
|
29
|
-
import { assertSourceOwnsFiles, buildNewPatchMetadata, buildSplitDiff, buildSplitSummary, projectSplitManifest, runProjectedSplitLint, splitDeclarationRenamer, } from './split-plan.js';
|
|
30
|
+
import { assertSourceOwnsFiles, buildNewPatchMetadata, buildSplitDiff, buildSplitSummary, projectSplitManifest, runProjectedSplitLint, splitDeclarationReason, splitDeclarationRenamer, } from './split-plan.js';
|
|
30
31
|
import { emptyStagedMaintenance, findOwnerRewriteHolders } from './split-staged.js';
|
|
32
|
+
/**
|
|
33
|
+
* Validates `--file` and `--scan-file` for a `--create` move: the source
|
|
34
|
+
* owns every moved file and keeps at least one, and every adopted file is
|
|
35
|
+
* new and unowned.
|
|
36
|
+
*/
|
|
37
|
+
async function resolveCreateMoveFiles(source, patches, enginePath, options) {
|
|
38
|
+
const movedFiles = [...new Set((options.file ?? []).map((f) => f.trim()).filter(Boolean))].sort();
|
|
39
|
+
if (movedFiles.length === 0) {
|
|
40
|
+
throw new InvalidArgumentError('Specify at least one --file path to move.', '--file');
|
|
41
|
+
}
|
|
42
|
+
assertSourceOwnsFiles(source, movedFiles);
|
|
43
|
+
const movedSet = new Set(movedFiles);
|
|
44
|
+
const remainingFiles = source.filesAffected.filter((f) => !movedSet.has(f));
|
|
45
|
+
if (remainingFiles.length === 0) {
|
|
46
|
+
throw new InvalidArgumentError(`Moving every file out of ${source.filename} would leave it empty. ` +
|
|
47
|
+
'Use "fireforge patch rename" / "fireforge patch reorder" to repurpose or move the whole patch instead, ' +
|
|
48
|
+
'or fold it into an existing patch with "patch move-files <from> <to> --retire-empty".', '--file');
|
|
49
|
+
}
|
|
50
|
+
// Adopted files join the new patch with the moved ones: one body, linted
|
|
51
|
+
// as the transaction leaves it.
|
|
52
|
+
const adoptedFiles = await resolveAdoptedFiles(options.scanFile, patches, enginePath, movedFiles);
|
|
53
|
+
const newPatchFiles = [...new Set([...movedFiles, ...adoptedFiles])].sort();
|
|
54
|
+
return { movedFiles, remainingFiles, adoptedFiles, newPatchFiles };
|
|
55
|
+
}
|
|
31
56
|
/**
|
|
32
57
|
* Runs `patch move-files --create --order <n>`: plans the create+move as a
|
|
33
58
|
* split, lints the projection, confirms, and commits transactionally.
|
|
@@ -53,18 +78,8 @@ export async function patchMoveFilesCreateCommand(projectRoot, fromIdentifier, n
|
|
|
53
78
|
throw new InvalidArgumentError(`--create target "${newPatchName}" already exists as ${existingTarget.filename}. ` +
|
|
54
79
|
'Omit --create to preview a move into the existing patch, or pick a new patch name.', '--create');
|
|
55
80
|
}
|
|
56
|
-
const movedFiles
|
|
57
|
-
|
|
58
|
-
throw new InvalidArgumentError('Specify at least one --file path to move.', '--file');
|
|
59
|
-
}
|
|
60
|
-
assertSourceOwnsFiles(source, movedFiles);
|
|
61
|
-
const movedSet = new Set(movedFiles);
|
|
62
|
-
const remainingFiles = source.filesAffected.filter((f) => !movedSet.has(f));
|
|
63
|
-
if (remainingFiles.length === 0) {
|
|
64
|
-
throw new InvalidArgumentError(`Moving every file out of ${source.filename} would leave it empty. ` +
|
|
65
|
-
'Use "fireforge patch rename" / "fireforge patch reorder" to repurpose or move the whole patch instead.', '--file');
|
|
66
|
-
}
|
|
67
|
-
const movedDiff = await buildSplitDiff(paths.engine, movedFiles, 'moved', source.filename);
|
|
81
|
+
const { movedFiles, remainingFiles, adoptedFiles, newPatchFiles } = await resolveCreateMoveFiles(source, manifest.patches, paths.engine, options);
|
|
82
|
+
const movedDiff = await buildSplitDiff(paths.engine, newPatchFiles, 'moved', source.filename);
|
|
68
83
|
const remainingDiff = await buildSplitDiff(paths.engine, remainingFiles, 'remaining', source.filename);
|
|
69
84
|
const category = options.category ?? source.category;
|
|
70
85
|
const placementOptions = { order: options.order };
|
|
@@ -78,7 +93,7 @@ export async function patchMoveFilesCreateCommand(projectRoot, fromIdentifier, n
|
|
|
78
93
|
}
|
|
79
94
|
const plan = {
|
|
80
95
|
source,
|
|
81
|
-
movedFiles,
|
|
96
|
+
movedFiles: newPatchFiles,
|
|
82
97
|
remainingFiles,
|
|
83
98
|
movedDiff,
|
|
84
99
|
remainingDiff,
|
|
@@ -87,11 +102,13 @@ export async function patchMoveFilesCreateCommand(projectRoot, fromIdentifier, n
|
|
|
87
102
|
category,
|
|
88
103
|
name: displayName,
|
|
89
104
|
description: options.description ?? '',
|
|
90
|
-
ownerRewrites: findOwnerRewriteHolders(manifest.patches, source.filename,
|
|
105
|
+
ownerRewrites: findOwnerRewriteHolders(manifest.patches, source.filename, new Set(newPatchFiles)),
|
|
91
106
|
// Populated by runProjectedSplitLint below (forward edges into the new
|
|
92
107
|
// patch, and the declarations the move re-homes or makes unused).
|
|
93
108
|
stagedDependencyAdditions: new Map(),
|
|
94
109
|
stagedMaintenance: emptyStagedMaintenance(),
|
|
110
|
+
operation: 'patch move-files',
|
|
111
|
+
...(adoptedFiles.length > 0 ? { adoptedFiles } : {}),
|
|
95
112
|
};
|
|
96
113
|
// Policy first: the projected manifest (orders, ranges, the gap rule)
|
|
97
114
|
// does not depend on the lint below, so a refused --order fails before
|
|
@@ -117,9 +134,11 @@ export async function patchMoveFilesCreateCommand(projectRoot, fromIdentifier, n
|
|
|
117
134
|
// projection lint is blind.
|
|
118
135
|
const patchQueueCtx = await buildPatchQueueContext(paths.patches, config);
|
|
119
136
|
const ignoreChecks = buildLintIgnoreChecks(source.lintIgnore);
|
|
120
|
-
await
|
|
137
|
+
await runMoveSideLint({
|
|
121
138
|
engineDir: paths.engine,
|
|
122
139
|
filesAffected: remainingFiles,
|
|
140
|
+
filesBefore: source.filesAffected,
|
|
141
|
+
role: 'source',
|
|
123
142
|
diffContent: remainingDiff,
|
|
124
143
|
config,
|
|
125
144
|
skipLint: options.skipLint,
|
|
@@ -127,9 +146,10 @@ export async function patchMoveFilesCreateCommand(projectRoot, fromIdentifier, n
|
|
|
127
146
|
ignoreChecks,
|
|
128
147
|
patchTier: source.tier,
|
|
129
148
|
});
|
|
130
|
-
await
|
|
149
|
+
await runMoveSideLint({
|
|
131
150
|
engineDir: paths.engine,
|
|
132
|
-
filesAffected:
|
|
151
|
+
filesAffected: newPatchFiles,
|
|
152
|
+
role: 'new patch',
|
|
133
153
|
diffContent: movedDiff,
|
|
134
154
|
config,
|
|
135
155
|
skipLint: options.skipLint,
|
|
@@ -145,6 +165,7 @@ export async function patchMoveFilesCreateCommand(projectRoot, fromIdentifier, n
|
|
|
145
165
|
regressions: projected.regressions,
|
|
146
166
|
out: options.declarationsOut,
|
|
147
167
|
rename: splitDeclarationRenamer(plan),
|
|
168
|
+
reason: splitDeclarationReason(plan),
|
|
148
169
|
});
|
|
149
170
|
const newMetadata = buildNewPatchMetadata(plan, config);
|
|
150
171
|
const decision = await confirmDestructive({
|
|
@@ -160,7 +181,7 @@ export async function patchMoveFilesCreateCommand(projectRoot, fromIdentifier, n
|
|
|
160
181
|
return;
|
|
161
182
|
await commitPatchSplit(paths.patches, plan, newMetadata, options, config);
|
|
162
183
|
success(`Created ${placement.newFilename} (order ${formatPatchOrder(placement.insertionOrder)}) ` +
|
|
163
|
-
`and moved ${
|
|
184
|
+
`and moved ${movedFiles.length} file(s) out of ${source.filename}`);
|
|
164
185
|
reportSplitStagedChanges(plan);
|
|
165
186
|
outro('Move complete');
|
|
166
187
|
}
|
|
@@ -12,6 +12,11 @@
|
|
|
12
12
|
* Preconditions match `re-export`: the engine worktree must currently
|
|
13
13
|
* reflect both patches' content, because both bodies are regenerated from
|
|
14
14
|
* the worktree.
|
|
15
|
+
*
|
|
16
|
+
* The same transaction can also adopt new, unowned engine files
|
|
17
|
+
* into the target (`--scan-file`, see move-files-adopt.ts) and fold the
|
|
18
|
+
* whole source into the target, retiring its slot (`--retire-empty`, see
|
|
19
|
+
* move-files-retire.ts).
|
|
15
20
|
*/
|
|
16
21
|
import type { PatchMetadata, PatchMoveFilesOptions, PatchStagedForwardImport } from '../../types/commands/index.js';
|
|
17
22
|
import type { FireForgeConfig } from '../../types/config.js';
|
|
@@ -28,6 +33,14 @@ export interface MoveIntoPlan {
|
|
|
28
33
|
ownerRewrites: string[];
|
|
29
34
|
/** Forward-import declarations the move introduces, keyed by importing patch. */
|
|
30
35
|
stagedDependencyAdditions: Map<string, PatchStagedForwardImport[]>;
|
|
36
|
+
/** New engine files no patch owned that the target adopts (`--scan-file`). */
|
|
37
|
+
adoptedFiles: string[];
|
|
38
|
+
/**
|
|
39
|
+
* `--retire-empty`: the move empties the source, so every declaration
|
|
40
|
+
* owned by the source re-points to the target and the source's slot is
|
|
41
|
+
* retired. `sourceDiff` is empty then.
|
|
42
|
+
*/
|
|
43
|
+
retireSource: boolean;
|
|
31
44
|
}
|
|
32
45
|
/**
|
|
33
46
|
* Plans, lints, confirms, and commits a file-ownership move into an
|
|
@@ -41,7 +54,12 @@ export declare function runMoveFilesInto(args: {
|
|
|
41
54
|
target: PatchMetadata;
|
|
42
55
|
files: string[];
|
|
43
56
|
sourceAfter: string[];
|
|
57
|
+
/** Includes `adoptedFiles`. */
|
|
44
58
|
targetAfter: string[];
|
|
59
|
+
/** New, unowned engine files the target adopts (`--scan-file`). */
|
|
60
|
+
adoptedFiles?: string[];
|
|
61
|
+
/** The move empties the source and retires its slot (`--retire-empty`). */
|
|
62
|
+
retireSource?: boolean;
|
|
45
63
|
options: PatchMoveFilesOptions;
|
|
46
64
|
config: FireForgeConfig;
|
|
47
65
|
}): Promise<void>;
|
|
@@ -13,6 +13,11 @@
|
|
|
13
13
|
* Preconditions match `re-export`: the engine worktree must currently
|
|
14
14
|
* reflect both patches' content, because both bodies are regenerated from
|
|
15
15
|
* the worktree.
|
|
16
|
+
*
|
|
17
|
+
* The same transaction can also adopt new, unowned engine files
|
|
18
|
+
* into the target (`--scan-file`, see move-files-adopt.ts) and fold the
|
|
19
|
+
* whole source into the target, retiring its slot (`--retire-empty`, see
|
|
20
|
+
* move-files-retire.ts).
|
|
16
21
|
*/
|
|
17
22
|
import { dirname, join, resolve } from 'node:path';
|
|
18
23
|
import { assertConfirmationAvailable, confirmDestructive, } from '../../core/destructive.js';
|
|
@@ -22,17 +27,45 @@ import { buildLintIgnoreChecks } from '../../core/patch-lint-ignore.js';
|
|
|
22
27
|
import { computeProjectedLintRegressions } from '../../core/patch-lint-projection.js';
|
|
23
28
|
import { withPatchDirectoryLock } from '../../core/patch-lock.js';
|
|
24
29
|
import { loadPatchesManifest, savePatchesManifest, validatePatchesManifest, } from '../../core/patch-manifest.js';
|
|
30
|
+
import { removePatchFileAndManifest } from '../../core/patch-manifest.js';
|
|
25
31
|
import { GeneralError, InvalidArgumentError } from '../../errors/base.js';
|
|
26
32
|
import { toError } from '../../utils/errors.js';
|
|
27
|
-
import { readText, writeText } from '../../utils/fs.js';
|
|
33
|
+
import { pathExists, readText, writeText } from '../../utils/fs.js';
|
|
28
34
|
import { info, outro, success, warn } from '../../utils/logger.js';
|
|
29
35
|
import { resolveWaitLockSeconds } from '../../utils/options.js';
|
|
30
36
|
import { withDeclarationsReport } from '../declarations-report.js';
|
|
31
37
|
import { proceedAfterDecision } from '../destructive-decision.js';
|
|
32
|
-
import { runPatchLint } from '../export-shared.js';
|
|
33
38
|
import { projectEntryBody } from './entry-projection.js';
|
|
34
|
-
import {
|
|
39
|
+
import { assertAdoptedStillUnowned } from './move-files-adopt.js';
|
|
40
|
+
import { foldSourceRows, retiredSlotForFold } from './move-files-retire.js';
|
|
41
|
+
import { runMoveSideLint } from './move-size-tier.js';
|
|
42
|
+
import { buildSplitDiff, mergeStagedForwardImports, moveDeclarationReason } from './split-plan.js';
|
|
35
43
|
import { findOwnerRewriteHolders, rewriteSplitOwners } from './split-staged.js';
|
|
44
|
+
/**
|
|
45
|
+
* Applies the manifest side of a move to its rows: owner rewrites (every
|
|
46
|
+
* source-owned declaration on a fold, the moved files' otherwise), the
|
|
47
|
+
* auto-declared forward imports and both `filesAffected` lists. A folded
|
|
48
|
+
* source row is left as is for the retire step to remove.
|
|
49
|
+
*/
|
|
50
|
+
function applyMoveToRows(rows, plan) {
|
|
51
|
+
const movedSet = new Set([...plan.movedFiles, ...plan.adoptedFiles]);
|
|
52
|
+
const rewritten = plan.retireSource
|
|
53
|
+
? foldSourceRows(rows, plan.source.filename, plan.target.filename).patches
|
|
54
|
+
: rows.map((row) => rewriteSplitOwners(row, plan.source.filename, movedSet, plan.target.filename));
|
|
55
|
+
return rewritten.map((row) => {
|
|
56
|
+
let updated = row;
|
|
57
|
+
const decls = plan.stagedDependencyAdditions.get(updated.filename);
|
|
58
|
+
if (decls?.length)
|
|
59
|
+
updated = mergeStagedForwardImports(updated, decls);
|
|
60
|
+
if (updated.filename === plan.source.filename && !plan.retireSource) {
|
|
61
|
+
return { ...updated, filesAffected: plan.sourceAfter };
|
|
62
|
+
}
|
|
63
|
+
if (updated.filename === plan.target.filename) {
|
|
64
|
+
return { ...updated, filesAffected: plan.targetAfter };
|
|
65
|
+
}
|
|
66
|
+
return updated;
|
|
67
|
+
});
|
|
68
|
+
}
|
|
36
69
|
/**
|
|
37
70
|
* Projects the post-move queue through cross-patch lint, auto-declaring the
|
|
38
71
|
* forward edges the move introduces into the target (an importer that used
|
|
@@ -40,12 +73,16 @@ import { findOwnerRewriteHolders, rewriteSplitOwners } from './split-staged.js';
|
|
|
40
73
|
* target is later-ordered). Reports only regressions the move itself adds.
|
|
41
74
|
*/
|
|
42
75
|
function runProjectedMoveLint(plan, baseCtx) {
|
|
43
|
-
const movedSet = new Set(plan.movedFiles);
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
76
|
+
const movedSet = new Set([...plan.movedFiles, ...plan.adoptedFiles]);
|
|
77
|
+
const reason = moveDeclarationReason(plan.source.filename, plan.target.filename, 'patch move-files');
|
|
78
|
+
// The projected rows go through the same rewrite the commit applies, so
|
|
79
|
+
// the projection and the written manifest cannot disagree.
|
|
80
|
+
const rows = baseCtx.entries.flatMap((entry) => (entry.metadata ? [entry.metadata] : []));
|
|
81
|
+
const projectedRows = new Map(applyMoveToRows(rows, { ...plan, stagedDependencyAdditions: new Map() }).map((row) => [row.filename, row]));
|
|
82
|
+
const entries = baseCtx.entries
|
|
83
|
+
.filter((entry) => !(plan.retireSource && entry.filename === plan.source.filename))
|
|
84
|
+
.map((entry) => {
|
|
85
|
+
const metadata = entry.metadata ? (projectedRows.get(entry.filename) ?? null) : null;
|
|
49
86
|
if (entry.filename === plan.source.filename) {
|
|
50
87
|
return { ...entry, metadata, ...projectEntryBody(plan.sourceDiff) };
|
|
51
88
|
}
|
|
@@ -63,6 +100,7 @@ function runProjectedMoveLint(plan, baseCtx) {
|
|
|
63
100
|
specifier: edge.specifier,
|
|
64
101
|
creates: edge.creates,
|
|
65
102
|
owner: plan.target.filename,
|
|
103
|
+
reason,
|
|
66
104
|
};
|
|
67
105
|
const list = stagedDependencyAdditions.get(edge.entry) ?? [];
|
|
68
106
|
if (!list.some((d) => d.file === decl.file && d.specifier === decl.specifier && d.creates === decl.creates)) {
|
|
@@ -77,10 +115,10 @@ function runProjectedMoveLint(plan, baseCtx) {
|
|
|
77
115
|
}
|
|
78
116
|
}
|
|
79
117
|
const baselineIssues = lintPatchQueue(baseCtx).filter((i) => i.severity === 'error');
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
}).filter((i) => i.severity === 'error');
|
|
118
|
+
// Keep every queue-wide lint setting (patch policy, forward-registration
|
|
119
|
+
// scope, token scales) so the projection checks what the committed gate
|
|
120
|
+
// checks, test-manifest suite entries included.
|
|
121
|
+
const projectedIssues = lintPatchQueue({ ...baseCtx, entries }).filter((i) => i.severity === 'error');
|
|
84
122
|
const regressions = computeProjectedLintRegressions(baselineIssues, projectedIssues);
|
|
85
123
|
if (baselineIssues.length > 0 && regressions.length === 0) {
|
|
86
124
|
warn(`Note: projected queue still has ${baselineIssues.length} pre-existing cross-patch ` +
|
|
@@ -94,6 +132,15 @@ function runProjectedMoveLint(plan, baseCtx) {
|
|
|
94
132
|
};
|
|
95
133
|
return { conflicts, regressions, stagedDependencyAdditions };
|
|
96
134
|
}
|
|
135
|
+
/** Restores one body during rollback, warning instead of throwing. */
|
|
136
|
+
async function restoreBody(path, body, label) {
|
|
137
|
+
try {
|
|
138
|
+
await writeText(path, body);
|
|
139
|
+
}
|
|
140
|
+
catch (rollbackError) {
|
|
141
|
+
warn(`Rollback warning: could not restore ${label} body: ${toError(rollbackError).message}`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
97
144
|
/**
|
|
98
145
|
* Commits a confirmed move under the patch-directory lock: write both
|
|
99
146
|
* rewritten bodies, then one manifest rewrite (filesAffected on both rows +
|
|
@@ -113,7 +160,7 @@ async function commitMoveInto(patchesDir, plan, waitLockSeconds) {
|
|
|
113
160
|
currentTarget.filesAffected.join('\n') !== plan.target.filesAffected.join('\n')) {
|
|
114
161
|
throw new InvalidArgumentError('Patch queue changed while waiting for move confirmation. Re-run the command.', 'patch move-files');
|
|
115
162
|
}
|
|
116
|
-
|
|
163
|
+
assertAdoptedStillUnowned(manifest.patches, plan.adoptedFiles, 'patch move-files');
|
|
117
164
|
const sourcePath = join(patchesDir, plan.source.filename);
|
|
118
165
|
const targetPath = join(patchesDir, plan.target.filename);
|
|
119
166
|
const originalSourceBody = await readText(sourcePath);
|
|
@@ -121,25 +168,20 @@ async function commitMoveInto(patchesDir, plan, waitLockSeconds) {
|
|
|
121
168
|
let sourceWritten = false;
|
|
122
169
|
let targetWritten = false;
|
|
123
170
|
try {
|
|
124
|
-
|
|
125
|
-
|
|
171
|
+
if (!plan.retireSource) {
|
|
172
|
+
await writeText(sourcePath, plan.sourceDiff);
|
|
173
|
+
sourceWritten = true;
|
|
174
|
+
}
|
|
126
175
|
await writeText(targetPath, plan.targetDiff);
|
|
127
176
|
targetWritten = true;
|
|
128
|
-
const updatedPatches = manifest.patches
|
|
129
|
-
let updated = rewriteSplitOwners(patch, plan.source.filename, movedSet, plan.target.filename);
|
|
130
|
-
const decls = plan.stagedDependencyAdditions.get(updated.filename);
|
|
131
|
-
if (decls?.length)
|
|
132
|
-
updated = mergeStagedForwardImports(updated, decls);
|
|
133
|
-
if (updated.filename === plan.source.filename) {
|
|
134
|
-
return { ...updated, filesAffected: plan.sourceAfter };
|
|
135
|
-
}
|
|
136
|
-
if (updated.filename === plan.target.filename) {
|
|
137
|
-
return { ...updated, filesAffected: plan.targetAfter };
|
|
138
|
-
}
|
|
139
|
-
return updated;
|
|
140
|
-
});
|
|
177
|
+
const updatedPatches = applyMoveToRows(manifest.patches, plan);
|
|
141
178
|
const updated = validatePatchesManifest({ ...manifest, patches: updatedPatches });
|
|
142
179
|
await savePatchesManifest(patchesDir, updated);
|
|
180
|
+
if (plan.retireSource) {
|
|
181
|
+
// Same write `patch retire` makes: the row and the .patch file
|
|
182
|
+
// leave, and the order stays spent as a retired slot.
|
|
183
|
+
await removePatchFileAndManifest(patchesDir, plan.source.filename, retiredSlotForFold(plan.source, plan.target.filename));
|
|
184
|
+
}
|
|
143
185
|
await appendHistoryBestEffort(patchesDir, {
|
|
144
186
|
operation: 'patch-move-files',
|
|
145
187
|
args: {
|
|
@@ -147,26 +189,19 @@ async function commitMoveInto(patchesDir, plan, waitLockSeconds) {
|
|
|
147
189
|
target: plan.target.filename,
|
|
148
190
|
files: plan.movedFiles,
|
|
149
191
|
ownerRewrites: plan.ownerRewrites,
|
|
192
|
+
...(plan.adoptedFiles.length > 0 ? { adoptedFiles: plan.adoptedFiles } : {}),
|
|
193
|
+
...(plan.retireSource
|
|
194
|
+
? { retiredSource: { filename: plan.source.filename, order: plan.source.order } }
|
|
195
|
+
: {}),
|
|
150
196
|
},
|
|
151
197
|
result: 'ok',
|
|
152
198
|
}, `patch move-files committed`);
|
|
153
199
|
}
|
|
154
200
|
catch (error) {
|
|
155
|
-
if (targetWritten)
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
catch (rollbackError) {
|
|
160
|
-
warn(`Rollback warning: could not restore target body: ${toError(rollbackError).message}`);
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
if (sourceWritten) {
|
|
164
|
-
try {
|
|
165
|
-
await writeText(sourcePath, originalSourceBody);
|
|
166
|
-
}
|
|
167
|
-
catch (rollbackError) {
|
|
168
|
-
warn(`Rollback warning: could not restore source body: ${toError(rollbackError).message}`);
|
|
169
|
-
}
|
|
201
|
+
if (targetWritten)
|
|
202
|
+
await restoreBody(targetPath, originalTargetBody, 'target');
|
|
203
|
+
if (sourceWritten || !(await pathExists(sourcePath))) {
|
|
204
|
+
await restoreBody(sourcePath, originalSourceBody, 'source');
|
|
170
205
|
}
|
|
171
206
|
try {
|
|
172
207
|
await savePatchesManifest(patchesDir, manifest);
|
|
@@ -185,28 +220,40 @@ async function commitMoveInto(patchesDir, plan, waitLockSeconds) {
|
|
|
185
220
|
*/
|
|
186
221
|
export async function runMoveFilesInto(args) {
|
|
187
222
|
const { enginePath, patchesDir, source, target, files, sourceAfter, targetAfter, options } = args;
|
|
223
|
+
const adoptedFiles = args.adoptedFiles ?? [];
|
|
224
|
+
const retireSource = args.retireSource === true;
|
|
188
225
|
// Refuse a prompt-less run before the diff/lint work, not after it.
|
|
189
226
|
assertConfirmationAvailable('patch move-files', options);
|
|
190
|
-
const sourceDiff =
|
|
227
|
+
const sourceDiff = retireSource
|
|
228
|
+
? ''
|
|
229
|
+
: await buildSplitDiff(enginePath, sourceAfter, 'remaining', source.filename);
|
|
191
230
|
const targetDiff = await buildSplitDiff(enginePath, targetAfter, 'moved', target.filename);
|
|
192
|
-
// Per-patch lint both projected bodies with each patch's own waivers
|
|
193
|
-
//
|
|
231
|
+
// Per-patch lint both projected bodies with each patch's own waivers,
|
|
232
|
+
// judged on what the whole transaction leaves (adopted files land in the
|
|
233
|
+
// target directly, so no patch is judged on an intermediate body). The
|
|
234
|
+
// whole-queue context (built once, with the config for committed-gate
|
|
194
235
|
// parity) resolves cross-patch imports and sibling head.js harness roots
|
|
195
236
|
// instead of linting each projected body blind.
|
|
196
237
|
const patchQueueCtx = await buildPatchQueueContext(patchesDir, args.config);
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
238
|
+
if (!retireSource) {
|
|
239
|
+
await runMoveSideLint({
|
|
240
|
+
engineDir: enginePath,
|
|
241
|
+
filesAffected: sourceAfter,
|
|
242
|
+
filesBefore: source.filesAffected,
|
|
243
|
+
role: 'source',
|
|
244
|
+
diffContent: sourceDiff,
|
|
245
|
+
config: args.config,
|
|
246
|
+
skipLint: options.skipLint,
|
|
247
|
+
patchQueueCtx,
|
|
248
|
+
ignoreChecks: buildLintIgnoreChecks(source.lintIgnore),
|
|
249
|
+
patchTier: source.tier,
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
await runMoveSideLint({
|
|
208
253
|
engineDir: enginePath,
|
|
209
254
|
filesAffected: targetAfter,
|
|
255
|
+
filesBefore: target.filesAffected,
|
|
256
|
+
role: 'destination',
|
|
210
257
|
diffContent: targetDiff,
|
|
211
258
|
config: args.config,
|
|
212
259
|
skipLint: options.skipLint,
|
|
@@ -223,8 +270,12 @@ export async function runMoveFilesInto(args) {
|
|
|
223
270
|
targetAfter,
|
|
224
271
|
sourceDiff,
|
|
225
272
|
targetDiff,
|
|
226
|
-
ownerRewrites:
|
|
273
|
+
ownerRewrites: retireSource
|
|
274
|
+
? foldSourceRows(manifest?.patches ?? [], source.filename, target.filename).repointed
|
|
275
|
+
: findOwnerRewriteHolders(manifest?.patches ?? [], source.filename, new Set([...files, ...adoptedFiles])),
|
|
227
276
|
stagedDependencyAdditions: new Map(),
|
|
277
|
+
adoptedFiles,
|
|
278
|
+
retireSource,
|
|
228
279
|
};
|
|
229
280
|
const projected = runProjectedMoveLint(plan, patchQueueCtx);
|
|
230
281
|
plan.stagedDependencyAdditions = projected.stagedDependencyAdditions;
|
|
@@ -232,13 +283,21 @@ export async function runMoveFilesInto(args) {
|
|
|
232
283
|
projectRoot: dirname(resolve(patchesDir)),
|
|
233
284
|
regressions: projected.regressions,
|
|
234
285
|
out: options.declarationsOut,
|
|
286
|
+
reason: moveDeclarationReason(source.filename, target.filename, 'patch move-files'),
|
|
235
287
|
});
|
|
236
288
|
const decision = await confirmDestructive({
|
|
237
289
|
operation: 'patch-move-files',
|
|
238
290
|
title: `Move ${files.length} file(s) from ${source.filename} to ${target.filename}`,
|
|
239
291
|
summary: [
|
|
240
292
|
`moved files (${files.length}): ${files.join(', ')}`,
|
|
241
|
-
|
|
293
|
+
...(adoptedFiles.length > 0
|
|
294
|
+
? [
|
|
295
|
+
`adopted from the worktree (no patch owned them) (${adoptedFiles.length}): ${adoptedFiles.join(', ')}`,
|
|
296
|
+
]
|
|
297
|
+
: []),
|
|
298
|
+
retireSource
|
|
299
|
+
? `${source.filename} is emptied and retired; order ${source.order} stays spent as a retired slot`
|
|
300
|
+
: `${source.filename} keeps (${sourceAfter.length}): ${sourceAfter.join(', ')}`,
|
|
242
301
|
`${target.filename} grows to (${targetAfter.length}): ${targetAfter.join(', ')}`,
|
|
243
302
|
...(plan.ownerRewrites.length > 0
|
|
244
303
|
? [`staged-dependency owners re-pointed in: ${plan.ownerRewrites.join(', ')}`]
|
|
@@ -253,8 +312,14 @@ export async function runMoveFilesInto(args) {
|
|
|
253
312
|
return;
|
|
254
313
|
await commitMoveInto(patchesDir, plan, resolveWaitLockSeconds(options.waitLock));
|
|
255
314
|
success(`Moved ${files.length} file(s) from ${source.filename} to ${target.filename}`);
|
|
315
|
+
if (adoptedFiles.length > 0) {
|
|
316
|
+
info(`Adopted ${adoptedFiles.length} new file(s) into ${target.filename}`);
|
|
317
|
+
}
|
|
256
318
|
if (plan.ownerRewrites.length > 0) {
|
|
257
319
|
info(`Re-pointed staged-dependency owners in: ${plan.ownerRewrites.join(', ')}`);
|
|
258
320
|
}
|
|
321
|
+
if (retireSource) {
|
|
322
|
+
info(`Retired ${source.filename}; order ${source.order} is kept as a retired slot.`);
|
|
323
|
+
}
|
|
259
324
|
outro('Move complete');
|
|
260
325
|
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `patch move-files --retire-empty`: fold every file of a patch into
|
|
3
|
+
* another and retire the emptied patch's slot in the same transaction.
|
|
4
|
+
*
|
|
5
|
+
* Without the flag `move-files` refuses to empty the source, and
|
|
6
|
+
* `patch retire` on an emptied patch refuses while another patch's
|
|
7
|
+
* declaration still names it as the owner. With the flag the move re-points
|
|
8
|
+
* those declarations to the destination first (after the fold the
|
|
9
|
+
* destination creates every file the source did), carries the source's own declarations over with its files, and then
|
|
10
|
+
* retires the source the way `patch retire` does: the row and `.patch` file
|
|
11
|
+
* leave, and the order stays spent as a `patches.json#retired` slot so
|
|
12
|
+
* `patchPolicy.allowGaps: false` sees no gap.
|
|
13
|
+
*/
|
|
14
|
+
import type { PatchMetadata, PatchRetiredSlot } from '../../types/commands/index.js';
|
|
15
|
+
/** The rows of a fold, before the source row is retired. */
|
|
16
|
+
export interface FoldedRows {
|
|
17
|
+
/** Every row: owners re-pointed, the destination carrying the source's declarations. */
|
|
18
|
+
patches: PatchMetadata[];
|
|
19
|
+
/** Patches (other than the source) whose declarations named the source as owner. */
|
|
20
|
+
repointed: string[];
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Re-points every declaration whose owner is `source` to `destination` and
|
|
24
|
+
* carries the source's own declarations onto the destination. The source
|
|
25
|
+
* row is returned unchanged; the caller retires it.
|
|
26
|
+
*
|
|
27
|
+
* @param patches - Manifest rows
|
|
28
|
+
* @param source - Filename of the patch the fold empties
|
|
29
|
+
* @param destination - Filename of the patch that receives its files
|
|
30
|
+
*/
|
|
31
|
+
export declare function foldSourceRows(patches: readonly PatchMetadata[], source: string, destination: string): FoldedRows;
|
|
32
|
+
/**
|
|
33
|
+
* The retired slot the emptied source leaves, shaped as `patch retire`
|
|
34
|
+
* writes it.
|
|
35
|
+
*
|
|
36
|
+
* @param source - The emptied source row
|
|
37
|
+
* @param destination - Filename of the patch that took its files
|
|
38
|
+
*/
|
|
39
|
+
export declare function retiredSlotForFold(source: PatchMetadata, destination: string): PatchRetiredSlot;
|