@wp-typia/project-tools 0.16.11 → 0.16.13

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.
Files changed (119) hide show
  1. package/README.md +9 -3
  2. package/dist/runtime/block-generator-service-core.d.ts +8 -0
  3. package/dist/runtime/block-generator-service-core.js +274 -0
  4. package/dist/runtime/block-generator-service-spec.d.ts +104 -0
  5. package/dist/runtime/block-generator-service-spec.js +139 -0
  6. package/dist/runtime/block-generator-service.d.ts +2 -110
  7. package/dist/runtime/block-generator-service.js +2 -389
  8. package/dist/runtime/built-in-block-artifact-documents.d.ts +3 -0
  9. package/dist/runtime/built-in-block-artifact-documents.js +2 -0
  10. package/dist/runtime/built-in-block-artifact-types.d.ts +51 -0
  11. package/dist/runtime/built-in-block-artifact-types.js +304 -0
  12. package/dist/runtime/built-in-block-artifacts.js +4 -803
  13. package/dist/runtime/built-in-block-attribute-emitters.d.ts +71 -0
  14. package/dist/runtime/built-in-block-attribute-emitters.js +176 -0
  15. package/dist/runtime/built-in-block-attribute-specs.d.ts +38 -0
  16. package/dist/runtime/built-in-block-attribute-specs.js +358 -0
  17. package/dist/runtime/built-in-block-code-templates/basic.d.ts +4 -0
  18. package/dist/runtime/built-in-block-code-templates/basic.js +249 -0
  19. package/dist/runtime/built-in-block-code-templates/compound-child.d.ts +4 -0
  20. package/dist/runtime/built-in-block-code-templates/compound-child.js +138 -0
  21. package/dist/runtime/built-in-block-code-templates/compound-parent.d.ts +6 -0
  22. package/dist/runtime/built-in-block-code-templates/compound-parent.js +227 -0
  23. package/dist/runtime/built-in-block-code-templates/compound-persistence.d.ts +4 -0
  24. package/dist/runtime/built-in-block-code-templates/compound-persistence.js +478 -0
  25. package/dist/runtime/built-in-block-code-templates/compound.d.ts +3 -0
  26. package/dist/runtime/built-in-block-code-templates/compound.js +3 -0
  27. package/dist/runtime/built-in-block-code-templates/interactivity.d.ts +5 -0
  28. package/dist/runtime/built-in-block-code-templates/interactivity.js +547 -0
  29. package/dist/runtime/built-in-block-code-templates/persistence.d.ts +5 -0
  30. package/dist/runtime/built-in-block-code-templates/persistence.js +550 -0
  31. package/dist/runtime/built-in-block-code-templates/shared.d.ts +16 -0
  32. package/dist/runtime/built-in-block-code-templates/shared.js +53 -0
  33. package/dist/runtime/built-in-block-code-templates.d.ts +5 -32
  34. package/dist/runtime/built-in-block-code-templates.js +5 -2230
  35. package/dist/runtime/cli-add-block-config.d.ts +6 -0
  36. package/dist/runtime/cli-add-block-config.js +143 -0
  37. package/dist/runtime/cli-add-block-legacy-validator.d.ts +4 -0
  38. package/dist/runtime/cli-add-block-legacy-validator.js +168 -0
  39. package/dist/runtime/cli-add-block.js +3 -301
  40. package/dist/runtime/cli-add-workspace-assets.d.ts +38 -0
  41. package/dist/runtime/cli-add-workspace-assets.js +399 -0
  42. package/dist/runtime/cli-add-workspace.d.ts +2 -38
  43. package/dist/runtime/cli-add-workspace.js +5 -396
  44. package/dist/runtime/cli-diagnostics.js +76 -4
  45. package/dist/runtime/cli-doctor-environment.d.ts +12 -0
  46. package/dist/runtime/cli-doctor-environment.js +123 -0
  47. package/dist/runtime/cli-doctor-workspace.d.ts +18 -0
  48. package/dist/runtime/cli-doctor-workspace.js +308 -0
  49. package/dist/runtime/cli-doctor.d.ts +4 -2
  50. package/dist/runtime/cli-doctor.js +10 -405
  51. package/dist/runtime/cli-help.js +1 -1
  52. package/dist/runtime/cli-scaffold.d.ts +8 -1
  53. package/dist/runtime/cli-scaffold.js +47 -4
  54. package/dist/runtime/migration-command-surface.d.ts +67 -0
  55. package/dist/runtime/migration-command-surface.js +189 -0
  56. package/dist/runtime/migration-diff-rename.d.ts +13 -0
  57. package/dist/runtime/migration-diff-rename.js +192 -0
  58. package/dist/runtime/migration-diff-transform.d.ts +14 -0
  59. package/dist/runtime/migration-diff-transform.js +105 -0
  60. package/dist/runtime/migration-diff.js +12 -297
  61. package/dist/runtime/migration-generated-artifacts.d.ts +3 -0
  62. package/dist/runtime/migration-generated-artifacts.js +41 -0
  63. package/dist/runtime/migration-maintenance-fixtures.d.ts +23 -0
  64. package/dist/runtime/migration-maintenance-fixtures.js +126 -0
  65. package/dist/runtime/migration-maintenance-verify.d.ts +26 -0
  66. package/dist/runtime/migration-maintenance-verify.js +262 -0
  67. package/dist/runtime/migration-maintenance.d.ts +2 -0
  68. package/dist/runtime/migration-maintenance.js +2 -0
  69. package/dist/runtime/migration-planning.d.ts +23 -0
  70. package/dist/runtime/migration-planning.js +131 -0
  71. package/dist/runtime/migration-project-config-source.d.ts +6 -0
  72. package/dist/runtime/migration-project-config-source.js +424 -0
  73. package/dist/runtime/migration-project-layout-discovery.d.ts +61 -0
  74. package/dist/runtime/migration-project-layout-discovery.js +337 -0
  75. package/dist/runtime/migration-project-layout-paths.d.ts +135 -0
  76. package/dist/runtime/migration-project-layout-paths.js +288 -0
  77. package/dist/runtime/migration-project-layout.d.ts +3 -0
  78. package/dist/runtime/migration-project-layout.js +2 -0
  79. package/dist/runtime/migration-project-workspace.d.ts +47 -0
  80. package/dist/runtime/migration-project-workspace.js +212 -0
  81. package/dist/runtime/migration-project.d.ts +4 -94
  82. package/dist/runtime/migration-project.js +3 -1101
  83. package/dist/runtime/migration-render-diff-rule.d.ts +5 -0
  84. package/dist/runtime/migration-render-diff-rule.js +120 -0
  85. package/dist/runtime/migration-render-execution.d.ts +3 -0
  86. package/dist/runtime/migration-render-execution.js +428 -0
  87. package/dist/runtime/migration-render-generated.d.ts +27 -0
  88. package/dist/runtime/migration-render-generated.js +230 -0
  89. package/dist/runtime/migration-render-support.d.ts +3 -0
  90. package/dist/runtime/migration-render-support.js +16 -0
  91. package/dist/runtime/migration-render.d.ts +3 -33
  92. package/dist/runtime/migration-render.js +3 -789
  93. package/dist/runtime/migrations.d.ts +24 -121
  94. package/dist/runtime/migrations.js +12 -700
  95. package/dist/runtime/scaffold-apply-utils.d.ts +9 -0
  96. package/dist/runtime/scaffold-apply-utils.js +27 -4
  97. package/dist/runtime/scaffold-bootstrap.d.ts +45 -0
  98. package/dist/runtime/scaffold-bootstrap.js +185 -0
  99. package/dist/runtime/scaffold-onboarding.d.ts +12 -0
  100. package/dist/runtime/scaffold-onboarding.js +42 -5
  101. package/dist/runtime/scaffold-package-manager-files.d.ts +35 -0
  102. package/dist/runtime/scaffold-package-manager-files.js +79 -0
  103. package/dist/runtime/scaffold.d.ts +1 -12
  104. package/dist/runtime/scaffold.js +11 -394
  105. package/dist/runtime/template-source-contracts.d.ts +81 -0
  106. package/dist/runtime/template-source-contracts.js +1 -0
  107. package/dist/runtime/template-source-external.d.ts +21 -0
  108. package/dist/runtime/template-source-external.js +184 -0
  109. package/dist/runtime/template-source-locators.d.ts +4 -0
  110. package/dist/runtime/template-source-locators.js +72 -0
  111. package/dist/runtime/template-source-normalization.d.ts +7 -0
  112. package/dist/runtime/template-source-normalization.js +53 -0
  113. package/dist/runtime/template-source-remote.d.ts +23 -0
  114. package/dist/runtime/template-source-remote.js +336 -0
  115. package/dist/runtime/template-source-seeds.d.ts +12 -0
  116. package/dist/runtime/template-source-seeds.js +243 -0
  117. package/dist/runtime/template-source.d.ts +4 -86
  118. package/dist/runtime/template-source.js +9 -828
  119. package/package.json +4 -4
