@wp-typia/project-tools 0.16.10 → 0.16.12
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/README.md +9 -3
- package/dist/runtime/built-in-block-artifact-documents.d.ts +3 -0
- package/dist/runtime/built-in-block-artifact-documents.js +2 -0
- package/dist/runtime/built-in-block-artifact-types.d.ts +51 -0
- package/dist/runtime/built-in-block-artifact-types.js +304 -0
- package/dist/runtime/built-in-block-artifacts.js +4 -803
- package/dist/runtime/built-in-block-attribute-emitters.d.ts +71 -0
- package/dist/runtime/built-in-block-attribute-emitters.js +176 -0
- package/dist/runtime/built-in-block-attribute-specs.d.ts +38 -0
- package/dist/runtime/built-in-block-attribute-specs.js +358 -0
- package/dist/runtime/built-in-block-code-templates/basic.d.ts +4 -0
- package/dist/runtime/built-in-block-code-templates/basic.js +249 -0
- package/dist/runtime/built-in-block-code-templates/compound-child.d.ts +4 -0
- package/dist/runtime/built-in-block-code-templates/compound-child.js +138 -0
- package/dist/runtime/built-in-block-code-templates/compound-parent.d.ts +6 -0
- package/dist/runtime/built-in-block-code-templates/compound-parent.js +227 -0
- package/dist/runtime/built-in-block-code-templates/compound-persistence.d.ts +4 -0
- package/dist/runtime/built-in-block-code-templates/compound-persistence.js +478 -0
- package/dist/runtime/built-in-block-code-templates/compound.d.ts +3 -0
- package/dist/runtime/built-in-block-code-templates/compound.js +3 -0
- package/dist/runtime/built-in-block-code-templates/interactivity.d.ts +5 -0
- package/dist/runtime/built-in-block-code-templates/interactivity.js +547 -0
- package/dist/runtime/built-in-block-code-templates/persistence.d.ts +5 -0
- package/dist/runtime/built-in-block-code-templates/persistence.js +550 -0
- package/dist/runtime/built-in-block-code-templates/shared.d.ts +16 -0
- package/dist/runtime/built-in-block-code-templates/shared.js +53 -0
- package/dist/runtime/built-in-block-code-templates.d.ts +5 -32
- package/dist/runtime/built-in-block-code-templates.js +5 -2230
- package/dist/runtime/cli-add-block-config.d.ts +6 -0
- package/dist/runtime/cli-add-block-config.js +143 -0
- package/dist/runtime/cli-add-block-legacy-validator.d.ts +4 -0
- package/dist/runtime/cli-add-block-legacy-validator.js +168 -0
- package/dist/runtime/cli-add-block.js +3 -301
- package/dist/runtime/cli-add-workspace-assets.d.ts +38 -0
- package/dist/runtime/cli-add-workspace-assets.js +399 -0
- package/dist/runtime/cli-add-workspace.d.ts +2 -38
- package/dist/runtime/cli-add-workspace.js +5 -396
- package/dist/runtime/cli-doctor-environment.d.ts +12 -0
- package/dist/runtime/cli-doctor-environment.js +123 -0
- package/dist/runtime/cli-doctor-workspace.d.ts +14 -0
- package/dist/runtime/cli-doctor-workspace.js +296 -0
- package/dist/runtime/cli-doctor.d.ts +4 -2
- package/dist/runtime/cli-doctor.js +10 -405
- package/dist/runtime/cli-help.js +1 -1
- package/dist/runtime/cli-scaffold.js +1 -1
- package/dist/runtime/migration-command-surface.d.ts +67 -0
- package/dist/runtime/migration-command-surface.js +189 -0
- package/dist/runtime/migration-diff-rename.d.ts +13 -0
- package/dist/runtime/migration-diff-rename.js +192 -0
- package/dist/runtime/migration-diff-transform.d.ts +14 -0
- package/dist/runtime/migration-diff-transform.js +105 -0
- package/dist/runtime/migration-diff.js +12 -297
- package/dist/runtime/migration-generated-artifacts.d.ts +3 -0
- package/dist/runtime/migration-generated-artifacts.js +41 -0
- package/dist/runtime/migration-maintenance.d.ts +51 -0
- package/dist/runtime/migration-maintenance.js +380 -0
- package/dist/runtime/migration-planning.d.ts +23 -0
- package/dist/runtime/migration-planning.js +131 -0
- package/dist/runtime/migration-project-config-source.d.ts +6 -0
- package/dist/runtime/migration-project-config-source.js +424 -0
- package/dist/runtime/migration-project-layout-discovery.d.ts +61 -0
- package/dist/runtime/migration-project-layout-discovery.js +337 -0
- package/dist/runtime/migration-project-layout-paths.d.ts +135 -0
- package/dist/runtime/migration-project-layout-paths.js +288 -0
- package/dist/runtime/migration-project-layout.d.ts +3 -0
- package/dist/runtime/migration-project-layout.js +2 -0
- package/dist/runtime/migration-project-workspace.d.ts +47 -0
- package/dist/runtime/migration-project-workspace.js +212 -0
- package/dist/runtime/migration-project.d.ts +4 -94
- package/dist/runtime/migration-project.js +3 -1101
- package/dist/runtime/migration-render-diff-rule.d.ts +5 -0
- package/dist/runtime/migration-render-diff-rule.js +120 -0
- package/dist/runtime/migration-render-execution.d.ts +3 -0
- package/dist/runtime/migration-render-execution.js +428 -0
- package/dist/runtime/migration-render-generated.d.ts +27 -0
- package/dist/runtime/migration-render-generated.js +230 -0
- package/dist/runtime/migration-render-support.d.ts +3 -0
- package/dist/runtime/migration-render-support.js +16 -0
- package/dist/runtime/migration-render.d.ts +3 -33
- package/dist/runtime/migration-render.js +3 -789
- package/dist/runtime/migration-ui-capability.js +1 -1
- package/dist/runtime/migrations.d.ts +24 -118
- package/dist/runtime/migrations.js +12 -700
- package/dist/runtime/scaffold-bootstrap.d.ts +45 -0
- package/dist/runtime/scaffold-bootstrap.js +185 -0
- package/dist/runtime/scaffold-package-manager-files.d.ts +35 -0
- package/dist/runtime/scaffold-package-manager-files.js +79 -0
- package/dist/runtime/scaffold.d.ts +1 -12
- package/dist/runtime/scaffold.js +10 -393
- package/dist/runtime/template-source-contracts.d.ts +81 -0
- package/dist/runtime/template-source-contracts.js +1 -0
- package/dist/runtime/template-source-external.d.ts +21 -0
- package/dist/runtime/template-source-external.js +184 -0
- package/dist/runtime/template-source-locators.d.ts +4 -0
- package/dist/runtime/template-source-locators.js +72 -0
- package/dist/runtime/template-source-normalization.d.ts +7 -0
- package/dist/runtime/template-source-normalization.js +53 -0
- package/dist/runtime/template-source-remote.d.ts +23 -0
- package/dist/runtime/template-source-remote.js +336 -0
- package/dist/runtime/template-source-seeds.d.ts +12 -0
- package/dist/runtime/template-source-seeds.js +243 -0
- package/dist/runtime/template-source.d.ts +4 -86
- package/dist/runtime/template-source.js +9 -828
- package/package.json +5 -5
|
@@ -1,153 +1,19 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import { execFileSync } from "node:child_process";
|
|
4
3
|
import { createReadlinePrompt } from "./cli-prompt.js";
|
|
4
|
+
import { formatMigrationHelpText, parseMigrationArgs, parseNonNegativeInteger, parsePositiveInteger, } from "./migration-command-surface.js";
|
|
5
5
|
import { formatRunScript } from "./package-managers.js";
|
|
6
|
-
import {
|
|
6
|
+
import { SNAPSHOT_DIR, } from "./migration-constants.js";
|
|
7
7
|
import { createMigrationDiff } from "./migration-diff.js";
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
8
|
+
import { ensureEdgeFixtureFile } from "./migration-fixtures.js";
|
|
9
|
+
import { regenerateGeneratedArtifacts, } from "./migration-generated-artifacts.js";
|
|
10
|
+
import { assertDistinctMigrationEdge, createMigrationPlanNextSteps, createMissingProjectSnapshotMessage, formatEdgeCommand, hasSnapshotForVersion, listPreviewableLegacyVersions, resolveLegacyVersions, } from "./migration-planning.js";
|
|
11
|
+
import { assertNoLegacySemverMigrationWorkspace, discoverMigrationInitLayout, ensureAdvancedMigrationProject, ensureMigrationDirectories, getProjectPaths, getRuleFilePath, getSnapshotBlockJsonPath, getSnapshotManifestPath, getSnapshotRoot, getSnapshotSavePath, loadMigrationProject, writeInitialMigrationScaffold, writeMigrationConfig, } from "./migration-project.js";
|
|
12
|
+
import { formatDiffReport, renderMigrationRuleFile, } from "./migration-render.js";
|
|
12
13
|
import { createMigrationRiskSummary, formatMigrationRiskSummary } from "./migration-risk.js";
|
|
13
|
-
import { assertMigrationVersionLabel, compareMigrationVersionLabels, copyFile, detectPackageManagerId,
|
|
14
|
-
import {
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Returns the formatted help text for migration CLI commands and flags.
|
|
18
|
-
*
|
|
19
|
-
* @returns Multi-line usage text for the `wp-typia migrate` command surface.
|
|
20
|
-
*/
|
|
21
|
-
export function formatMigrationHelpText() {
|
|
22
|
-
return `Usage:
|
|
23
|
-
wp-typia migrate init --current-migration-version <label>
|
|
24
|
-
wp-typia migrate snapshot --migration-version <label>
|
|
25
|
-
wp-typia migrate plan --from-migration-version <label> [--to-migration-version current]
|
|
26
|
-
wp-typia migrate wizard
|
|
27
|
-
wp-typia migrate diff --from-migration-version <label> [--to-migration-version current]
|
|
28
|
-
wp-typia migrate scaffold --from-migration-version <label> [--to-migration-version current]
|
|
29
|
-
wp-typia migrate verify [--from-migration-version <label>|--all]
|
|
30
|
-
wp-typia migrate doctor [--from-migration-version <label>|--all]
|
|
31
|
-
wp-typia migrate fixtures [--from-migration-version <label>|--all] [--to-migration-version current] [--force]
|
|
32
|
-
wp-typia migrate fuzz [--from-migration-version <label>|--all] [--iterations <n>] [--seed <n>]
|
|
33
|
-
|
|
34
|
-
Notes:
|
|
35
|
-
\`migrate init\` auto-detects supported single-block and \`src/blocks/*\` multi-block layouts.
|
|
36
|
-
Migration versions use strict schema labels like \`v1\`, \`v2\`, and \`v3\`.
|
|
37
|
-
\`migrate wizard\` is TTY-only and helps you choose one legacy migration version to preview.
|
|
38
|
-
\`migrate plan\` and \`migrate wizard\` are read-only previews; they do not scaffold rules or fixtures.
|
|
39
|
-
--all runs across every configured legacy migration version and every configured block target.
|
|
40
|
-
Existing fixture files are preserved and reported as skipped unless you pass \`--force\`.
|
|
41
|
-
Use \`migrate fixtures --force\` as the explicit refresh path for generated fixture files.
|
|
42
|
-
In TTY usage, \`migrate fixtures --force\` asks before overwriting existing fixture files.
|
|
43
|
-
In non-interactive usage, \`migrate fixtures --force\` overwrites immediately for script compatibility.`;
|
|
44
|
-
}
|
|
45
|
-
/**
|
|
46
|
-
* Parses migration CLI arguments into a structured command payload.
|
|
47
|
-
*
|
|
48
|
-
* @param argv Command-line arguments that follow the `migrate` subcommand.
|
|
49
|
-
* @returns Parsed migration command and normalized flags for runtime dispatch.
|
|
50
|
-
* @throws Error When no arguments are provided, an unknown flag is encountered, or legacy semver flags are used.
|
|
51
|
-
*/
|
|
52
|
-
export function parseMigrationArgs(argv) {
|
|
53
|
-
const parsed = {
|
|
54
|
-
command: undefined,
|
|
55
|
-
flags: {
|
|
56
|
-
all: false,
|
|
57
|
-
currentMigrationVersion: undefined,
|
|
58
|
-
force: false,
|
|
59
|
-
fromMigrationVersion: undefined,
|
|
60
|
-
iterations: undefined,
|
|
61
|
-
migrationVersion: undefined,
|
|
62
|
-
seed: undefined,
|
|
63
|
-
toMigrationVersion: "current",
|
|
64
|
-
},
|
|
65
|
-
};
|
|
66
|
-
if (argv.length === 0) {
|
|
67
|
-
throw new Error(formatMigrationHelpText());
|
|
68
|
-
}
|
|
69
|
-
parsed.command = argv[0];
|
|
70
|
-
for (let index = 1; index < argv.length; index += 1) {
|
|
71
|
-
const arg = argv[index];
|
|
72
|
-
const next = argv[index + 1];
|
|
73
|
-
if (arg === "--")
|
|
74
|
-
continue;
|
|
75
|
-
if (arg === "--all") {
|
|
76
|
-
parsed.flags.all = true;
|
|
77
|
-
continue;
|
|
78
|
-
}
|
|
79
|
-
if (arg === "--force") {
|
|
80
|
-
parsed.flags.force = true;
|
|
81
|
-
continue;
|
|
82
|
-
}
|
|
83
|
-
if (arg === "--current-migration-version") {
|
|
84
|
-
parsed.flags.currentMigrationVersion = next;
|
|
85
|
-
index += 1;
|
|
86
|
-
continue;
|
|
87
|
-
}
|
|
88
|
-
if (arg.startsWith("--current-migration-version=")) {
|
|
89
|
-
parsed.flags.currentMigrationVersion = arg.split("=", 2)[1];
|
|
90
|
-
continue;
|
|
91
|
-
}
|
|
92
|
-
if (arg === "--from-migration-version") {
|
|
93
|
-
parsed.flags.fromMigrationVersion = next;
|
|
94
|
-
index += 1;
|
|
95
|
-
continue;
|
|
96
|
-
}
|
|
97
|
-
if (arg.startsWith("--from-migration-version=")) {
|
|
98
|
-
parsed.flags.fromMigrationVersion = arg.split("=", 2)[1];
|
|
99
|
-
continue;
|
|
100
|
-
}
|
|
101
|
-
if (arg === "--iterations") {
|
|
102
|
-
parsed.flags.iterations = next;
|
|
103
|
-
index += 1;
|
|
104
|
-
continue;
|
|
105
|
-
}
|
|
106
|
-
if (arg.startsWith("--iterations=")) {
|
|
107
|
-
parsed.flags.iterations = arg.split("=", 2)[1];
|
|
108
|
-
continue;
|
|
109
|
-
}
|
|
110
|
-
if (arg === "--seed") {
|
|
111
|
-
parsed.flags.seed = next;
|
|
112
|
-
index += 1;
|
|
113
|
-
continue;
|
|
114
|
-
}
|
|
115
|
-
if (arg.startsWith("--seed=")) {
|
|
116
|
-
parsed.flags.seed = arg.split("=", 2)[1];
|
|
117
|
-
continue;
|
|
118
|
-
}
|
|
119
|
-
if (arg === "--to-migration-version") {
|
|
120
|
-
parsed.flags.toMigrationVersion = next;
|
|
121
|
-
index += 1;
|
|
122
|
-
continue;
|
|
123
|
-
}
|
|
124
|
-
if (arg.startsWith("--to-migration-version=")) {
|
|
125
|
-
parsed.flags.toMigrationVersion = arg.split("=", 2)[1];
|
|
126
|
-
continue;
|
|
127
|
-
}
|
|
128
|
-
if (arg === "--migration-version") {
|
|
129
|
-
parsed.flags.migrationVersion = next;
|
|
130
|
-
index += 1;
|
|
131
|
-
continue;
|
|
132
|
-
}
|
|
133
|
-
if (arg.startsWith("--migration-version=")) {
|
|
134
|
-
parsed.flags.migrationVersion = arg.split("=", 2)[1];
|
|
135
|
-
continue;
|
|
136
|
-
}
|
|
137
|
-
if (arg === "--current-version" ||
|
|
138
|
-
arg.startsWith("--current-version=") ||
|
|
139
|
-
arg === "--version" ||
|
|
140
|
-
arg.startsWith("--version=") ||
|
|
141
|
-
arg === "--from" ||
|
|
142
|
-
arg.startsWith("--from=") ||
|
|
143
|
-
arg === "--to" ||
|
|
144
|
-
arg.startsWith("--to=")) {
|
|
145
|
-
throwLegacyMigrationFlagError(arg);
|
|
146
|
-
}
|
|
147
|
-
throw new Error(`Unknown migration flag: ${arg}`);
|
|
148
|
-
}
|
|
149
|
-
return parsed;
|
|
150
|
-
}
|
|
14
|
+
import { assertMigrationVersionLabel, compareMigrationVersionLabels, copyFile, detectPackageManagerId, isInteractiveTerminal, readJson, resolveTargetMigrationVersion, runProjectScriptIfPresent, sanitizeSaveSnapshotSource, sanitizeSnapshotBlockJson, } from "./migration-utils.js";
|
|
15
|
+
import { doctorProjectMigrations, fixturesProjectMigrations, fuzzProjectMigrations, verifyProjectMigrations, } from "./migration-maintenance.js";
|
|
16
|
+
export { formatMigrationHelpText, parseMigrationArgs };
|
|
151
17
|
export { formatDiffReport };
|
|
152
18
|
/**
|
|
153
19
|
* Dispatch a parsed migration command to the matching runtime workflow.
|
|
@@ -231,7 +97,7 @@ export function runMigrationCommand(command, cwd, { prompt, renderLine = console
|
|
|
231
97
|
fromMigrationVersion: command.flags.fromMigrationVersion,
|
|
232
98
|
iterations: parsePositiveInteger(command.flags.iterations, "iterations") ?? 25,
|
|
233
99
|
renderLine,
|
|
234
|
-
seed: parseNonNegativeInteger(command.flags.seed, "seed")
|
|
100
|
+
seed: parseNonNegativeInteger(command.flags.seed, "seed"),
|
|
235
101
|
});
|
|
236
102
|
default:
|
|
237
103
|
throw new Error(formatMigrationHelpText());
|
|
@@ -517,438 +383,7 @@ export function scaffoldProjectMigrations(projectDir, { fromMigrationVersion, to
|
|
|
517
383
|
}
|
|
518
384
|
return scaffolded.length === 1 ? scaffolded[0] : { scaffolded };
|
|
519
385
|
}
|
|
520
|
-
|
|
521
|
-
* Run deterministic migration verification against generated fixtures.
|
|
522
|
-
*
|
|
523
|
-
* @param projectDir Absolute or relative project directory containing the migration workspace.
|
|
524
|
-
* @param options Verification scope and console rendering options.
|
|
525
|
-
* @returns Verified legacy versions.
|
|
526
|
-
*/
|
|
527
|
-
export function verifyProjectMigrations(projectDir, { all = false, fromMigrationVersion, renderLine = console.log } = {}) {
|
|
528
|
-
const state = loadMigrationProject(projectDir);
|
|
529
|
-
const targetVersions = resolveLegacyVersions(state, { all, fromMigrationVersion });
|
|
530
|
-
const blockEntries = getSelectedEntriesByBlock(state, targetVersions, "verify");
|
|
531
|
-
const legacySingleBlock = isLegacySingleBlockProject(state);
|
|
532
|
-
if (targetVersions.length === 0) {
|
|
533
|
-
renderLine("No legacy migration versions configured for verification.");
|
|
534
|
-
return { verifiedVersions: [] };
|
|
535
|
-
}
|
|
536
|
-
const tsxBinary = getLocalTsxBinary(projectDir);
|
|
537
|
-
for (const [blockKey, entries] of Object.entries(blockEntries)) {
|
|
538
|
-
const block = state.blocks.find((entry) => entry.key === blockKey);
|
|
539
|
-
if (!block || entries.length === 0) {
|
|
540
|
-
continue;
|
|
541
|
-
}
|
|
542
|
-
for (const entry of entries) {
|
|
543
|
-
assertRuleHasNoTodos(projectDir, block, entry.fromVersion, state.config.currentMigrationVersion);
|
|
544
|
-
}
|
|
545
|
-
const verifyScriptPath = path.join(getGeneratedDirForBlock(state.paths, block), "verify.ts");
|
|
546
|
-
if (!fs.existsSync(verifyScriptPath)) {
|
|
547
|
-
const selectedVersionsForBlock = entries.map((entry) => entry.fromVersion);
|
|
548
|
-
throw new Error(`Generated verify script is missing for ${block.blockName} (${selectedVersionsForBlock.join(", ")}). ` +
|
|
549
|
-
`Run \`${formatScaffoldCommand(selectedVersionsForBlock)}\` first, then \`wp-typia migrate doctor --all\` if the workspace should already be scaffolded.`);
|
|
550
|
-
}
|
|
551
|
-
const selectedVersionsForBlock = entries.map((entry) => entry.fromVersion);
|
|
552
|
-
const filteredArgs = all
|
|
553
|
-
? ["--all"]
|
|
554
|
-
: ["--from-migration-version", selectedVersionsForBlock[0]];
|
|
555
|
-
execFileSync(tsxBinary, [verifyScriptPath, ...filteredArgs], {
|
|
556
|
-
cwd: projectDir,
|
|
557
|
-
shell: process.platform === "win32",
|
|
558
|
-
stdio: "inherit",
|
|
559
|
-
});
|
|
560
|
-
renderLine(legacySingleBlock
|
|
561
|
-
? `Verified migrations for ${selectedVersionsForBlock.join(", ")}`
|
|
562
|
-
: `Verified ${block.blockName} migrations for ${selectedVersionsForBlock.join(", ")}`);
|
|
563
|
-
}
|
|
564
|
-
return { verifiedVersions: targetVersions };
|
|
565
|
-
}
|
|
566
|
-
function recordWorkspaceMigrationTargetAlignment(projectDir, state, recordCheck) {
|
|
567
|
-
let invalidWorkspaceReason = null;
|
|
568
|
-
let workspace;
|
|
569
|
-
try {
|
|
570
|
-
invalidWorkspaceReason = getInvalidWorkspaceProjectReason(projectDir);
|
|
571
|
-
workspace = tryResolveWorkspaceProject(projectDir);
|
|
572
|
-
}
|
|
573
|
-
catch (error) {
|
|
574
|
-
recordCheck("fail", "Workspace migration targets", error instanceof Error ? error.message : String(error));
|
|
575
|
-
return;
|
|
576
|
-
}
|
|
577
|
-
if (!workspace) {
|
|
578
|
-
if (invalidWorkspaceReason) {
|
|
579
|
-
recordCheck("fail", "Workspace migration targets", invalidWorkspaceReason);
|
|
580
|
-
}
|
|
581
|
-
return;
|
|
582
|
-
}
|
|
583
|
-
try {
|
|
584
|
-
const inventory = readWorkspaceInventory(workspace.projectDir);
|
|
585
|
-
const expectedTargets = inventory.blocks.map((block) => `${workspace.workspace.namespace}/${block.slug}`);
|
|
586
|
-
const configuredTargets = state.blocks.map((block) => block.blockName);
|
|
587
|
-
const expectedTargetSet = new Set(expectedTargets);
|
|
588
|
-
const configuredTargetSet = new Set(configuredTargets);
|
|
589
|
-
const missingTargets = expectedTargets.filter((target) => !configuredTargetSet.has(target));
|
|
590
|
-
const staleTargets = configuredTargets.filter((target) => !expectedTargetSet.has(target));
|
|
591
|
-
recordCheck(missingTargets.length === 0 && staleTargets.length === 0 ? "pass" : "fail", "Workspace migration targets", missingTargets.length === 0 && staleTargets.length === 0
|
|
592
|
-
? `${expectedTargets.length} workspace block target(s) align with migration config`
|
|
593
|
-
: [
|
|
594
|
-
missingTargets.length > 0
|
|
595
|
-
? `Missing from migration config: ${missingTargets.join(", ")}`
|
|
596
|
-
: null,
|
|
597
|
-
staleTargets.length > 0
|
|
598
|
-
? `Not present in scripts/block-config.ts: ${staleTargets.join(", ")}`
|
|
599
|
-
: null,
|
|
600
|
-
]
|
|
601
|
-
.filter((detail) => typeof detail === "string")
|
|
602
|
-
.join("; "));
|
|
603
|
-
}
|
|
604
|
-
catch (error) {
|
|
605
|
-
recordCheck("fail", "Workspace migration targets", error instanceof Error ? error.message : String(error));
|
|
606
|
-
}
|
|
607
|
-
}
|
|
608
|
-
/**
|
|
609
|
-
* Validate the migration workspace without mutating files.
|
|
610
|
-
*
|
|
611
|
-
* @param projectDir Absolute or relative project directory containing the migration workspace.
|
|
612
|
-
* @param options Doctor scope and console rendering options.
|
|
613
|
-
* @returns Structured doctor check results for the selected legacy versions.
|
|
614
|
-
*/
|
|
615
|
-
export function doctorProjectMigrations(projectDir, { all = false, fromMigrationVersion, renderLine = console.log } = {}) {
|
|
616
|
-
const checks = [];
|
|
617
|
-
const recordCheck = (status, label, detail) => {
|
|
618
|
-
checks.push({ detail, label, status });
|
|
619
|
-
renderLine(`${status === "pass" ? "PASS" : "FAIL"} ${label}: ${detail}`);
|
|
620
|
-
};
|
|
621
|
-
let state;
|
|
622
|
-
try {
|
|
623
|
-
state = loadMigrationProject(projectDir);
|
|
624
|
-
const legacySingleBlock = isLegacySingleBlockProject(state);
|
|
625
|
-
recordCheck("pass", "Migration config", legacySingleBlock
|
|
626
|
-
? `Loaded ${state.blocks[0]?.blockName} @ ${state.config.currentMigrationVersion}`
|
|
627
|
-
: `Loaded ${state.blocks.length} block target(s) @ ${state.config.currentMigrationVersion}`);
|
|
628
|
-
}
|
|
629
|
-
catch (error) {
|
|
630
|
-
recordCheck("fail", "Migration config", error instanceof Error ? error.message : String(error));
|
|
631
|
-
throw new Error("Migration doctor failed.");
|
|
632
|
-
}
|
|
633
|
-
const targetVersions = resolveLegacyVersions(state, { all, fromMigrationVersion });
|
|
634
|
-
const legacySingleBlock = isLegacySingleBlockProject(state);
|
|
635
|
-
const snapshotVersions = new Set(targetVersions.length > 0
|
|
636
|
-
? [state.config.currentMigrationVersion, ...targetVersions]
|
|
637
|
-
: state.config.supportedMigrationVersions);
|
|
638
|
-
recordWorkspaceMigrationTargetAlignment(projectDir, state, recordCheck);
|
|
639
|
-
for (const version of snapshotVersions) {
|
|
640
|
-
for (const block of state.blocks) {
|
|
641
|
-
const snapshotRoot = getSnapshotRoot(projectDir, block, version);
|
|
642
|
-
const blockJsonPath = getSnapshotBlockJsonPath(projectDir, block, version);
|
|
643
|
-
const manifestPath = getSnapshotManifestPath(projectDir, block, version);
|
|
644
|
-
const savePath = getSnapshotSavePath(projectDir, block, version);
|
|
645
|
-
const hasSnapshot = fs.existsSync(snapshotRoot);
|
|
646
|
-
const snapshotIsOptional = !hasSnapshot && isSnapshotOptionalForBlockVersion(state, block, version);
|
|
647
|
-
recordCheck(hasSnapshot || snapshotIsOptional ? "pass" : "fail", legacySingleBlock ? `Snapshot ${version}` : `Snapshot ${block.blockName} @ ${version}`, hasSnapshot
|
|
648
|
-
? path.relative(projectDir, snapshotRoot)
|
|
649
|
-
: `Not present for this version`);
|
|
650
|
-
if (!hasSnapshot) {
|
|
651
|
-
continue;
|
|
652
|
-
}
|
|
653
|
-
for (const targetPath of [blockJsonPath, manifestPath, savePath]) {
|
|
654
|
-
recordCheck(fs.existsSync(targetPath) ? "pass" : "fail", legacySingleBlock
|
|
655
|
-
? `Snapshot file ${version}`
|
|
656
|
-
: `Snapshot file ${block.blockName} @ ${version}`, fs.existsSync(targetPath)
|
|
657
|
-
? path.relative(projectDir, targetPath)
|
|
658
|
-
: `Missing ${path.relative(projectDir, targetPath)}`);
|
|
659
|
-
}
|
|
660
|
-
}
|
|
661
|
-
}
|
|
662
|
-
try {
|
|
663
|
-
const generatedEntries = collectGeneratedMigrationEntries(state);
|
|
664
|
-
const expectedGeneratedFiles = new Map();
|
|
665
|
-
for (const block of state.blocks) {
|
|
666
|
-
const blockGeneratedEntries = generatedEntries.filter(({ entry }) => entry.block.key === block.key);
|
|
667
|
-
const entries = blockGeneratedEntries.map(({ entry }) => entry);
|
|
668
|
-
const generatedDir = getGeneratedDirForBlock(state.paths, block);
|
|
669
|
-
expectedGeneratedFiles.set(path.join(generatedDir, "registry.ts"), renderMigrationRegistryFile(state, block.key, blockGeneratedEntries));
|
|
670
|
-
expectedGeneratedFiles.set(path.join(generatedDir, "deprecated.ts"), renderGeneratedDeprecatedFile(state, block.key, entries));
|
|
671
|
-
expectedGeneratedFiles.set(path.join(generatedDir, "verify.ts"), renderVerifyFile(state, block.key, entries));
|
|
672
|
-
expectedGeneratedFiles.set(path.join(generatedDir, "fuzz.ts"), renderFuzzFile(state, block.key, blockGeneratedEntries));
|
|
673
|
-
}
|
|
674
|
-
expectedGeneratedFiles.set(path.join(state.paths.generatedDir, "index.ts"), renderGeneratedMigrationIndexFile(state, generatedEntries.map(({ entry }) => entry)));
|
|
675
|
-
expectedGeneratedFiles.set(path.join(projectDir, ROOT_PHP_MIGRATION_REGISTRY), renderPhpMigrationRegistryFile(state, generatedEntries.map(({ entry }) => entry)));
|
|
676
|
-
for (const [filePath, expectedSource] of expectedGeneratedFiles) {
|
|
677
|
-
const inSync = fs.existsSync(filePath) && fs.readFileSync(filePath, "utf8") === expectedSource;
|
|
678
|
-
recordCheck(inSync ? "pass" : "fail", `Generated ${path.relative(projectDir, filePath)}`, inSync
|
|
679
|
-
? "In sync"
|
|
680
|
-
: `Run \`wp-typia migrate scaffold --from-migration-version <label>\` or regenerate artifacts`);
|
|
681
|
-
}
|
|
682
|
-
}
|
|
683
|
-
catch (error) {
|
|
684
|
-
recordCheck("fail", "Generated artifacts", error instanceof Error ? error.message : String(error));
|
|
685
|
-
}
|
|
686
|
-
for (const version of targetVersions) {
|
|
687
|
-
for (const block of state.blocks) {
|
|
688
|
-
if (!hasSnapshotForVersion(state, block, version)) {
|
|
689
|
-
recordCheck("pass", `Snapshot coverage ${block.blockName} @ ${version}`, "Target not present for this version");
|
|
690
|
-
continue;
|
|
691
|
-
}
|
|
692
|
-
const rulePath = getRuleFilePath(state.paths, block, version, state.config.currentMigrationVersion);
|
|
693
|
-
const fixturePath = getFixtureFilePath(state.paths, block, version, state.config.currentMigrationVersion);
|
|
694
|
-
recordCheck(fs.existsSync(rulePath) ? "pass" : "fail", legacySingleBlock ? `Rule ${version}` : `Rule ${block.blockName} @ ${version}`, fs.existsSync(rulePath)
|
|
695
|
-
? path.relative(projectDir, rulePath)
|
|
696
|
-
: `Missing ${path.relative(projectDir, rulePath)}`);
|
|
697
|
-
recordCheck(fs.existsSync(fixturePath) ? "pass" : "fail", legacySingleBlock ? `Fixture ${version}` : `Fixture ${block.blockName} @ ${version}`, fs.existsSync(fixturePath)
|
|
698
|
-
? path.relative(projectDir, fixturePath)
|
|
699
|
-
: `Missing ${path.relative(projectDir, fixturePath)}`);
|
|
700
|
-
if (!fs.existsSync(rulePath) || !fs.existsSync(fixturePath)) {
|
|
701
|
-
continue;
|
|
702
|
-
}
|
|
703
|
-
try {
|
|
704
|
-
assertRuleHasNoTodos(projectDir, block, version, state.config.currentMigrationVersion);
|
|
705
|
-
recordCheck("pass", legacySingleBlock
|
|
706
|
-
? `Rule TODOs ${version}`
|
|
707
|
-
: `Rule TODOs ${block.blockName} @ ${version}`, "No TODO MIGRATION markers remain");
|
|
708
|
-
}
|
|
709
|
-
catch (error) {
|
|
710
|
-
recordCheck("fail", legacySingleBlock
|
|
711
|
-
? `Rule TODOs ${version}`
|
|
712
|
-
: `Rule TODOs ${block.blockName} @ ${version}`, error instanceof Error ? error.message : String(error));
|
|
713
|
-
}
|
|
714
|
-
try {
|
|
715
|
-
const ruleMetadata = readRuleMetadata(rulePath);
|
|
716
|
-
recordCheck(ruleMetadata.unresolved.length === 0 ? "pass" : "fail", legacySingleBlock
|
|
717
|
-
? `Rule unresolved ${version}`
|
|
718
|
-
: `Rule unresolved ${block.blockName} @ ${version}`, ruleMetadata.unresolved.length === 0
|
|
719
|
-
? "No unresolved entries remain"
|
|
720
|
-
: ruleMetadata.unresolved.join(", "));
|
|
721
|
-
}
|
|
722
|
-
catch (error) {
|
|
723
|
-
recordCheck("fail", legacySingleBlock
|
|
724
|
-
? `Rule unresolved ${version}`
|
|
725
|
-
: `Rule unresolved ${block.blockName} @ ${version}`, error instanceof Error ? error.message : String(error));
|
|
726
|
-
}
|
|
727
|
-
try {
|
|
728
|
-
const fixtureDocument = readJson(fixturePath);
|
|
729
|
-
recordCheck(Array.isArray(fixtureDocument.cases) && fixtureDocument.cases.length > 0 ? "pass" : "fail", legacySingleBlock
|
|
730
|
-
? `Fixture parse ${version}`
|
|
731
|
-
: `Fixture parse ${block.blockName} @ ${version}`, Array.isArray(fixtureDocument.cases) && fixtureDocument.cases.length > 0
|
|
732
|
-
? `${fixtureDocument.cases.length} case(s)`
|
|
733
|
-
: "Fixture document has no cases");
|
|
734
|
-
const diff = createMigrationDiff(state, block, version, state.config.currentMigrationVersion);
|
|
735
|
-
const expectedFixture = createEdgeFixtureDocument(projectDir, block, version, state.config.currentMigrationVersion, diff);
|
|
736
|
-
const actualCaseNames = new Set((fixtureDocument.cases ?? []).map((fixtureCase) => fixtureCase.name));
|
|
737
|
-
const missingCases = expectedFixture.cases
|
|
738
|
-
.map((fixtureCase) => fixtureCase.name)
|
|
739
|
-
.filter((name) => !actualCaseNames.has(name));
|
|
740
|
-
recordCheck(missingCases.length === 0 ? "pass" : "fail", legacySingleBlock
|
|
741
|
-
? `Fixture coverage ${version}`
|
|
742
|
-
: `Fixture coverage ${block.blockName} @ ${version}`, missingCases.length === 0 ? "All expected fixture cases are present" : `Missing ${missingCases.join(", ")}`);
|
|
743
|
-
recordCheck("pass", legacySingleBlock
|
|
744
|
-
? `Risk summary ${version}`
|
|
745
|
-
: `Risk summary ${block.blockName} @ ${version}`, formatMigrationRiskSummary(createMigrationRiskSummary(diff)));
|
|
746
|
-
}
|
|
747
|
-
catch (error) {
|
|
748
|
-
recordCheck("fail", legacySingleBlock
|
|
749
|
-
? `Fixture parse ${version}`
|
|
750
|
-
: `Fixture parse ${block.blockName} @ ${version}`, error instanceof Error ? error.message : String(error));
|
|
751
|
-
}
|
|
752
|
-
}
|
|
753
|
-
}
|
|
754
|
-
const failedChecks = checks.filter((check) => check.status === "fail");
|
|
755
|
-
renderLine(`${failedChecks.length === 0 ? "PASS" : "FAIL"} Migration doctor summary: ${checks.length - failedChecks.length}/${checks.length} checks passed`);
|
|
756
|
-
if (failedChecks.length > 0) {
|
|
757
|
-
throw new Error("Migration doctor failed.");
|
|
758
|
-
}
|
|
759
|
-
return {
|
|
760
|
-
checkedVersions: targetVersions,
|
|
761
|
-
checks,
|
|
762
|
-
};
|
|
763
|
-
}
|
|
764
|
-
/**
|
|
765
|
-
* Generate or refresh migration fixtures for one or more legacy edges.
|
|
766
|
-
*
|
|
767
|
-
* @param projectDir Absolute or relative project directory containing the migration workspace.
|
|
768
|
-
* @param options Fixture generation scope and refresh options.
|
|
769
|
-
* @returns Generated and skipped legacy versions.
|
|
770
|
-
*/
|
|
771
|
-
export function fixturesProjectMigrations(projectDir, { all = false, confirmOverwrite, force = false, fromMigrationVersion, isInteractive = isInteractiveTerminal(), renderLine = console.log, toMigrationVersion = "current", } = {}) {
|
|
772
|
-
ensureMigrationDirectories(projectDir);
|
|
773
|
-
const state = loadMigrationProject(projectDir);
|
|
774
|
-
const targetMigrationVersion = resolveTargetMigrationVersion(state.config.currentMigrationVersion, toMigrationVersion);
|
|
775
|
-
const targetVersions = resolveLegacyVersions(state, { all, fromMigrationVersion });
|
|
776
|
-
if (targetVersions.length === 0) {
|
|
777
|
-
renderLine("No legacy migration versions configured for fixture generation.");
|
|
778
|
-
return { generatedVersions: [], skippedVersions: [] };
|
|
779
|
-
}
|
|
780
|
-
const generatedVersions = [];
|
|
781
|
-
const skippedVersions = [];
|
|
782
|
-
const fixtureTargets = collectFixtureTargets(state, targetVersions, targetMigrationVersion);
|
|
783
|
-
if (force) {
|
|
784
|
-
const overwriteTargets = fixtureTargets.filter(({ fixturePath }) => fs.existsSync(fixturePath));
|
|
785
|
-
if (isInteractive && overwriteTargets.length > 0) {
|
|
786
|
-
const confirmed = confirmOverwrite?.(`About to overwrite ${overwriteTargets.length} existing migration fixture file(s). Continue?`) ?? promptForConfirmation(`About to overwrite ${overwriteTargets.length} existing migration fixture file(s). Continue?`);
|
|
787
|
-
if (!confirmed) {
|
|
788
|
-
renderLine(`Cancelled fixture refresh. Kept ${overwriteTargets.length} existing fixture file(s).`);
|
|
789
|
-
return {
|
|
790
|
-
generatedVersions,
|
|
791
|
-
skippedVersions: overwriteTargets.map(({ scopedLabel }) => scopedLabel),
|
|
792
|
-
};
|
|
793
|
-
}
|
|
794
|
-
}
|
|
795
|
-
}
|
|
796
|
-
for (const { block, fixturePath, scopedLabel, version } of fixtureTargets) {
|
|
797
|
-
const existed = fs.existsSync(fixturePath);
|
|
798
|
-
const diff = createMigrationDiff(state, block, version, targetMigrationVersion);
|
|
799
|
-
const result = ensureEdgeFixtureFile(projectDir, block, version, targetMigrationVersion, diff, { force });
|
|
800
|
-
if (result.written) {
|
|
801
|
-
generatedVersions.push(scopedLabel);
|
|
802
|
-
renderLine(`${existed ? "Refreshed" : "Generated"} fixture ${path.relative(projectDir, fixturePath)}`);
|
|
803
|
-
}
|
|
804
|
-
else {
|
|
805
|
-
skippedVersions.push(scopedLabel);
|
|
806
|
-
renderLine(`Preserved existing fixture ${path.relative(projectDir, fixturePath)} (use --force to refresh)`);
|
|
807
|
-
}
|
|
808
|
-
}
|
|
809
|
-
return {
|
|
810
|
-
generatedVersions,
|
|
811
|
-
skippedVersions,
|
|
812
|
-
};
|
|
813
|
-
}
|
|
814
|
-
/**
|
|
815
|
-
* Run seeded migration fuzz verification against generated fuzz artifacts.
|
|
816
|
-
*
|
|
817
|
-
* @param projectDir Absolute or relative project directory containing the migration workspace.
|
|
818
|
-
* @param options Fuzz scope, iteration count, seed, and console rendering options.
|
|
819
|
-
* @returns Fuzzed legacy versions and the effective seed.
|
|
820
|
-
*/
|
|
821
|
-
export function fuzzProjectMigrations(projectDir, { all = false, fromMigrationVersion, iterations = 25, renderLine = console.log, seed, } = {}) {
|
|
822
|
-
const state = loadMigrationProject(projectDir);
|
|
823
|
-
const targetVersions = resolveLegacyVersions(state, { all, fromMigrationVersion });
|
|
824
|
-
const blockEntries = getSelectedEntriesByBlock(state, targetVersions, "fuzz");
|
|
825
|
-
const legacySingleBlock = isLegacySingleBlockProject(state);
|
|
826
|
-
if (targetVersions.length === 0) {
|
|
827
|
-
renderLine("No legacy migration versions configured for fuzzing.");
|
|
828
|
-
return { fuzzedVersions: [] };
|
|
829
|
-
}
|
|
830
|
-
const tsxBinary = getLocalTsxBinary(projectDir);
|
|
831
|
-
for (const [blockKey, entries] of Object.entries(blockEntries)) {
|
|
832
|
-
const block = state.blocks.find((entry) => entry.key === blockKey);
|
|
833
|
-
if (!block || entries.length === 0) {
|
|
834
|
-
continue;
|
|
835
|
-
}
|
|
836
|
-
for (const entry of entries) {
|
|
837
|
-
assertRuleHasNoTodos(projectDir, block, entry.fromVersion, state.config.currentMigrationVersion);
|
|
838
|
-
}
|
|
839
|
-
const fuzzScriptPath = path.join(getGeneratedDirForBlock(state.paths, block), "fuzz.ts");
|
|
840
|
-
if (!fs.existsSync(fuzzScriptPath)) {
|
|
841
|
-
const selectedVersionsForBlock = entries.map((entry) => entry.fromVersion);
|
|
842
|
-
throw new Error(`Generated fuzz script is missing for ${block.blockName} (${selectedVersionsForBlock.join(", ")}). ` +
|
|
843
|
-
`Run \`${formatScaffoldCommand(selectedVersionsForBlock)}\` first, then \`wp-typia migrate doctor --all\` if the workspace should already be scaffolded.`);
|
|
844
|
-
}
|
|
845
|
-
const selectedVersionsForBlock = entries.map((entry) => entry.fromVersion);
|
|
846
|
-
const args = [
|
|
847
|
-
fuzzScriptPath,
|
|
848
|
-
...(all ? ["--all"] : ["--from-migration-version", selectedVersionsForBlock[0]]),
|
|
849
|
-
"--iterations",
|
|
850
|
-
String(iterations),
|
|
851
|
-
...(seed === undefined ? [] : ["--seed", String(seed)]),
|
|
852
|
-
];
|
|
853
|
-
execFileSync(tsxBinary, args, {
|
|
854
|
-
cwd: projectDir,
|
|
855
|
-
shell: process.platform === "win32",
|
|
856
|
-
stdio: "inherit",
|
|
857
|
-
});
|
|
858
|
-
renderLine(legacySingleBlock
|
|
859
|
-
? `Fuzzed migrations for ${selectedVersionsForBlock.join(", ")}`
|
|
860
|
-
: `Fuzzed ${block.blockName} migrations for ${selectedVersionsForBlock.join(", ")}`);
|
|
861
|
-
}
|
|
862
|
-
return { fuzzedVersions: targetVersions, seed };
|
|
863
|
-
}
|
|
864
|
-
function parsePositiveInteger(value, label) {
|
|
865
|
-
if (!value) {
|
|
866
|
-
return undefined;
|
|
867
|
-
}
|
|
868
|
-
if (!/^\d+$/.test(value)) {
|
|
869
|
-
throw new Error(`Invalid ${label}: ${value}. Expected a positive integer.`);
|
|
870
|
-
}
|
|
871
|
-
const parsed = Number.parseInt(value, 10);
|
|
872
|
-
if (!Number.isInteger(parsed) || parsed <= 0) {
|
|
873
|
-
throw new Error(`Invalid ${label}: ${value}. Expected a positive integer.`);
|
|
874
|
-
}
|
|
875
|
-
return parsed;
|
|
876
|
-
}
|
|
877
|
-
function parseNonNegativeInteger(value, label) {
|
|
878
|
-
if (!value) {
|
|
879
|
-
return undefined;
|
|
880
|
-
}
|
|
881
|
-
if (!/^\d+$/.test(value)) {
|
|
882
|
-
throw new Error(`Invalid ${label}: ${value}. Expected a non-negative integer.`);
|
|
883
|
-
}
|
|
884
|
-
const parsed = Number.parseInt(value, 10);
|
|
885
|
-
if (!Number.isInteger(parsed) || parsed < 0) {
|
|
886
|
-
throw new Error(`Invalid ${label}: ${value}. Expected a non-negative integer.`);
|
|
887
|
-
}
|
|
888
|
-
return parsed;
|
|
889
|
-
}
|
|
890
|
-
function resolveLegacyVersions(state, { all = false, availableVersions, fromMigrationVersion, }) {
|
|
891
|
-
const configuredLegacyVersions = listConfiguredLegacyVersions(state);
|
|
892
|
-
const legacyVersions = availableVersions ?? configuredLegacyVersions;
|
|
893
|
-
if (fromMigrationVersion) {
|
|
894
|
-
if (!legacyVersions.includes(fromMigrationVersion)) {
|
|
895
|
-
throw new Error(legacyVersions.length === 0
|
|
896
|
-
? availableVersions && configuredLegacyVersions.length > 0
|
|
897
|
-
? `Unsupported migration version: ${fromMigrationVersion}. No previewable legacy migration versions are available yet because none currently have snapshot coverage. ` +
|
|
898
|
-
`Restore or recapture the missing snapshots first.`
|
|
899
|
-
: `Unsupported migration version: ${fromMigrationVersion}. No legacy migration versions are configured yet. ` +
|
|
900
|
-
`Capture an older schema release with \`wp-typia migrate snapshot --migration-version <label>\` first.`
|
|
901
|
-
: `Unsupported migration version: ${fromMigrationVersion}. Available legacy migration versions: ${legacyVersions.join(", ")}.`);
|
|
902
|
-
}
|
|
903
|
-
return [fromMigrationVersion];
|
|
904
|
-
}
|
|
905
|
-
if (all) {
|
|
906
|
-
return legacyVersions;
|
|
907
|
-
}
|
|
908
|
-
return legacyVersions.slice(0, 1);
|
|
909
|
-
}
|
|
910
|
-
function listConfiguredLegacyVersions(state) {
|
|
911
|
-
return state.config.supportedMigrationVersions
|
|
912
|
-
.filter((version) => version !== state.config.currentMigrationVersion)
|
|
913
|
-
.sort(compareMigrationVersionLabels);
|
|
914
|
-
}
|
|
915
|
-
function listPreviewableLegacyVersions(state) {
|
|
916
|
-
return [...new Set(state.blocks.flatMap((block) => getAvailableSnapshotVersionsForBlock(state.projectDir, state.config.supportedMigrationVersions, block)))]
|
|
917
|
-
.filter((version) => version !== state.config.currentMigrationVersion)
|
|
918
|
-
.sort(compareMigrationVersionLabels);
|
|
919
|
-
}
|
|
920
|
-
function collectGeneratedMigrationEntries(state) {
|
|
921
|
-
return discoverMigrationEntries(state).map((entry) => {
|
|
922
|
-
const block = state.blocks.find((target) => target.key === entry.block.key);
|
|
923
|
-
if (!block) {
|
|
924
|
-
throw new Error(`Unknown migration block target: ${entry.block.key}`);
|
|
925
|
-
}
|
|
926
|
-
const diff = createMigrationDiff(state, entry.block, entry.fromVersion, entry.toVersion);
|
|
927
|
-
const legacyManifest = readJson(getSnapshotManifestPath(state.projectDir, entry.block, entry.fromVersion));
|
|
928
|
-
return {
|
|
929
|
-
diff,
|
|
930
|
-
entry,
|
|
931
|
-
fuzzPlan: createMigrationFuzzPlan(legacyManifest, block.currentManifest, diff),
|
|
932
|
-
riskSummary: createMigrationRiskSummary(diff),
|
|
933
|
-
};
|
|
934
|
-
});
|
|
935
|
-
}
|
|
936
|
-
function regenerateGeneratedArtifacts(projectDir) {
|
|
937
|
-
const state = loadMigrationProject(projectDir);
|
|
938
|
-
const generatedEntries = collectGeneratedMigrationEntries(state);
|
|
939
|
-
for (const block of state.blocks) {
|
|
940
|
-
const blockGeneratedEntries = generatedEntries.filter(({ entry }) => entry.block.key === block.key);
|
|
941
|
-
const entries = blockGeneratedEntries.map(({ entry }) => entry);
|
|
942
|
-
const generatedDir = getGeneratedDirForBlock(state.paths, block);
|
|
943
|
-
fs.mkdirSync(generatedDir, { recursive: true });
|
|
944
|
-
fs.writeFileSync(path.join(generatedDir, "registry.ts"), renderMigrationRegistryFile(state, block.key, blockGeneratedEntries), "utf8");
|
|
945
|
-
fs.writeFileSync(path.join(generatedDir, "deprecated.ts"), renderGeneratedDeprecatedFile(state, block.key, entries), "utf8");
|
|
946
|
-
fs.writeFileSync(path.join(generatedDir, "verify.ts"), renderVerifyFile(state, block.key, entries), "utf8");
|
|
947
|
-
fs.writeFileSync(path.join(generatedDir, "fuzz.ts"), renderFuzzFile(state, block.key, blockGeneratedEntries), "utf8");
|
|
948
|
-
}
|
|
949
|
-
fs.writeFileSync(path.join(state.paths.generatedDir, "index.ts"), renderGeneratedMigrationIndexFile(state, generatedEntries.map(({ entry }) => entry)), "utf8");
|
|
950
|
-
fs.writeFileSync(path.join(projectDir, ROOT_PHP_MIGRATION_REGISTRY), renderPhpMigrationRegistryFile(state, generatedEntries.map(({ entry }) => entry)), "utf8");
|
|
951
|
-
}
|
|
386
|
+
export { doctorProjectMigrations, fixturesProjectMigrations, fuzzProjectMigrations, verifyProjectMigrations, };
|
|
952
387
|
/**
|
|
953
388
|
* Initialize migration scaffolding for one or more block targets.
|
|
954
389
|
*
|
|
@@ -981,126 +416,3 @@ export function seedProjectMigrations(projectDir, currentMigrationVersion, block
|
|
|
981
416
|
renderLine(`Initialized migrations for ${blocks.map((block) => block.blockName).join(", ")} at migration version ${currentMigrationVersion}`);
|
|
982
417
|
return loadMigrationProject(projectDir);
|
|
983
418
|
}
|
|
984
|
-
function hasSnapshotForVersion(state, block, version) {
|
|
985
|
-
return fs.existsSync(getSnapshotManifestPath(state.projectDir, block, version));
|
|
986
|
-
}
|
|
987
|
-
function getSelectedEntriesByBlock(state, targetVersions, command) {
|
|
988
|
-
const discoveredEntries = discoverMigrationEntries(state);
|
|
989
|
-
const discoveredEntryKeys = new Set(discoveredEntries.map((entry) => `${entry.block.key}:${entry.fromVersion}`));
|
|
990
|
-
const missingEntries = targetVersions.flatMap((version) => state.blocks
|
|
991
|
-
.filter((block) => hasSnapshotForVersion(state, block, version))
|
|
992
|
-
.filter((block) => !discoveredEntryKeys.has(`${block.key}:${version}`))
|
|
993
|
-
.map((block) => ({ block, version })));
|
|
994
|
-
if (missingEntries.length > 0) {
|
|
995
|
-
const missingLabels = missingEntries
|
|
996
|
-
.map(({ block, version }) => `${block.blockName} @ ${version}`)
|
|
997
|
-
.join(", ");
|
|
998
|
-
const missingVersions = [...new Set(missingEntries.map(({ version }) => version))].sort(compareMigrationVersionLabels);
|
|
999
|
-
throw new Error(`Missing migration ${command} inputs for ${missingLabels}. ` +
|
|
1000
|
-
`Run \`${formatScaffoldCommand(missingVersions)}\` first, then \`wp-typia migrate doctor --all\` if the workspace should already be scaffolded.`);
|
|
1001
|
-
}
|
|
1002
|
-
return groupEntriesByBlock(discoveredEntries.filter((entry) => targetVersions.includes(entry.fromVersion)));
|
|
1003
|
-
}
|
|
1004
|
-
function isSnapshotOptionalForBlockVersion(state, block, version) {
|
|
1005
|
-
if (block.layout !== "multi") {
|
|
1006
|
-
return false;
|
|
1007
|
-
}
|
|
1008
|
-
const introducedVersions = [...new Set(state.config.supportedMigrationVersions)]
|
|
1009
|
-
.filter((candidateVersion) => hasSnapshotForVersion(state, block, candidateVersion))
|
|
1010
|
-
.sort(compareMigrationVersionLabels);
|
|
1011
|
-
const firstIntroducedVersion = introducedVersions[0];
|
|
1012
|
-
if (!firstIntroducedVersion) {
|
|
1013
|
-
return false;
|
|
1014
|
-
}
|
|
1015
|
-
return compareMigrationVersionLabels(version, firstIntroducedVersion) < 0;
|
|
1016
|
-
}
|
|
1017
|
-
function groupEntriesByBlock(entries) {
|
|
1018
|
-
return entries.reduce((accumulator, entry) => {
|
|
1019
|
-
if (!accumulator[entry.block.key]) {
|
|
1020
|
-
accumulator[entry.block.key] = [];
|
|
1021
|
-
}
|
|
1022
|
-
accumulator[entry.block.key].push(entry);
|
|
1023
|
-
return accumulator;
|
|
1024
|
-
}, {});
|
|
1025
|
-
}
|
|
1026
|
-
function isLegacySingleBlockProject(state) {
|
|
1027
|
-
return state.blocks.length === 1 && state.blocks[0]?.layout === "legacy";
|
|
1028
|
-
}
|
|
1029
|
-
function assertDistinctMigrationEdge(command, fromVersion, toVersion) {
|
|
1030
|
-
if (fromVersion === toVersion) {
|
|
1031
|
-
throw new Error(`\`migrate ${command}\` requires different source and target migration versions, but both resolved to ${fromVersion}. ` +
|
|
1032
|
-
`Choose an older snapshot with \`--from-migration-version <label>\` or capture a newer schema release with \`wp-typia migrate snapshot --migration-version <label>\` first.`);
|
|
1033
|
-
}
|
|
1034
|
-
}
|
|
1035
|
-
function createMigrationPlanNextSteps(fromVersion, targetVersion, currentVersion) {
|
|
1036
|
-
if (targetVersion !== currentVersion) {
|
|
1037
|
-
return [
|
|
1038
|
-
formatEdgeCommand("scaffold", fromVersion, targetVersion, currentVersion),
|
|
1039
|
-
];
|
|
1040
|
-
}
|
|
1041
|
-
return [
|
|
1042
|
-
formatEdgeCommand("scaffold", fromVersion, targetVersion, currentVersion),
|
|
1043
|
-
`wp-typia migrate doctor --from-migration-version ${fromVersion}`,
|
|
1044
|
-
`wp-typia migrate verify --from-migration-version ${fromVersion}`,
|
|
1045
|
-
`wp-typia migrate fuzz --from-migration-version ${fromVersion}`,
|
|
1046
|
-
];
|
|
1047
|
-
}
|
|
1048
|
-
function formatEdgeCommand(command, fromVersion, targetVersion, currentVersion) {
|
|
1049
|
-
return targetVersion === currentVersion
|
|
1050
|
-
? `wp-typia migrate ${command} --from-migration-version ${fromVersion}`
|
|
1051
|
-
: `wp-typia migrate ${command} --from-migration-version ${fromVersion} --to-migration-version ${targetVersion}`;
|
|
1052
|
-
}
|
|
1053
|
-
function createMissingProjectSnapshotMessage(state, fromVersion) {
|
|
1054
|
-
const snapshotVersions = [...new Set(state.blocks.flatMap((block) => getAvailableSnapshotVersionsForBlock(state.projectDir, state.config.supportedMigrationVersions, block)))].sort(compareMigrationVersionLabels);
|
|
1055
|
-
return snapshotVersions.length === 0
|
|
1056
|
-
? `No migration block targets have a snapshot for ${fromVersion}. No snapshots exist yet in this project. ` +
|
|
1057
|
-
`Run \`wp-typia migrate snapshot --migration-version ${fromVersion}\` first if you want to preserve that schema state.`
|
|
1058
|
-
: `No migration block targets have a snapshot for ${fromVersion}. ` +
|
|
1059
|
-
`Available snapshot versions in this project: ${snapshotVersions.join(", ")}. ` +
|
|
1060
|
-
`Run \`wp-typia migrate snapshot --migration-version ${fromVersion}\` first if you want to preserve that schema state.`;
|
|
1061
|
-
}
|
|
1062
|
-
function formatScaffoldCommand(versions) {
|
|
1063
|
-
const uniqueVersions = [...new Set(versions)].sort(compareMigrationVersionLabels);
|
|
1064
|
-
return uniqueVersions.length === 1
|
|
1065
|
-
? `wp-typia migrate scaffold --from-migration-version ${uniqueVersions[0]}`
|
|
1066
|
-
: "wp-typia migrate scaffold --from-migration-version <label>";
|
|
1067
|
-
}
|
|
1068
|
-
function throwLegacyMigrationFlagError(flag) {
|
|
1069
|
-
const replacement = flag.startsWith("--current-version")
|
|
1070
|
-
? "--current-migration-version"
|
|
1071
|
-
: flag.startsWith("--version")
|
|
1072
|
-
? "--migration-version"
|
|
1073
|
-
: flag.startsWith("--from")
|
|
1074
|
-
? "--from-migration-version"
|
|
1075
|
-
: "--to-migration-version";
|
|
1076
|
-
throw new Error(`Legacy migration flag \`${flag}\` is no longer supported. Use \`${replacement}\` with schema labels like \`v1\` and \`v2\` instead. ` +
|
|
1077
|
-
formatLegacyMigrationWorkspaceResetGuidance());
|
|
1078
|
-
}
|
|
1079
|
-
function promptForConfirmation(message) {
|
|
1080
|
-
process.stdout.write(`${message} [y/N]: `);
|
|
1081
|
-
const buffer = Buffer.alloc(1);
|
|
1082
|
-
let answer = "";
|
|
1083
|
-
while (true) {
|
|
1084
|
-
const bytesRead = fs.readSync(process.stdin.fd, buffer, 0, 1, null);
|
|
1085
|
-
if (bytesRead === 0) {
|
|
1086
|
-
break;
|
|
1087
|
-
}
|
|
1088
|
-
const char = buffer.toString("utf8", 0, bytesRead);
|
|
1089
|
-
if (char === "\n" || char === "\r") {
|
|
1090
|
-
break;
|
|
1091
|
-
}
|
|
1092
|
-
answer += char;
|
|
1093
|
-
}
|
|
1094
|
-
const normalized = answer.trim().toLowerCase();
|
|
1095
|
-
return normalized === "y" || normalized === "yes";
|
|
1096
|
-
}
|
|
1097
|
-
function collectFixtureTargets(state, targetVersions, targetVersion) {
|
|
1098
|
-
return targetVersions.flatMap((version) => state.blocks
|
|
1099
|
-
.filter((block) => hasSnapshotForVersion(state, block, version))
|
|
1100
|
-
.map((block) => ({
|
|
1101
|
-
block,
|
|
1102
|
-
fixturePath: getFixtureFilePath(state.paths, block, version, targetVersion),
|
|
1103
|
-
scopedLabel: `${block.key}@${version}`,
|
|
1104
|
-
version,
|
|
1105
|
-
})));
|
|
1106
|
-
}
|