@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.
Files changed (104) hide show
  1. package/README.md +9 -3
  2. package/dist/runtime/built-in-block-artifact-documents.d.ts +3 -0
  3. package/dist/runtime/built-in-block-artifact-documents.js +2 -0
  4. package/dist/runtime/built-in-block-artifact-types.d.ts +51 -0
  5. package/dist/runtime/built-in-block-artifact-types.js +304 -0
  6. package/dist/runtime/built-in-block-artifacts.js +4 -803
  7. package/dist/runtime/built-in-block-attribute-emitters.d.ts +71 -0
  8. package/dist/runtime/built-in-block-attribute-emitters.js +176 -0
  9. package/dist/runtime/built-in-block-attribute-specs.d.ts +38 -0
  10. package/dist/runtime/built-in-block-attribute-specs.js +358 -0
  11. package/dist/runtime/built-in-block-code-templates/basic.d.ts +4 -0
  12. package/dist/runtime/built-in-block-code-templates/basic.js +249 -0
  13. package/dist/runtime/built-in-block-code-templates/compound-child.d.ts +4 -0
  14. package/dist/runtime/built-in-block-code-templates/compound-child.js +138 -0
  15. package/dist/runtime/built-in-block-code-templates/compound-parent.d.ts +6 -0
  16. package/dist/runtime/built-in-block-code-templates/compound-parent.js +227 -0
  17. package/dist/runtime/built-in-block-code-templates/compound-persistence.d.ts +4 -0
  18. package/dist/runtime/built-in-block-code-templates/compound-persistence.js +478 -0
  19. package/dist/runtime/built-in-block-code-templates/compound.d.ts +3 -0
  20. package/dist/runtime/built-in-block-code-templates/compound.js +3 -0
  21. package/dist/runtime/built-in-block-code-templates/interactivity.d.ts +5 -0
  22. package/dist/runtime/built-in-block-code-templates/interactivity.js +547 -0
  23. package/dist/runtime/built-in-block-code-templates/persistence.d.ts +5 -0
  24. package/dist/runtime/built-in-block-code-templates/persistence.js +550 -0
  25. package/dist/runtime/built-in-block-code-templates/shared.d.ts +16 -0
  26. package/dist/runtime/built-in-block-code-templates/shared.js +53 -0
  27. package/dist/runtime/built-in-block-code-templates.d.ts +5 -32
  28. package/dist/runtime/built-in-block-code-templates.js +5 -2230
  29. package/dist/runtime/cli-add-block-config.d.ts +6 -0
  30. package/dist/runtime/cli-add-block-config.js +143 -0
  31. package/dist/runtime/cli-add-block-legacy-validator.d.ts +4 -0
  32. package/dist/runtime/cli-add-block-legacy-validator.js +168 -0
  33. package/dist/runtime/cli-add-block.js +3 -301
  34. package/dist/runtime/cli-add-workspace-assets.d.ts +38 -0
  35. package/dist/runtime/cli-add-workspace-assets.js +399 -0
  36. package/dist/runtime/cli-add-workspace.d.ts +2 -38
  37. package/dist/runtime/cli-add-workspace.js +5 -396
  38. package/dist/runtime/cli-doctor-environment.d.ts +12 -0
  39. package/dist/runtime/cli-doctor-environment.js +123 -0
  40. package/dist/runtime/cli-doctor-workspace.d.ts +14 -0
  41. package/dist/runtime/cli-doctor-workspace.js +296 -0
  42. package/dist/runtime/cli-doctor.d.ts +4 -2
  43. package/dist/runtime/cli-doctor.js +10 -405
  44. package/dist/runtime/cli-help.js +1 -1
  45. package/dist/runtime/cli-scaffold.js +1 -1
  46. package/dist/runtime/migration-command-surface.d.ts +67 -0
  47. package/dist/runtime/migration-command-surface.js +189 -0
  48. package/dist/runtime/migration-diff-rename.d.ts +13 -0
  49. package/dist/runtime/migration-diff-rename.js +192 -0
  50. package/dist/runtime/migration-diff-transform.d.ts +14 -0
  51. package/dist/runtime/migration-diff-transform.js +105 -0
  52. package/dist/runtime/migration-diff.js +12 -297
  53. package/dist/runtime/migration-generated-artifacts.d.ts +3 -0
  54. package/dist/runtime/migration-generated-artifacts.js +41 -0
  55. package/dist/runtime/migration-maintenance.d.ts +51 -0
  56. package/dist/runtime/migration-maintenance.js +380 -0
  57. package/dist/runtime/migration-planning.d.ts +23 -0
  58. package/dist/runtime/migration-planning.js +131 -0
  59. package/dist/runtime/migration-project-config-source.d.ts +6 -0
  60. package/dist/runtime/migration-project-config-source.js +424 -0
  61. package/dist/runtime/migration-project-layout-discovery.d.ts +61 -0
  62. package/dist/runtime/migration-project-layout-discovery.js +337 -0
  63. package/dist/runtime/migration-project-layout-paths.d.ts +135 -0
  64. package/dist/runtime/migration-project-layout-paths.js +288 -0
  65. package/dist/runtime/migration-project-layout.d.ts +3 -0
  66. package/dist/runtime/migration-project-layout.js +2 -0
  67. package/dist/runtime/migration-project-workspace.d.ts +47 -0
  68. package/dist/runtime/migration-project-workspace.js +212 -0
  69. package/dist/runtime/migration-project.d.ts +4 -94
  70. package/dist/runtime/migration-project.js +3 -1101
  71. package/dist/runtime/migration-render-diff-rule.d.ts +5 -0
  72. package/dist/runtime/migration-render-diff-rule.js +120 -0
  73. package/dist/runtime/migration-render-execution.d.ts +3 -0
  74. package/dist/runtime/migration-render-execution.js +428 -0
  75. package/dist/runtime/migration-render-generated.d.ts +27 -0
  76. package/dist/runtime/migration-render-generated.js +230 -0
  77. package/dist/runtime/migration-render-support.d.ts +3 -0
  78. package/dist/runtime/migration-render-support.js +16 -0
  79. package/dist/runtime/migration-render.d.ts +3 -33
  80. package/dist/runtime/migration-render.js +3 -789
  81. package/dist/runtime/migration-ui-capability.js +1 -1
  82. package/dist/runtime/migrations.d.ts +24 -118
  83. package/dist/runtime/migrations.js +12 -700
  84. package/dist/runtime/scaffold-bootstrap.d.ts +45 -0
  85. package/dist/runtime/scaffold-bootstrap.js +185 -0
  86. package/dist/runtime/scaffold-package-manager-files.d.ts +35 -0
  87. package/dist/runtime/scaffold-package-manager-files.js +79 -0
  88. package/dist/runtime/scaffold.d.ts +1 -12
  89. package/dist/runtime/scaffold.js +10 -393
  90. package/dist/runtime/template-source-contracts.d.ts +81 -0
  91. package/dist/runtime/template-source-contracts.js +1 -0
  92. package/dist/runtime/template-source-external.d.ts +21 -0
  93. package/dist/runtime/template-source-external.js +184 -0
  94. package/dist/runtime/template-source-locators.d.ts +4 -0
  95. package/dist/runtime/template-source-locators.js +72 -0
  96. package/dist/runtime/template-source-normalization.d.ts +7 -0
  97. package/dist/runtime/template-source-normalization.js +53 -0
  98. package/dist/runtime/template-source-remote.d.ts +23 -0
  99. package/dist/runtime/template-source-remote.js +336 -0
  100. package/dist/runtime/template-source-seeds.d.ts +12 -0
  101. package/dist/runtime/template-source-seeds.js +243 -0
  102. package/dist/runtime/template-source.d.ts +4 -86
  103. package/dist/runtime/template-source.js +9 -828
  104. package/package.json +5 -5