@@ -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 { ROOT_PHP_MIGRATION_REGISTRY, SNAPSHOT_DIR, } from "./migration-constants.js";
6
+ import { SNAPSHOT_DIR, } from "./migration-constants.js";
7
7
  import { createMigrationDiff } from "./migration-diff.js";
8
- import { createMigrationFuzzPlan } from "./migration-fuzz-plan.js";
9
- import { createEdgeFixtureDocument, ensureEdgeFixtureFile } from "./migration-fixtures.js";
10
- import { assertRuleHasNoTodos, assertNoLegacySemverMigrationWorkspace, discoverMigrationInitLayout, discoverMigrationEntries, ensureAdvancedMigrationProject, ensureMigrationDirectories, getFixtureFilePath, getAvailableSnapshotVersionsForBlock, getGeneratedDirForBlock, getProjectPaths, getRuleFilePath, getSnapshotBlockJsonPath, getSnapshotManifestPath, getSnapshotRoot, getSnapshotSavePath, loadMigrationProject, readRuleMetadata, writeInitialMigrationScaffold, writeMigrationConfig, } from "./migration-project.js";
11
- import { formatDiffReport, renderFuzzFile, renderGeneratedDeprecatedFile, renderGeneratedMigrationIndexFile, renderMigrationRegistryFile, renderMigrationRuleFile, renderPhpMigrationRegistryFile, renderVerifyFile, } from "./migration-render.js";
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, formatLegacyMigrationWorkspaceResetGuidance, getLocalTsxBinary, isInteractiveTerminal, readJson, resolveTargetMigrationVersion, runProjectScriptIfPresent, sanitizeSaveSnapshotSource, sanitizeSnapshotBlockJson, } from "./migration-utils.js";
14
- import { readWorkspaceInventory } from "./workspace-inventory.js";
15
- import { getInvalidWorkspaceProjectReason, tryResolveWorkspaceProject } from "./workspace-project.js";
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") ?? undefined,
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
- }