@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
@@ -0,0 +1,189 @@
1
+ import { formatLegacyMigrationWorkspaceResetGuidance } from './migration-utils.js';
2
+ /**
3
+ * Returns the formatted help text for migration CLI commands and flags.
4
+ *
5
+ * @returns Multi-line usage text for the `wp-typia migrate` command surface.
6
+ */
7
+ export function formatMigrationHelpText() {
8
+ return `Usage:
9
+ wp-typia migrate init --current-migration-version <label>
10
+ wp-typia migrate snapshot --migration-version <label>
11
+ wp-typia migrate plan --from-migration-version <label> [--to-migration-version current]
12
+ wp-typia migrate wizard
13
+ wp-typia migrate diff --from-migration-version <label> [--to-migration-version current]
14
+ wp-typia migrate scaffold --from-migration-version <label> [--to-migration-version current]
15
+ wp-typia migrate verify [--from-migration-version <label>|--all]
16
+ wp-typia migrate doctor [--from-migration-version <label>|--all]
17
+ wp-typia migrate fixtures [--from-migration-version <label>|--all] [--to-migration-version current] [--force]
18
+ wp-typia migrate fuzz [--from-migration-version <label>|--all] [--iterations <n>] [--seed <n>]
19
+
20
+ Notes:
21
+ \`migrate init\` auto-detects supported single-block and \`src/blocks/*\` multi-block layouts.
22
+ Migration versions use strict schema labels like \`v1\`, \`v2\`, and \`v3\`.
23
+ \`migrate wizard\` is TTY-only and helps you choose one legacy migration version to preview.
24
+ \`migrate plan\` and \`migrate wizard\` are read-only previews; they do not scaffold rules or fixtures.
25
+ --all runs across every configured legacy migration version and every configured block target.
26
+ Existing fixture files are preserved and reported as skipped unless you pass \`--force\`.
27
+ Use \`migrate fixtures --force\` as the explicit refresh path for generated fixture files.
28
+ In TTY usage, \`migrate fixtures --force\` asks before overwriting existing fixture files.
29
+ In non-interactive usage, \`migrate fixtures --force\` overwrites immediately for script compatibility.`;
30
+ }
31
+ /**
32
+ * Parses migration CLI arguments into a structured command payload.
33
+ *
34
+ * @param argv Command-line arguments that follow the `migrate` subcommand.
35
+ * @returns Parsed migration command and normalized flags for runtime dispatch.
36
+ * @throws Error When no arguments are provided, an unknown flag is encountered, or legacy semver flags are used.
37
+ */
38
+ export function parseMigrationArgs(argv) {
39
+ const parsed = {
40
+ command: undefined,
41
+ flags: {
42
+ all: false,
43
+ currentMigrationVersion: undefined,
44
+ force: false,
45
+ fromMigrationVersion: undefined,
46
+ iterations: undefined,
47
+ migrationVersion: undefined,
48
+ seed: undefined,
49
+ toMigrationVersion: 'current',
50
+ },
51
+ };
52
+ if (argv.length === 0) {
53
+ throw new Error(formatMigrationHelpText());
54
+ }
55
+ parsed.command = argv[0];
56
+ for (let index = 1; index < argv.length; index += 1) {
57
+ const arg = argv[index];
58
+ const next = argv[index + 1];
59
+ if (arg === '--')
60
+ continue;
61
+ if (arg === '--all') {
62
+ parsed.flags.all = true;
63
+ continue;
64
+ }
65
+ if (arg === '--force') {
66
+ parsed.flags.force = true;
67
+ continue;
68
+ }
69
+ if (arg === '--current-migration-version') {
70
+ parsed.flags.currentMigrationVersion = next;
71
+ index += 1;
72
+ continue;
73
+ }
74
+ if (arg.startsWith('--current-migration-version=')) {
75
+ parsed.flags.currentMigrationVersion = arg.split('=', 2)[1];
76
+ continue;
77
+ }
78
+ if (arg === '--from-migration-version') {
79
+ parsed.flags.fromMigrationVersion = next;
80
+ index += 1;
81
+ continue;
82
+ }
83
+ if (arg.startsWith('--from-migration-version=')) {
84
+ parsed.flags.fromMigrationVersion = arg.split('=', 2)[1];
85
+ continue;
86
+ }
87
+ if (arg === '--iterations') {
88
+ parsed.flags.iterations = next;
89
+ index += 1;
90
+ continue;
91
+ }
92
+ if (arg.startsWith('--iterations=')) {
93
+ parsed.flags.iterations = arg.split('=', 2)[1];
94
+ continue;
95
+ }
96
+ if (arg === '--seed') {
97
+ parsed.flags.seed = next;
98
+ index += 1;
99
+ continue;
100
+ }
101
+ if (arg.startsWith('--seed=')) {
102
+ parsed.flags.seed = arg.split('=', 2)[1];
103
+ continue;
104
+ }
105
+ if (arg === '--to-migration-version') {
106
+ parsed.flags.toMigrationVersion = next;
107
+ index += 1;
108
+ continue;
109
+ }
110
+ if (arg.startsWith('--to-migration-version=')) {
111
+ parsed.flags.toMigrationVersion = arg.split('=', 2)[1];
112
+ continue;
113
+ }
114
+ if (arg === '--migration-version') {
115
+ parsed.flags.migrationVersion = next;
116
+ index += 1;
117
+ continue;
118
+ }
119
+ if (arg.startsWith('--migration-version=')) {
120
+ parsed.flags.migrationVersion = arg.split('=', 2)[1];
121
+ continue;
122
+ }
123
+ if (arg === '--current-version' ||
124
+ arg.startsWith('--current-version=') ||
125
+ arg === '--version' ||
126
+ arg.startsWith('--version=') ||
127
+ arg === '--from' ||
128
+ arg.startsWith('--from=') ||
129
+ arg === '--to' ||
130
+ arg.startsWith('--to=')) {
131
+ throwLegacyMigrationFlagError(arg);
132
+ }
133
+ throw new Error(`Unknown migration flag: ${arg}`);
134
+ }
135
+ return parsed;
136
+ }
137
+ /**
138
+ * Parse an optional positive integer flag value.
139
+ *
140
+ * @param value Raw CLI flag value, or `undefined` when the flag was omitted.
141
+ * @param label Human-readable flag label used in validation error messages.
142
+ * @returns The parsed integer when provided, otherwise `undefined`.
143
+ * @throws Error When the value is not a base-10 integer greater than zero.
144
+ */
145
+ export function parsePositiveInteger(value, label) {
146
+ if (!value) {
147
+ return undefined;
148
+ }
149
+ if (!/^\d+$/.test(value)) {
150
+ throw new Error(`Invalid ${label}: ${value}. Expected a positive integer.`);
151
+ }
152
+ const parsed = Number.parseInt(value, 10);
153
+ if (!Number.isInteger(parsed) || parsed <= 0) {
154
+ throw new Error(`Invalid ${label}: ${value}. Expected a positive integer.`);
155
+ }
156
+ return parsed;
157
+ }
158
+ /**
159
+ * Parse an optional non-negative integer flag value.
160
+ *
161
+ * @param value Raw CLI flag value, or `undefined` when the flag was omitted.
162
+ * @param label Human-readable flag label used in validation error messages.
163
+ * @returns The parsed integer when provided, otherwise `undefined`.
164
+ * @throws Error When the value is not a base-10 integer greater than or equal to zero.
165
+ */
166
+ export function parseNonNegativeInteger(value, label) {
167
+ if (!value) {
168
+ return undefined;
169
+ }
170
+ if (!/^\d+$/.test(value)) {
171
+ throw new Error(`Invalid ${label}: ${value}. Expected a non-negative integer.`);
172
+ }
173
+ const parsed = Number.parseInt(value, 10);
174
+ if (!Number.isInteger(parsed) || parsed < 0) {
175
+ throw new Error(`Invalid ${label}: ${value}. Expected a non-negative integer.`);
176
+ }
177
+ return parsed;
178
+ }
179
+ function throwLegacyMigrationFlagError(flag) {
180
+ const replacement = flag.startsWith('--current-version')
181
+ ? '--current-migration-version'
182
+ : flag.startsWith('--version')
183
+ ? '--migration-version'
184
+ : flag.startsWith('--from')
185
+ ? '--from-migration-version'
186
+ : '--to-migration-version';
187
+ throw new Error(`Legacy migration flag \`${flag}\` is no longer supported. Use \`${replacement}\` with schema labels like \`v1\` and \`v2\` instead. ` +
188
+ formatLegacyMigrationWorkspaceResetGuidance());
189
+ }
@@ -0,0 +1,13 @@
1
+ import type { FlattenedAttributeDescriptor, ManifestAttribute, RenameCandidate } from "./migration-types.js";
2
+ interface CreateRenameCandidatesOptions {
3
+ addedKeys: string[];
4
+ isUnionRenameCompatible: (oldAttribute: ManifestAttribute, newAttribute: ManifestAttribute) => boolean;
5
+ newAttributes: Record<string, ManifestAttribute>;
6
+ newLeafAttributes: FlattenedAttributeDescriptor[];
7
+ oldAttributes: Record<string, ManifestAttribute>;
8
+ oldLeafAttributes: FlattenedAttributeDescriptor[];
9
+ removedKeys: string[];
10
+ }
11
+ export declare function createRenameCandidates({ addedKeys, isUnionRenameCompatible, newAttributes, newLeafAttributes, oldAttributes, oldLeafAttributes, removedKeys, }: CreateRenameCandidatesOptions): RenameCandidate[];
12
+ export declare function passesNameSimilarityRule(legacyPath: string, currentPath: string): boolean;
13
+ export {};
@@ -0,0 +1,192 @@
1
+ export function createRenameCandidates({ addedKeys, isUnionRenameCompatible, newAttributes, newLeafAttributes, oldAttributes, oldLeafAttributes, removedKeys, }) {
2
+ const assessments = [];
3
+ for (const currentPath of addedKeys) {
4
+ const nextAttribute = newAttributes[currentPath];
5
+ if (!nextAttribute)
6
+ continue;
7
+ for (const legacyPath of removedKeys) {
8
+ const previous = oldAttributes[legacyPath];
9
+ if (!previous)
10
+ continue;
11
+ const candidate = assessRenameCandidate(previous, nextAttribute, legacyPath, currentPath, isUnionRenameCompatible);
12
+ if (candidate) {
13
+ assessments.push(candidate);
14
+ }
15
+ }
16
+ }
17
+ const oldLeafMap = new Map(oldLeafAttributes.map((descriptor) => [descriptor.currentPath, descriptor]));
18
+ const newLeafMap = new Map(newLeafAttributes.map((descriptor) => [descriptor.currentPath, descriptor]));
19
+ const removedLeafDescriptors = oldLeafAttributes.filter((descriptor) => !newLeafMap.has(descriptor.currentPath));
20
+ const addedLeafDescriptors = newLeafAttributes.filter((descriptor) => !oldLeafMap.has(descriptor.currentPath));
21
+ for (const nextDescriptor of addedLeafDescriptors) {
22
+ if (!nextDescriptor.currentPath.includes(".")) {
23
+ continue;
24
+ }
25
+ for (const previousDescriptor of removedLeafDescriptors) {
26
+ if (!previousDescriptor.currentPath.includes(".")) {
27
+ continue;
28
+ }
29
+ const candidate = assessRenameCandidate(previousDescriptor.attribute, nextDescriptor.attribute, previousDescriptor.currentPath, nextDescriptor.currentPath, isUnionRenameCompatible);
30
+ if (candidate) {
31
+ assessments.push(candidate);
32
+ }
33
+ }
34
+ }
35
+ return assessments
36
+ .map((candidate) => {
37
+ const currentMatches = assessments
38
+ .filter((item) => item.currentPath === candidate.currentPath)
39
+ .sort((left, right) => right.score - left.score);
40
+ const legacyMatches = assessments
41
+ .filter((item) => item.legacyPath === candidate.legacyPath)
42
+ .sort((left, right) => right.score - left.score);
43
+ const currentLeader = currentMatches[0];
44
+ const legacyLeader = legacyMatches[0];
45
+ const currentHasTie = currentMatches.length > 1 && Math.abs((currentMatches[1]?.score ?? 0) - currentLeader.score) < 0.05;
46
+ const legacyHasTie = legacyMatches.length > 1 && Math.abs((legacyMatches[1]?.score ?? 0) - legacyLeader.score) < 0.05;
47
+ return {
48
+ ...candidate,
49
+ autoApply: currentLeader.legacyPath === candidate.legacyPath &&
50
+ legacyLeader.currentPath === candidate.currentPath &&
51
+ !currentHasTie &&
52
+ !legacyHasTie &&
53
+ candidate.score >= 0.6,
54
+ };
55
+ })
56
+ .filter((candidate, index, list) => {
57
+ const firstMatch = list.findIndex((item) => item.currentPath === candidate.currentPath && item.legacyPath === candidate.legacyPath);
58
+ return firstMatch === index;
59
+ })
60
+ .sort((left, right) => right.score - left.score);
61
+ }
62
+ export function passesNameSimilarityRule(legacyPath, currentPath) {
63
+ return scoreRenameSimilarity(legacyPath, currentPath) >= 0.6;
64
+ }
65
+ function isRenameCandidateShapeCompatible(oldAttribute, newAttribute, isUnionRenameCompatible) {
66
+ if (!oldAttribute || !newAttribute || oldAttribute.ts.kind !== newAttribute.ts.kind) {
67
+ return false;
68
+ }
69
+ if (["string", "number", "boolean"].includes(oldAttribute.ts.kind)) {
70
+ return hasRenameCompatibleConstraints(oldAttribute, newAttribute);
71
+ }
72
+ if (oldAttribute.ts.kind === "union") {
73
+ return isUnionRenameCompatible(oldAttribute, newAttribute);
74
+ }
75
+ return false;
76
+ }
77
+ function assessRenameCandidate(oldAttribute, newAttribute, legacyPath, currentPath, isUnionRenameCompatible) {
78
+ if (!isRenameCandidateShapeCompatible(oldAttribute, newAttribute, isUnionRenameCompatible)) {
79
+ return null;
80
+ }
81
+ const baseScore = scoreRenameSimilarity(legacyPath, currentPath);
82
+ const score = getParentPath(legacyPath) === getParentPath(currentPath) ? Math.max(baseScore, 0.75) : baseScore;
83
+ return {
84
+ autoApply: false,
85
+ currentPath,
86
+ legacyPath,
87
+ reason: describeRenameReason(oldAttribute, legacyPath, currentPath, score),
88
+ score,
89
+ };
90
+ }
91
+ function hasRenameCompatibleConstraints(oldAttribute, newAttribute) {
92
+ const oldEnum = oldAttribute.wp.enum ?? null;
93
+ const nextEnum = newAttribute.wp.enum ?? null;
94
+ if (oldEnum && nextEnum) {
95
+ const oldIsSubset = oldEnum.every((value) => nextEnum.includes(value));
96
+ if (!oldIsSubset) {
97
+ return false;
98
+ }
99
+ }
100
+ else if (oldEnum && !nextEnum) {
101
+ return false;
102
+ }
103
+ const oldConstraints = oldAttribute.typia.constraints ?? {};
104
+ const nextConstraints = newAttribute.typia.constraints ?? {};
105
+ return [
106
+ compareMinimumBound(oldConstraints.minLength, nextConstraints.minLength),
107
+ compareMaximumBound(oldConstraints.maxLength, nextConstraints.maxLength),
108
+ compareMinimumBound(oldConstraints.minimum, nextConstraints.minimum),
109
+ compareMaximumBound(oldConstraints.maximum, nextConstraints.maximum),
110
+ comparePatternBound(oldConstraints.pattern, nextConstraints.pattern),
111
+ comparePatternBound(oldConstraints.format, nextConstraints.format),
112
+ comparePatternBound(oldConstraints.typeTag, nextConstraints.typeTag),
113
+ ].every(Boolean);
114
+ }
115
+ function compareMinimumBound(oldValue, nextValue) {
116
+ if (nextValue === null || nextValue === undefined)
117
+ return true;
118
+ if (oldValue === null || oldValue === undefined)
119
+ return true;
120
+ return Number(oldValue) <= Number(nextValue);
121
+ }
122
+ function compareMaximumBound(oldValue, nextValue) {
123
+ if (nextValue === null || nextValue === undefined)
124
+ return true;
125
+ if (oldValue === null || oldValue === undefined)
126
+ return true;
127
+ return Number(oldValue) >= Number(nextValue);
128
+ }
129
+ function comparePatternBound(oldValue, nextValue) {
130
+ return oldValue === nextValue || oldValue === null || oldValue === undefined;
131
+ }
132
+ function scoreRenameSimilarity(legacyPath, currentPath) {
133
+ const legacy = normalizeFieldName(legacyPath);
134
+ const current = normalizeFieldName(currentPath);
135
+ if (legacy === current)
136
+ return 1;
137
+ if (shareAliasGroup(legacy, current))
138
+ return 0.9;
139
+ const legacyTokens = tokenizeFieldName(legacy);
140
+ const currentTokens = tokenizeFieldName(current);
141
+ const overlap = legacyTokens.filter((token) => currentTokens.includes(token));
142
+ const jaccard = overlap.length / new Set([...legacyTokens, ...currentTokens]).size;
143
+ if (legacy.includes(current) || current.includes(legacy)) {
144
+ return Math.max(jaccard, 0.7);
145
+ }
146
+ if (legacyTokens.length > 0 &&
147
+ currentTokens.length > 0 &&
148
+ legacyTokens[legacyTokens.length - 1] === currentTokens[currentTokens.length - 1]) {
149
+ return Math.max(jaccard, 0.6);
150
+ }
151
+ return jaccard;
152
+ }
153
+ function normalizeFieldName(name) {
154
+ return String(name)
155
+ .replace(/([a-z0-9])([A-Z])/g, "$1 $2")
156
+ .replace(/[^a-zA-Z0-9]+/g, " ")
157
+ .trim()
158
+ .toLowerCase()
159
+ .replace(/\s+/g, "");
160
+ }
161
+ function tokenizeFieldName(name) {
162
+ return String(name)
163
+ .replace(/([a-z0-9])([A-Z])/g, "$1 $2")
164
+ .toLowerCase()
165
+ .split(/[^a-z0-9]+/)
166
+ .filter(Boolean);
167
+ }
168
+ function getParentPath(pathLabel) {
169
+ const segments = String(pathLabel).split(".");
170
+ return segments.length <= 1 ? "" : segments.slice(0, -1).join(".");
171
+ }
172
+ function shareAliasGroup(left, right) {
173
+ const aliasGroups = [
174
+ ["content", "headline", "body", "text", "copy", "message"],
175
+ ["id", "uniqueid", "uuid"],
176
+ ["visible", "isvisible", "show", "shown", "enabled"],
177
+ ["align", "alignment", "textalign"],
178
+ ["count", "clickcount", "counter"],
179
+ ["url", "href", "link"],
180
+ ];
181
+ return aliasGroups.some((group) => group.includes(left) && group.includes(right));
182
+ }
183
+ function describeRenameReason(attribute, legacyPath, currentPath, score) {
184
+ if (attribute.ts.kind === "union") {
185
+ return `compatible discriminated union (${legacyPath} → ${currentPath})`;
186
+ }
187
+ if (score >= 0.9)
188
+ return "high-confidence compatible field";
189
+ if (score >= 0.6)
190
+ return "name-similar compatible field";
191
+ return "compatible field requiring review";
192
+ }
@@ -0,0 +1,14 @@
1
+ import type { DiffOutcome, FlattenedAttributeDescriptor, ManifestAttribute, RenameCandidate, TransformSuggestion } from "./migration-types.js";
2
+ interface CreateTransformSuggestionsOptions {
3
+ addedKeys: string[];
4
+ manualItems: DiffOutcome[];
5
+ newAttributes: Record<string, ManifestAttribute>;
6
+ newLeafAttributes: FlattenedAttributeDescriptor[];
7
+ oldAttributes: Record<string, ManifestAttribute>;
8
+ oldLeafAttributes: FlattenedAttributeDescriptor[];
9
+ removedKeys: string[];
10
+ renameCandidates: RenameCandidate[];
11
+ }
12
+ export declare function createTransformSuggestions({ oldAttributes, newAttributes, addedKeys, removedKeys, manualItems, renameCandidates, oldLeafAttributes, newLeafAttributes, }: CreateTransformSuggestionsOptions): TransformSuggestion[];
13
+ export declare function describeConstraintChange(oldAttribute: ManifestAttribute, newAttribute: ManifestAttribute): string;
14
+ export {};
@@ -0,0 +1,105 @@
1
+ import { getAttributeByCurrentPath } from "./migration-manifest.js";
2
+ import { passesNameSimilarityRule } from "./migration-diff-rename.js";
3
+ export function createTransformSuggestions({ oldAttributes, newAttributes, addedKeys, removedKeys, manualItems, renameCandidates, oldLeafAttributes, newLeafAttributes, }) {
4
+ const suggestions = [];
5
+ const activeRenameTargets = new Set(renameCandidates.filter((candidate) => candidate.autoApply).map((candidate) => candidate.currentPath));
6
+ const oldLeafMap = new Map(oldLeafAttributes.map((descriptor) => [descriptor.currentPath, descriptor]));
7
+ const newLeafMap = new Map(newLeafAttributes.map((descriptor) => [descriptor.currentPath, descriptor]));
8
+ for (const currentPath of [
9
+ ...new Set([
10
+ ...Object.keys(newAttributes),
11
+ ...manualItems.map((item) => item.path),
12
+ ...newLeafAttributes.map((item) => item.currentPath),
13
+ ]),
14
+ ]) {
15
+ if (activeRenameTargets.has(currentPath)) {
16
+ continue;
17
+ }
18
+ const manualItem = manualItems.find((item) => item.path === currentPath || item.path.startsWith(`${currentPath}.`));
19
+ const currentAttribute = newLeafMap.get(currentPath)?.attribute ??
20
+ getAttributeByCurrentPath(newAttributes, currentPath) ??
21
+ null;
22
+ if (!manualItem || !currentAttribute) {
23
+ continue;
24
+ }
25
+ const exactLegacy = oldLeafMap.get(currentPath)?.attribute ??
26
+ getAttributeByCurrentPath(oldAttributes, currentPath) ??
27
+ null;
28
+ if (exactLegacy && exactLegacy.ts.kind !== currentAttribute.ts.kind) {
29
+ suggestions.push({
30
+ bodyLines: buildTransformBodyLines(currentAttribute, currentPath),
31
+ attribute: currentAttribute,
32
+ currentPath,
33
+ legacyPath: currentPath,
34
+ reason: `semantic coercion suggested for ${manualItem.kind}`,
35
+ });
36
+ continue;
37
+ }
38
+ const bestRenameCandidate = renameCandidates.find((candidate) => candidate.currentPath === currentPath);
39
+ if (bestRenameCandidate && !bestRenameCandidate.autoApply) {
40
+ suggestions.push({
41
+ bodyLines: buildTransformBodyLines(currentAttribute, bestRenameCandidate.legacyPath),
42
+ attribute: currentAttribute,
43
+ currentPath,
44
+ legacyPath: bestRenameCandidate.legacyPath,
45
+ reason: `review coercion from ${bestRenameCandidate.legacyPath}`,
46
+ });
47
+ continue;
48
+ }
49
+ const addedCurrent = addedKeys.includes(currentPath) ||
50
+ (newLeafMap.has(currentPath) && !oldLeafMap.has(currentPath));
51
+ if (!addedCurrent) {
52
+ continue;
53
+ }
54
+ const compatibleLegacyPath = [
55
+ ...removedKeys,
56
+ ...oldLeafAttributes
57
+ .filter((descriptor) => !newLeafMap.has(descriptor.currentPath))
58
+ .map((descriptor) => descriptor.currentPath),
59
+ ].find((legacyPath) => passesNameSimilarityRule(legacyPath, currentPath));
60
+ if (compatibleLegacyPath) {
61
+ suggestions.push({
62
+ bodyLines: buildTransformBodyLines(currentAttribute, compatibleLegacyPath),
63
+ attribute: currentAttribute,
64
+ currentPath,
65
+ legacyPath: compatibleLegacyPath,
66
+ reason: `review coercion from ${compatibleLegacyPath}`,
67
+ });
68
+ }
69
+ }
70
+ return suggestions;
71
+ }
72
+ export function describeConstraintChange(oldAttribute, newAttribute) {
73
+ const details = [];
74
+ const oldConstraints = oldAttribute.typia.constraints;
75
+ const nextConstraints = newAttribute.typia.constraints;
76
+ if (newAttribute.wp.enum && JSON.stringify(newAttribute.wp.enum) !== JSON.stringify(oldAttribute.wp.enum)) {
77
+ details.push("enum changed");
78
+ }
79
+ for (const key of ["minLength", "maxLength", "minimum", "maximum", "pattern", "format", "typeTag"]) {
80
+ if (oldConstraints[key] !== nextConstraints[key]) {
81
+ details.push(`${key}: ${oldConstraints[key]} -> ${nextConstraints[key]}`);
82
+ }
83
+ }
84
+ return details.join(", ");
85
+ }
86
+ function buildTransformBodyLines(attribute, legacyPath) {
87
+ switch (attribute.ts.kind) {
88
+ case "string":
89
+ return [`// return typeof legacyValue === "string" ? legacyValue : String(legacyValue ?? "");`];
90
+ case "number":
91
+ return [
92
+ `// const numericValue = typeof legacyValue === "number" ? legacyValue : Number(legacyValue ?? 0);`,
93
+ `// return Number.isNaN(numericValue) ? undefined : numericValue;`,
94
+ ];
95
+ case "boolean":
96
+ return [`// return typeof legacyValue === "boolean" ? legacyValue : Boolean(legacyValue);`];
97
+ case "union":
98
+ return [
99
+ `// const legacyObject = typeof legacyValue === "object" && legacyValue !== null ? legacyValue : {};`,
100
+ `// return legacyObject; // adjust discriminator / branch fields before verify`,
101
+ ];
102
+ default:
103
+ return [`// return legacyValue; // customize migration from ${legacyPath}`];
104
+ }
105
+ }