@@ -0,0 +1,5 @@
1
+ import type { MigrationDiff, MigrationRuleFileInput } from "./migration-types.js";
2
+ export declare function formatDiffReport(diff: MigrationDiff, { includeRiskSummary }?: {
3
+ includeRiskSummary?: boolean;
4
+ }): string;
5
+ export declare function renderMigrationRuleFile({ block, currentAttributes, currentTypeName, diff, fromVersion, projectDir, rulePath, targetVersion, }: MigrationRuleFileInput): string;
@@ -0,0 +1,120 @@
1
+ import path from "node:path";
2
+ import { MIGRATION_TODO_PREFIX } from "./migration-constants.js";
3
+ import { createMigrationRiskSummary, formatMigrationRiskSummary } from "./migration-risk.js";
4
+ import { normalizeImportPath } from "./migration-render-support.js";
5
+ import { escapeForCode, renderObjectKey } from "./migration-utils.js";
6
+ export function formatDiffReport(diff, { includeRiskSummary = true } = {}) {
7
+ const lines = [
8
+ `Migration diff: ${diff.fromVersion} -> ${diff.toVersion}`,
9
+ `Current type: ${diff.currentTypeName}`,
10
+ `Safe changes: ${diff.summary.auto}`,
11
+ `Manual changes: ${diff.summary.manual}`,
12
+ ];
13
+ if (diff.summary.autoItems.length > 0) {
14
+ lines.push("", "Safe changes:");
15
+ for (const item of diff.summary.autoItems) {
16
+ lines.push(` - ${item.path}: ${item.kind}${item.detail ? ` (${item.detail})` : ""}`);
17
+ }
18
+ }
19
+ if (diff.summary.manualItems.length > 0) {
20
+ lines.push("", "Manual review required:");
21
+ for (const item of diff.summary.manualItems) {
22
+ lines.push(` - ${item.path}: ${item.kind}${item.detail ? ` (${item.detail})` : ""}`);
23
+ }
24
+ }
25
+ if (diff.summary.renameCandidates.length > 0) {
26
+ const autoApplied = diff.summary.renameCandidates.filter((item) => item.autoApply);
27
+ const suggested = diff.summary.renameCandidates.filter((item) => !item.autoApply);
28
+ if (autoApplied.length > 0) {
29
+ lines.push("", "Auto-applied renames:");
30
+ for (const item of autoApplied) {
31
+ lines.push(` - ${item.currentPath} <- ${item.legacyPath} (${item.reason}, score ${item.score.toFixed(2)})`);
32
+ }
33
+ }
34
+ if (suggested.length > 0) {
35
+ lines.push("", "Suggested renames:");
36
+ for (const item of suggested) {
37
+ lines.push(` - ${item.currentPath} <- ${item.legacyPath} (${item.reason}, score ${item.score.toFixed(2)})`);
38
+ }
39
+ }
40
+ }
41
+ if (diff.summary.transformSuggestions.length > 0) {
42
+ lines.push("", "Suggested transforms:");
43
+ for (const item of diff.summary.transformSuggestions) {
44
+ lines.push(` - ${item.currentPath}${item.legacyPath ? ` <- ${item.legacyPath}` : ""} (${item.reason})`);
45
+ }
46
+ }
47
+ if (includeRiskSummary) {
48
+ lines.push("", `Risk summary: ${formatMigrationRiskSummary(createMigrationRiskSummary(diff))}`);
49
+ }
50
+ return lines.join("\n");
51
+ }
52
+ export function renderMigrationRuleFile({ block, currentAttributes, currentTypeName, diff, fromVersion, projectDir, rulePath, targetVersion, }) {
53
+ const activeRenameCandidates = diff.summary.renameCandidates.filter((candidate) => candidate.autoApply);
54
+ const suggestedRenameCandidates = diff.summary.renameCandidates.filter((candidate) => !candidate.autoApply);
55
+ const lines = [];
56
+ const ruleDir = path.dirname(rulePath);
57
+ const typesImport = normalizeImportPath(path.relative(ruleDir, path.join(projectDir, block.typesFile)));
58
+ const currentManifestImport = normalizeImportPath(path.relative(ruleDir, path.join(projectDir, block.manifestFile)));
59
+ const helpersImport = normalizeImportPath(path.relative(ruleDir, path.join(projectDir, "src", "migrations", "helpers.ts")), true);
60
+ lines.push(`import type { ${currentTypeName} } from "${typesImport}";`);
61
+ lines.push(`import currentManifest from "${currentManifestImport}";`);
62
+ lines.push(`import {`);
63
+ lines.push(`\ttype RenameMap,`);
64
+ lines.push(`\ttype TransformMap,`);
65
+ lines.push(`\tresolveMigrationAttribute,`);
66
+ lines.push(`} from "${helpersImport}";`);
67
+ lines.push("");
68
+ lines.push(`export const fromVersion = "${fromVersion}" as const;`);
69
+ lines.push(`export const toVersion = "${targetVersion}" as const;`);
70
+ lines.push("");
71
+ lines.push("export const renameMap: RenameMap = {");
72
+ for (const candidate of activeRenameCandidates) {
73
+ lines.push(`\t${renderObjectKey(candidate.currentPath)}: "${escapeForCode(candidate.legacyPath)}",`);
74
+ }
75
+ for (const candidate of suggestedRenameCandidates) {
76
+ lines.push(`\t// ${renderObjectKey(candidate.currentPath)}: "${escapeForCode(candidate.legacyPath)}",`);
77
+ }
78
+ lines.push("};");
79
+ lines.push("");
80
+ lines.push("export const transforms: TransformMap = {");
81
+ for (const suggestion of diff.summary.transformSuggestions) {
82
+ lines.push(`\t// ${renderObjectKey(suggestion.currentPath)}: (legacyValue, legacyInput) => {`);
83
+ for (const bodyLine of suggestion.bodyLines) {
84
+ lines.push(`\t${bodyLine}`);
85
+ }
86
+ lines.push(`\t// },`);
87
+ }
88
+ lines.push("};");
89
+ lines.push("");
90
+ lines.push("export const unresolved = [");
91
+ for (const item of diff.summary.manualItems) {
92
+ lines.push(`\t"${item.path}: ${item.kind}${item.detail ? ` (${escapeForCode(item.detail)})` : ""}",`);
93
+ }
94
+ for (const candidate of suggestedRenameCandidates) {
95
+ lines.push(`\t"${candidate.currentPath}: rename candidate from ${candidate.legacyPath}",`);
96
+ }
97
+ for (const suggestion of diff.summary.transformSuggestions) {
98
+ lines.push(`\t"${suggestion.currentPath}: transform suggested from ${suggestion.legacyPath ?? suggestion.currentPath}",`);
99
+ }
100
+ lines.push("] as const;");
101
+ lines.push("");
102
+ lines.push(`export function migrate(input: Record<string, unknown>): ${currentTypeName} {`);
103
+ lines.push(`\treturn {`);
104
+ for (const key of Object.keys(currentAttributes)) {
105
+ for (const manualItem of diff.summary.manualItems.filter((item) => item.path === key || item.path.startsWith(`${key}.`))) {
106
+ lines.push(`\t\t// ${MIGRATION_TODO_PREFIX} ${manualItem.path}: ${manualItem.kind}${manualItem.detail ? ` (${manualItem.detail})` : ""}`);
107
+ }
108
+ for (const renameCandidate of suggestedRenameCandidates.filter((item) => item.currentPath === key || item.currentPath.startsWith(`${key}.`))) {
109
+ lines.push(`\t\t// ${MIGRATION_TODO_PREFIX} consider renameMap[${JSON.stringify(renameCandidate.currentPath)}] = "${renameCandidate.legacyPath}"`);
110
+ }
111
+ for (const suggestion of diff.summary.transformSuggestions.filter((item) => item.currentPath === key || item.currentPath.startsWith(`${key}.`))) {
112
+ lines.push(`\t\t// ${MIGRATION_TODO_PREFIX} review transforms[${JSON.stringify(suggestion.currentPath)}]`);
113
+ }
114
+ lines.push(`\t\t${key}: resolveMigrationAttribute(currentManifest.attributes.${key}, "${key}", "${key}", input, renameMap, transforms),`);
115
+ }
116
+ lines.push(`\t} as ${currentTypeName};`);
117
+ lines.push("}");
118
+ lines.push("");
119
+ return `${lines.join("\n")}\n`;
120
+ }
@@ -0,0 +1,3 @@
1
+ import type { GeneratedMigrationEntry, MigrationEntry, MigrationProjectState } from "./migration-types.js";
2
+ export declare function renderVerifyFile(state: MigrationProjectState, blockKey: string, entries: MigrationEntry[]): string;
3
+ export declare function renderFuzzFile(state: MigrationProjectState, blockKey: string, entries: GeneratedMigrationEntry[]): string;
@@ -0,0 +1,428 @@
1
+ export function renderVerifyFile(state, blockKey, entries) {
2
+ const block = state.blocks.find((entry) => entry.key === blockKey);
3
+ if (!block) {
4
+ throw new Error(`Unknown migration block target: ${blockKey}`);
5
+ }
6
+ if (entries.length === 0) {
7
+ return `/* eslint-disable no-console */
8
+ console.log(
9
+ \t'Run \`wp-typia migrate scaffold --from-migration-version <label>\` before verify.'
10
+ );
11
+ `;
12
+ }
13
+ const imports = [
14
+ `import { validators } from "${entries[0]?.validatorImport ?? "./validators"}";`,
15
+ `import { deprecated } from "./deprecated";`,
16
+ ];
17
+ const checks = [];
18
+ entries.forEach((entry, index) => {
19
+ imports.push(`import fixture_${index} from "${entry.fixtureImport}";`);
20
+ imports.push(`import * as rule_${index} from "${entry.ruleImport}";`);
21
+ checks.push(`\tif (selectedMigrationVersions.length === 0 || selectedMigrationVersions.includes("${entry.fromVersion}")) {`);
22
+ checks.push(`\t\tif (rule_${index}.unresolved.length > 0) {`);
23
+ checks.push(`\t\t\tthrow new Error("Unresolved migration TODOs remain for ${entry.fromVersion} -> ${entry.toVersion}: " + rule_${index}.unresolved.join(", "));`);
24
+ checks.push(`\t\t}`);
25
+ checks.push(`\t\tconst cases_${index} = Array.isArray(fixture_${index}.cases) ? fixture_${index}.cases : [];`);
26
+ checks.push(`\t\tfor (const fixtureCase of cases_${index}) {`);
27
+ checks.push(`\t\t\tconst migrated_${index} = rule_${index}.migrate(fixtureCase.input ?? {});`);
28
+ checks.push(`\t\t\tconst validation_${index} = validators.validate(migrated_${index});`);
29
+ checks.push(`\t\t\tif (!isValidationSuccess(validation_${index})) {`);
30
+ checks.push(`\t\t\t\tthrow new Error("Current validator rejected migrated fixture for ${entry.fromVersion} case " + String(fixtureCase.name ?? "unknown") + ": " + JSON.stringify(getValidationErrors(validation_${index})));`);
31
+ checks.push(`\t\t\t}`);
32
+ checks.push(`\t\t}`);
33
+ checks.push(`\t\tconsole.log("Verified ${entry.fromVersion} -> ${entry.toVersion} (" + cases_${index}.length + " case(s))");`);
34
+ checks.push(`\t}`);
35
+ });
36
+ return `/* eslint-disable prettier/prettier, no-console, @typescript-eslint/no-unused-vars, no-nested-ternary */
37
+ ${imports.join("\n")}
38
+
39
+ function isValidationSuccess(result: unknown): boolean {
40
+ \treturn (
41
+ \t\tresult !== null &&
42
+ \t\ttypeof result === "object" &&
43
+ \t\t(
44
+ \t\t\t(result as { isValid?: unknown }).isValid === true ||
45
+ \t\t\t(result as { success?: unknown }).success === true
46
+ \t\t)
47
+ \t);
48
+ }
49
+
50
+ function getValidationErrors(result: unknown): unknown[] {
51
+ \tif (result !== null && typeof result === "object" && Array.isArray((result as { errors?: unknown[] }).errors)) {
52
+ \t\treturn (result as { errors: unknown[] }).errors;
53
+ \t}
54
+
55
+ \treturn [];
56
+ }
57
+
58
+ const args = process.argv.slice(2);
59
+ const selectedMigrationVersions =
60
+ \targs[0] === "--all"
61
+ \t\t? []
62
+ \t\t: args[0] === "--from-migration-version" && args[1]
63
+ \t\t\t? [args[1]]
64
+ \t\t\t: [];
65
+
66
+ if (deprecated.length !== ${entries.length}) {
67
+ \tthrow new Error("Generated deprecated entries are out of sync with migration registry.");
68
+ }
69
+
70
+ ${checks.join("\n")}
71
+
72
+ console.log("Migration verification passed for ${block.blockName}");
73
+ `;
74
+ }
75
+ export function renderFuzzFile(state, blockKey, entries) {
76
+ const block = state.blocks.find((entry) => entry.key === blockKey);
77
+ if (!block) {
78
+ throw new Error(`Unknown migration block target: ${blockKey}`);
79
+ }
80
+ if (entries.length === 0) {
81
+ return `/* eslint-disable no-console */
82
+ console.log(
83
+ \t'Run \`wp-typia migrate scaffold --from-migration-version <label>\` before fuzz.'
84
+ );
85
+ `;
86
+ }
87
+ const imports = [
88
+ `import { validators } from "${entries[0]?.entry.validatorImport ?? "./validators"}";`,
89
+ ];
90
+ const edgeDefinitions = [];
91
+ entries.forEach(({ entry, fuzzPlan }, index) => {
92
+ imports.push(`import fixture_${index} from "${entry.fixtureImport}";`);
93
+ imports.push(`import manifest_${index} from "${entry.manifestImport}";`);
94
+ imports.push(`import * as rule_${index} from "${entry.ruleImport}";`);
95
+ edgeDefinitions.push(`{
96
+ \tfromMigrationVersion: "${entry.fromVersion}",
97
+ \ttoMigrationVersion: "${entry.toVersion}",
98
+ \tfixture: fixture_${index},
99
+ \tlegacyManifest: manifest_${index},
100
+ \trule: rule_${index},
101
+ \tplan: ${JSON.stringify(fuzzPlan, null, "\t").replace(/\n/g, "\n\t")},
102
+ }`);
103
+ });
104
+ return `/* eslint-disable prettier/prettier, no-console, no-bitwise, @typescript-eslint/no-unused-vars, no-nested-ternary, @typescript-eslint/method-signature-style */
105
+ ${imports.join("\n")}
106
+
107
+ type JsonValue = string | number | boolean | null | JsonValue[] | { [key: string]: JsonValue };
108
+
109
+ type ManifestAttribute = {
110
+ \ttypia?: {
111
+ \t\tdefaultValue?: JsonValue | null;
112
+ \t\thasDefault?: boolean;
113
+ \t};
114
+ \tts?: {
115
+ \t\titems?: ManifestAttribute | null;
116
+ \t\tkind?: string | null;
117
+ \t\tproperties?: Record<string, ManifestAttribute> | null;
118
+ \t\tunion?: {
119
+ \t\t\tbranches?: Record<string, ManifestAttribute> | null;
120
+ \t\t\tdiscriminator?: string | null;
121
+ \t\t} | null;
122
+ \t} | null;
123
+ \twp?: {
124
+ \t\tdefaultValue?: JsonValue | null;
125
+ \t\tenum?: JsonValue[] | null;
126
+ \t\thasDefault?: boolean;
127
+ \t} | null;
128
+ };
129
+
130
+ type ManifestDocument = {
131
+ \tattributes?: Record<string, ManifestAttribute>;
132
+ };
133
+
134
+ type FixtureDocument = {
135
+ \tcases?: Array<{ input?: Record<string, unknown>; name?: string }>;
136
+ };
137
+
138
+ type FuzzEdge = {
139
+ \tfixture: FixtureDocument;
140
+ \tfromMigrationVersion: string;
141
+ \tlegacyManifest: ManifestDocument;
142
+ \tplan: {
143
+ \t\tblockedPaths: string[];
144
+ \t\tcompatibleMappings: Array<{ currentPath: string; legacyPath: string }>;
145
+ \t};
146
+ \trule: {
147
+ \t\tmigrate(input: Record<string, unknown>): Record<string, unknown>;
148
+ \t};
149
+ \ttoMigrationVersion: string;
150
+ };
151
+
152
+ const edges: FuzzEdge[] = [
153
+ ${edgeDefinitions.join(",\n")}
154
+ ];
155
+
156
+ function cloneJsonValue<T>(value: T): T {
157
+ \treturn JSON.parse(JSON.stringify(value));
158
+ }
159
+
160
+ function getValueAtPath(input: Record<string, unknown>, pathLabel: string): unknown {
161
+ \treturn String(pathLabel)
162
+ \t\t.split(".")
163
+ \t\t.reduce<unknown>(
164
+ \t\t\t(value, segment) => (value && typeof value === "object" ? (value as Record<string, unknown>)[segment] : undefined),
165
+ \t\t\tinput,
166
+ \t\t);
167
+ }
168
+
169
+ function setValueAtPath(input: Record<string, unknown>, pathLabel: string, value: unknown): void {
170
+ \tconst segments = String(pathLabel).split(".").filter(Boolean);
171
+ \tif (segments.length === 0) {
172
+ \t\treturn;
173
+ \t}
174
+
175
+ \tlet target: Record<string, unknown> = input;
176
+ \twhile (segments.length > 1) {
177
+ \t\tconst segment = segments.shift();
178
+ \t\tif (!segment) {
179
+ \t\t\tcontinue;
180
+ \t\t}
181
+ \t\tif (!target[segment] || typeof target[segment] !== "object" || Array.isArray(target[segment])) {
182
+ \t\t\ttarget[segment] = {};
183
+ \t\t}
184
+ \t\ttarget = target[segment];
185
+ \t}
186
+
187
+ \ttarget[segments[0]!] = value;
188
+ }
189
+
190
+ function createDefaultValue(attribute: ManifestAttribute): JsonValue | Record<string, JsonValue> | null {
191
+ \tif (attribute?.typia?.hasDefault) {
192
+ \t\treturn cloneJsonValue(attribute.typia.defaultValue ?? null);
193
+ \t}
194
+ \tif (attribute?.wp?.hasDefault) {
195
+ \t\treturn cloneJsonValue(attribute.wp.defaultValue ?? null);
196
+ \t}
197
+ \tif (Array.isArray(attribute?.wp?.enum) && attribute.wp.enum.length > 0) {
198
+ \t\treturn cloneJsonValue(attribute.wp.enum[0] ?? null);
199
+ \t}
200
+
201
+ \tswitch (attribute?.ts?.kind) {
202
+ \t\tcase "string":
203
+ \t\t\treturn "";
204
+ \t\tcase "number":
205
+ \t\t\treturn 0;
206
+ \t\tcase "boolean":
207
+ \t\t\treturn false;
208
+ \t\tcase "array":
209
+ \t\t\treturn [];
210
+ \t\tcase "object":
211
+ \t\t\treturn Object.fromEntries(
212
+ \t\t\t\tObject.entries(attribute?.ts?.properties ?? {}).map(([key, property]) => [
213
+ \t\t\t\t\tkey,
214
+ \t\t\t\t\tcreateDefaultValue(property),
215
+ \t\t\t\t]),
216
+ \t\t\t);
217
+ \t\tcase "union": {
218
+ \t\t\tconst firstBranch = Object.values(attribute?.ts?.union?.branches ?? {})[0];
219
+ \t\t\treturn firstBranch ? createDefaultValue(firstBranch) : null;
220
+ \t\t}
221
+ \t\tdefault:
222
+ \t\t\treturn null;
223
+ \t}
224
+ }
225
+
226
+ function createDefaultInput(manifest: ManifestDocument): Record<string, unknown> {
227
+ \treturn Object.fromEntries(
228
+ \t\tObject.entries(manifest?.attributes ?? {}).map(([key, attribute]) => [key, createDefaultValue(attribute)]),
229
+ \t);
230
+ }
231
+
232
+ function isValidationSuccess(result: unknown): boolean {
233
+ \tconst typedResult =
234
+ \t\tresult !== null && typeof result === "object"
235
+ \t\t\t? (result as { isValid?: unknown; success?: unknown })
236
+ \t\t\t: null;
237
+
238
+ \treturn (
239
+ \t\ttypedResult !== null &&
240
+ \t\t(
241
+ \t\t\ttypedResult.isValid === true ||
242
+ \t\t\ttypedResult.success === true
243
+ \t\t)
244
+ \t);
245
+ }
246
+
247
+ function getValidationErrors(result: unknown): unknown[] {
248
+ \tif (
249
+ \t\tresult !== null &&
250
+ \t\ttypeof result === "object" &&
251
+ \t\tArray.isArray((result as { errors?: unknown[] }).errors)
252
+ \t) {
253
+ \t\treturn (result as { errors: unknown[] }).errors;
254
+ \t}
255
+
256
+ \treturn [];
257
+ }
258
+
259
+ function createSeededRandom(seed: number): () => number {
260
+ \tlet state = seed >>> 0;
261
+ \treturn () => {
262
+ \t\tstate = (state * 1664525 + 1013904223) >>> 0;
263
+ \t\treturn state / 4294967296;
264
+ \t};
265
+ }
266
+
267
+ function withSeededRandom<T>(seed: number, callback: () => T): T {
268
+ \tconst originalRandom = Math.random;
269
+ \tMath.random = createSeededRandom(seed);
270
+ \ttry {
271
+ \t\treturn callback();
272
+ \t} finally {
273
+ \t\tMath.random = originalRandom;
274
+ \t}
275
+ }
276
+
277
+ function parseArgs(argv: string[]): {
278
+ \tall: boolean;
279
+ \tfromMigrationVersion?: string;
280
+ \titerations: number;
281
+ \tseed?: number;
282
+ } {
283
+ \tlet all = false;
284
+ \tlet fromMigrationVersion: string | undefined;
285
+ \tlet iterations = 25;
286
+ \tlet seed: number | undefined;
287
+
288
+ \tfor (let index = 0; index < argv.length; index += 1) {
289
+ \t\tconst arg = argv[index];
290
+ \t\tconst next = argv[index + 1];
291
+
292
+ \t\tif (arg === "--all") {
293
+ \t\t\tall = true;
294
+ \t\t\tcontinue;
295
+ \t\t}
296
+ \t\tif (arg === "--from-migration-version") {
297
+ \t\t\tfromMigrationVersion = next;
298
+ \t\t\tindex += 1;
299
+ \t\t\tcontinue;
300
+ \t\t}
301
+ \t\tif (arg === "--iterations") {
302
+ \t\t\titerations = Number.parseInt(next ?? "", 10) || 25;
303
+ \t\t\tindex += 1;
304
+ \t\t\tcontinue;
305
+ \t\t}
306
+ \t\tif (arg === "--seed") {
307
+ \t\t\tseed = Number.parseInt(next ?? "", 10);
308
+ \t\t\tindex += 1;
309
+ \t\t}
310
+ \t}
311
+
312
+ \treturn { all, fromMigrationVersion, iterations, seed };
313
+ }
314
+
315
+ function applyCompatibleMappings(
316
+ \tbaseInput: Record<string, unknown>,
317
+ \tcurrentSample: Record<string, unknown>,
318
+ \tmappings: Array<{ currentPath: string; legacyPath: string }>,
319
+ ): Record<string, unknown> {
320
+ \tfor (const mapping of mappings) {
321
+ \t\tconst value = getValueAtPath(currentSample, mapping.currentPath);
322
+ \t\tif (value !== undefined) {
323
+ \t\t\tsetValueAtPath(baseInput, mapping.legacyPath, cloneJsonValue(value));
324
+ \t\t}
325
+ \t}
326
+ \treturn baseInput;
327
+ }
328
+
329
+ function assertValidMigration(
330
+ \tedge: FuzzEdge,
331
+ \tcandidateInput: Record<string, unknown>,
332
+ \tmigratedOutput: Record<string, unknown>,
333
+ \tvalidation: unknown,
334
+ \titerationSeed: number,
335
+ \titeration: number | string,
336
+ ): void {
337
+ \tif (isValidationSuccess(validation)) {
338
+ \t\treturn;
339
+ \t}
340
+
341
+ \tthrow new Error(
342
+ \t\t"Migration fuzz failed for " +
343
+ \t\t\tedge.fromMigrationVersion +
344
+ \t\t\t" -> " +
345
+ \t\t\tedge.toMigrationVersion +
346
+ \t\t\t" (seed " +
347
+ \t\t\tString(iterationSeed) +
348
+ \t\t\t", iteration " +
349
+ \t\t\tString(iteration) +
350
+ \t\t\t"): " +
351
+ \t\t\tJSON.stringify({
352
+ \t\t\t\tinput: candidateInput,
353
+ \t\t\t\tmigrated: migratedOutput,
354
+ \t\t\t\terrors: getValidationErrors(validation),
355
+ \t\t\t}),
356
+ \t\t);
357
+ }
358
+
359
+ const parsed = parseArgs(process.argv.slice(2));
360
+ const selectedMigrationVersions = parsed.all
361
+ \t? []
362
+ \t: parsed.fromMigrationVersion
363
+ \t\t? [parsed.fromMigrationVersion]
364
+ \t\t: edges.map((edge) => edge.fromMigrationVersion);
365
+ const baseSeed = typeof parsed.seed === "number" ? parsed.seed : Date.now();
366
+
367
+ if (!Number.isInteger(parsed.seed)) {
368
+ \tconsole.log("Using migration fuzz seed " + String(baseSeed));
369
+ }
370
+
371
+ if (edges.length === 0) {
372
+ \tconsole.log("No legacy migration versions configured for migration fuzzing.");
373
+ \tprocess.exit(0);
374
+ }
375
+
376
+ let executedEdges = 0;
377
+
378
+ for (const [edgeIndex, edge] of edges.entries()) {
379
+ \tif (selectedMigrationVersions.length > 0 && !selectedMigrationVersions.includes(edge.fromMigrationVersion)) {
380
+ \t\tcontinue;
381
+ \t}
382
+
383
+ \texecutedEdges += 1;
384
+
385
+ \tconst fixtureCases = Array.isArray(edge.fixture?.cases) ? edge.fixture.cases : [];
386
+ \tfor (const fixtureCase of fixtureCases) {
387
+ \t\tconst migrated = edge.rule.migrate(fixtureCase.input ?? {});
388
+ \t\tconst validation = validators.validate(migrated);
389
+ \t\tassertValidMigration(edge, fixtureCase.input ?? {}, migrated, validation, baseSeed, fixtureCase.name ?? "fixture");
390
+ \t}
391
+
392
+ \tfor (let iteration = 0; iteration < parsed.iterations; iteration += 1) {
393
+ \t\tconst iterationSeed = (baseSeed + edgeIndex * 100003 + iteration) >>> 0;
394
+ \t\tconst currentSample = withSeededRandom(iterationSeed, () => validators.random());
395
+ \t\tconst baseFixture = fixtureCases.find((fixtureCase) => fixtureCase.name === "default")?.input;
396
+ \t\tconst legacyInput = applyCompatibleMappings(
397
+ \t\t\tcloneJsonValue(baseFixture ?? createDefaultInput(edge.legacyManifest)),
398
+ \t\t\tcurrentSample,
399
+ \t\t\tedge.plan.compatibleMappings,
400
+ \t\t);
401
+ \t\tconst migrated = edge.rule.migrate(legacyInput);
402
+ \t\tconst validation = validators.validate(migrated);
403
+ \t\tassertValidMigration(edge, legacyInput, migrated, validation, iterationSeed, iteration);
404
+ \t}
405
+
406
+ \tconsole.log(
407
+ \t\t"Fuzzed " +
408
+ \t\t\tedge.fromMigrationVersion +
409
+ \t\t\t" -> " +
410
+ \t\t\tedge.toMigrationVersion +
411
+ \t\t\t" (" +
412
+ \t\t\tString(fixtureCases.length) +
413
+ \t\t\t" fixture case(s), " +
414
+ \t\t\tString(parsed.iterations) +
415
+ \t\t\t" fuzz iteration(s))",
416
+ \t);
417
+ }
418
+
419
+ if (selectedMigrationVersions.length > 0 && executedEdges === 0) {
420
+ \tthrow new Error(
421
+ \t\t"Requested migration version was not exercised by fuzz: " +
422
+ \t\t\tselectedMigrationVersions.join(", "),
423
+ \t);
424
+ }
425
+
426
+ console.log("Migration fuzzing passed for ${block.blockName}");
427
+ `;
428
+ }
@@ -0,0 +1,27 @@
1
+ import type { GeneratedMigrationEntry, MigrationEntry, MigrationProjectState } from "./migration-types.js";
2
+ /**
3
+ * Renders the generated migration registry module for a block target.
4
+ *
5
+ * Prefers manifest wrapper modules when they are available in the project,
6
+ * while still validating the imported manifest before the registry consumes it.
7
+ *
8
+ * @param state The resolved migration project state.
9
+ * @param blockKey The stable key for the block whose registry is being generated.
10
+ * @param entries The generated migration entries to include in the registry.
11
+ * @returns The generated TypeScript source code for the migration registry file.
12
+ */
13
+ export declare function renderMigrationRegistryFile(state: MigrationProjectState, blockKey: string, entries: GeneratedMigrationEntry[]): string;
14
+ /**
15
+ * Renders the generated deprecated module for a block target.
16
+ *
17
+ * The emitted module exposes the ordered deprecation array consumed by block
18
+ * registration and migration helpers.
19
+ *
20
+ * @param state The resolved migration project state.
21
+ * @param blockKey The stable key for the block whose deprecated entries are being generated.
22
+ * @param entries The migration entries that define deprecated manifest versions.
23
+ * @returns The generated TypeScript source code for the deprecated module.
24
+ */
25
+ export declare function renderGeneratedDeprecatedFile(state: MigrationProjectState, blockKey: string, entries: MigrationEntry[]): string;
26
+ export declare function renderGeneratedMigrationIndexFile(state: MigrationProjectState, entries: MigrationEntry[]): string;
27
+ export declare function renderPhpMigrationRegistryFile(state: MigrationProjectState, entries: MigrationEntry[]): string;