@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,5 +1,7 @@
1
1
  import fs from "node:fs";
2
- import { flattenManifestLeafAttributes, getAttributeByCurrentPath, getManifestDefaultValue, hasManifestDefault, } from "./migration-manifest.js";
2
+ import { flattenManifestLeafAttributes, getManifestDefaultValue, hasManifestDefault, } from "./migration-manifest.js";
3
+ import { createRenameCandidates } from "./migration-diff-rename.js";
4
+ import { createTransformSuggestions, describeConstraintChange, } from "./migration-diff-transform.js";
3
5
  import { createMissingBlockSnapshotMessage, getAvailableSnapshotVersionsForBlock, getSnapshotManifestPath, } from "./migration-project.js";
4
6
  import { isNumber, readJson } from "./migration-utils.js";
5
7
  export function createMigrationDiff(state, blockOrFromVersion, fromVersionOrToVersion, maybeToVersion) {
@@ -79,7 +81,15 @@ export function createMigrationDiff(state, blockOrFromVersion, fromVersionOrToVe
79
81
  });
80
82
  }
81
83
  }
82
- const renameCandidates = createRenameCandidates(oldAttributes, newAttributes, removedKeys, addedKeys, oldLeafAttributes, newLeafAttributes);
84
+ const renameCandidates = createRenameCandidates({
85
+ addedKeys,
86
+ isUnionRenameCompatible: (oldAttribute, newAttribute) => compareUnionAttribute(oldAttribute, newAttribute, "$rename").status === "auto",
87
+ newAttributes,
88
+ newLeafAttributes,
89
+ oldAttributes,
90
+ oldLeafAttributes,
91
+ removedKeys,
92
+ });
83
93
  const activeRenameCandidates = renameCandidates.filter((candidate) => candidate.autoApply);
84
94
  for (const candidate of activeRenameCandidates) {
85
95
  removeOutcomeByPath(autoItems, candidate.legacyPath, "drop");
@@ -234,301 +244,6 @@ function hasStricterConstraints(oldAttribute, newAttribute) {
234
244
  }
235
245
  return false;
236
246
  }
237
- function createRenameCandidates(oldAttributes, newAttributes, removedKeys, addedKeys, oldLeafAttributes, newLeafAttributes) {
238
- const assessments = [];
239
- for (const currentPath of addedKeys) {
240
- const nextAttribute = newAttributes[currentPath];
241
- if (!nextAttribute)
242
- continue;
243
- for (const legacyPath of removedKeys) {
244
- const previous = oldAttributes[legacyPath];
245
- if (!previous)
246
- continue;
247
- const candidate = assessRenameCandidate(previous, nextAttribute, legacyPath, currentPath);
248
- if (candidate) {
249
- assessments.push(candidate);
250
- }
251
- }
252
- }
253
- const oldLeafMap = new Map(oldLeafAttributes.map((descriptor) => [descriptor.currentPath, descriptor]));
254
- const newLeafMap = new Map(newLeafAttributes.map((descriptor) => [descriptor.currentPath, descriptor]));
255
- const removedLeafDescriptors = oldLeafAttributes.filter((descriptor) => !newLeafMap.has(descriptor.currentPath));
256
- const addedLeafDescriptors = newLeafAttributes.filter((descriptor) => !oldLeafMap.has(descriptor.currentPath));
257
- for (const nextDescriptor of addedLeafDescriptors) {
258
- if (!nextDescriptor.currentPath.includes(".")) {
259
- continue;
260
- }
261
- for (const previousDescriptor of removedLeafDescriptors) {
262
- if (!previousDescriptor.currentPath.includes(".")) {
263
- continue;
264
- }
265
- const candidate = assessRenameCandidate(previousDescriptor.attribute, nextDescriptor.attribute, previousDescriptor.currentPath, nextDescriptor.currentPath);
266
- if (candidate) {
267
- assessments.push(candidate);
268
- }
269
- }
270
- }
271
- return assessments
272
- .map((candidate) => {
273
- const currentMatches = assessments
274
- .filter((item) => item.currentPath === candidate.currentPath)
275
- .sort((left, right) => right.score - left.score);
276
- const legacyMatches = assessments
277
- .filter((item) => item.legacyPath === candidate.legacyPath)
278
- .sort((left, right) => right.score - left.score);
279
- const currentLeader = currentMatches[0];
280
- const legacyLeader = legacyMatches[0];
281
- const currentHasTie = currentMatches.length > 1 && Math.abs((currentMatches[1]?.score ?? 0) - currentLeader.score) < 0.05;
282
- const legacyHasTie = legacyMatches.length > 1 && Math.abs((legacyMatches[1]?.score ?? 0) - legacyLeader.score) < 0.05;
283
- return {
284
- ...candidate,
285
- autoApply: currentLeader.legacyPath === candidate.legacyPath &&
286
- legacyLeader.currentPath === candidate.currentPath &&
287
- !currentHasTie &&
288
- !legacyHasTie &&
289
- candidate.score >= 0.6,
290
- };
291
- })
292
- .filter((candidate, index, list) => {
293
- const firstMatch = list.findIndex((item) => item.currentPath === candidate.currentPath && item.legacyPath === candidate.legacyPath);
294
- return firstMatch === index;
295
- })
296
- .sort((left, right) => right.score - left.score);
297
- }
298
- function createTransformSuggestions({ oldAttributes, newAttributes, addedKeys, removedKeys, manualItems, renameCandidates, oldLeafAttributes, newLeafAttributes, }) {
299
- const suggestions = [];
300
- const activeRenameTargets = new Set(renameCandidates.filter((candidate) => candidate.autoApply).map((candidate) => candidate.currentPath));
301
- const oldLeafMap = new Map(oldLeafAttributes.map((descriptor) => [descriptor.currentPath, descriptor]));
302
- const newLeafMap = new Map(newLeafAttributes.map((descriptor) => [descriptor.currentPath, descriptor]));
303
- for (const currentPath of [
304
- ...new Set([
305
- ...Object.keys(newAttributes),
306
- ...manualItems.map((item) => item.path),
307
- ...newLeafAttributes.map((item) => item.currentPath),
308
- ]),
309
- ]) {
310
- if (activeRenameTargets.has(currentPath)) {
311
- continue;
312
- }
313
- const manualItem = manualItems.find((item) => item.path === currentPath || item.path.startsWith(`${currentPath}.`));
314
- const currentAttribute = newLeafMap.get(currentPath)?.attribute ??
315
- getAttributeByCurrentPath(newAttributes, currentPath) ??
316
- null;
317
- if (!manualItem || !currentAttribute) {
318
- continue;
319
- }
320
- const exactLegacy = oldLeafMap.get(currentPath)?.attribute ??
321
- getAttributeByCurrentPath(oldAttributes, currentPath) ??
322
- null;
323
- if (exactLegacy && exactLegacy.ts.kind !== currentAttribute.ts.kind) {
324
- suggestions.push({
325
- bodyLines: buildTransformBodyLines(currentAttribute, currentPath),
326
- attribute: currentAttribute,
327
- currentPath,
328
- legacyPath: currentPath,
329
- reason: `semantic coercion suggested for ${manualItem.kind}`,
330
- });
331
- continue;
332
- }
333
- const bestRenameCandidate = renameCandidates.find((candidate) => candidate.currentPath === currentPath);
334
- if (bestRenameCandidate && !bestRenameCandidate.autoApply) {
335
- suggestions.push({
336
- bodyLines: buildTransformBodyLines(currentAttribute, bestRenameCandidate.legacyPath),
337
- attribute: currentAttribute,
338
- currentPath,
339
- legacyPath: bestRenameCandidate.legacyPath,
340
- reason: `review coercion from ${bestRenameCandidate.legacyPath}`,
341
- });
342
- continue;
343
- }
344
- const addedCurrent = addedKeys.includes(currentPath) ||
345
- (newLeafMap.has(currentPath) && !oldLeafMap.has(currentPath));
346
- if (!addedCurrent) {
347
- continue;
348
- }
349
- const compatibleLegacyPath = [
350
- ...removedKeys,
351
- ...oldLeafAttributes
352
- .filter((descriptor) => !newLeafMap.has(descriptor.currentPath))
353
- .map((descriptor) => descriptor.currentPath),
354
- ].find((legacyPath) => passesNameSimilarityRule(legacyPath, currentPath));
355
- if (compatibleLegacyPath) {
356
- suggestions.push({
357
- bodyLines: buildTransformBodyLines(currentAttribute, compatibleLegacyPath),
358
- attribute: currentAttribute,
359
- currentPath,
360
- legacyPath: compatibleLegacyPath,
361
- reason: `review coercion from ${compatibleLegacyPath}`,
362
- });
363
- }
364
- }
365
- return suggestions;
366
- }
367
- function isRenameCandidateShapeCompatible(oldAttribute, newAttribute) {
368
- if (!oldAttribute || !newAttribute || oldAttribute.ts.kind !== newAttribute.ts.kind) {
369
- return false;
370
- }
371
- if (["string", "number", "boolean"].includes(oldAttribute.ts.kind)) {
372
- return hasRenameCompatibleConstraints(oldAttribute, newAttribute);
373
- }
374
- if (oldAttribute.ts.kind === "union") {
375
- return compareUnionAttribute(oldAttribute, newAttribute, "$rename").status === "auto";
376
- }
377
- return false;
378
- }
379
- function assessRenameCandidate(oldAttribute, newAttribute, legacyPath, currentPath) {
380
- if (!isRenameCandidateShapeCompatible(oldAttribute, newAttribute)) {
381
- return null;
382
- }
383
- const baseScore = scoreRenameSimilarity(legacyPath, currentPath);
384
- const score = getParentPath(legacyPath) === getParentPath(currentPath) ? Math.max(baseScore, 0.75) : baseScore;
385
- return {
386
- autoApply: false,
387
- currentPath,
388
- legacyPath,
389
- reason: describeRenameReason(oldAttribute, legacyPath, currentPath, score),
390
- score,
391
- };
392
- }
393
- function hasRenameCompatibleConstraints(oldAttribute, newAttribute) {
394
- const oldEnum = oldAttribute.wp.enum ?? null;
395
- const nextEnum = newAttribute.wp.enum ?? null;
396
- if (oldEnum && nextEnum) {
397
- const oldIsSubset = oldEnum.every((value) => nextEnum.includes(value));
398
- if (!oldIsSubset) {
399
- return false;
400
- }
401
- }
402
- else if (oldEnum && !nextEnum) {
403
- return false;
404
- }
405
- const oldConstraints = oldAttribute.typia.constraints ?? {};
406
- const nextConstraints = newAttribute.typia.constraints ?? {};
407
- return [
408
- compareMinimumBound(oldConstraints.minLength, nextConstraints.minLength),
409
- compareMaximumBound(oldConstraints.maxLength, nextConstraints.maxLength),
410
- compareMinimumBound(oldConstraints.minimum, nextConstraints.minimum),
411
- compareMaximumBound(oldConstraints.maximum, nextConstraints.maximum),
412
- comparePatternBound(oldConstraints.pattern, nextConstraints.pattern),
413
- comparePatternBound(oldConstraints.format, nextConstraints.format),
414
- comparePatternBound(oldConstraints.typeTag, nextConstraints.typeTag),
415
- ].every(Boolean);
416
- }
417
- function compareMinimumBound(oldValue, nextValue) {
418
- if (nextValue === null || nextValue === undefined)
419
- return true;
420
- if (oldValue === null || oldValue === undefined)
421
- return true;
422
- return Number(oldValue) <= Number(nextValue);
423
- }
424
- function compareMaximumBound(oldValue, nextValue) {
425
- if (nextValue === null || nextValue === undefined)
426
- return true;
427
- if (oldValue === null || oldValue === undefined)
428
- return true;
429
- return Number(oldValue) >= Number(nextValue);
430
- }
431
- function comparePatternBound(oldValue, nextValue) {
432
- return oldValue === nextValue || oldValue === null || oldValue === undefined;
433
- }
434
- function scoreRenameSimilarity(legacyPath, currentPath) {
435
- const legacy = normalizeFieldName(legacyPath);
436
- const current = normalizeFieldName(currentPath);
437
- if (legacy === current)
438
- return 1;
439
- if (shareAliasGroup(legacy, current))
440
- return 0.9;
441
- const legacyTokens = tokenizeFieldName(legacy);
442
- const currentTokens = tokenizeFieldName(current);
443
- const overlap = legacyTokens.filter((token) => currentTokens.includes(token));
444
- const jaccard = overlap.length / new Set([...legacyTokens, ...currentTokens]).size;
445
- if (legacy.includes(current) || current.includes(legacy)) {
446
- return Math.max(jaccard, 0.7);
447
- }
448
- if (legacyTokens.length > 0 &&
449
- currentTokens.length > 0 &&
450
- legacyTokens[legacyTokens.length - 1] === currentTokens[currentTokens.length - 1]) {
451
- return Math.max(jaccard, 0.6);
452
- }
453
- return jaccard;
454
- }
455
- function passesNameSimilarityRule(legacyPath, currentPath) {
456
- return scoreRenameSimilarity(legacyPath, currentPath) >= 0.6;
457
- }
458
- function normalizeFieldName(name) {
459
- return String(name)
460
- .replace(/([a-z0-9])([A-Z])/g, "$1 $2")
461
- .replace(/[^a-zA-Z0-9]+/g, " ")
462
- .trim()
463
- .toLowerCase()
464
- .replace(/\s+/g, "");
465
- }
466
- function tokenizeFieldName(name) {
467
- return String(name)
468
- .replace(/([a-z0-9])([A-Z])/g, "$1 $2")
469
- .toLowerCase()
470
- .split(/[^a-z0-9]+/)
471
- .filter(Boolean);
472
- }
473
- function getParentPath(pathLabel) {
474
- const segments = String(pathLabel).split(".");
475
- return segments.length <= 1 ? "" : segments.slice(0, -1).join(".");
476
- }
477
- function shareAliasGroup(left, right) {
478
- const aliasGroups = [
479
- ["content", "headline", "body", "text", "copy", "message"],
480
- ["id", "uniqueid", "uuid"],
481
- ["visible", "isvisible", "show", "shown", "enabled"],
482
- ["align", "alignment", "textalign"],
483
- ["count", "clickcount", "counter"],
484
- ["url", "href", "link"],
485
- ];
486
- return aliasGroups.some((group) => group.includes(left) && group.includes(right));
487
- }
488
- function describeRenameReason(attribute, legacyPath, currentPath, score) {
489
- if (attribute.ts.kind === "union") {
490
- return `compatible discriminated union (${legacyPath} → ${currentPath})`;
491
- }
492
- if (score >= 0.9)
493
- return "high-confidence compatible field";
494
- if (score >= 0.6)
495
- return "name-similar compatible field";
496
- return "compatible field requiring review";
497
- }
498
- function buildTransformBodyLines(attribute, legacyPath) {
499
- switch (attribute.ts.kind) {
500
- case "string":
501
- return [`// return typeof legacyValue === "string" ? legacyValue : String(legacyValue ?? "");`];
502
- case "number":
503
- return [
504
- `// const numericValue = typeof legacyValue === "number" ? legacyValue : Number(legacyValue ?? 0);`,
505
- `// return Number.isNaN(numericValue) ? undefined : numericValue;`,
506
- ];
507
- case "boolean":
508
- return [`// return typeof legacyValue === "boolean" ? legacyValue : Boolean(legacyValue);`];
509
- case "union":
510
- return [
511
- `// const legacyObject = typeof legacyValue === "object" && legacyValue !== null ? legacyValue : {};`,
512
- `// return legacyObject; // adjust discriminator / branch fields before verify`,
513
- ];
514
- default:
515
- return [`// return legacyValue; // customize migration from ${legacyPath}`];
516
- }
517
- }
518
- function describeConstraintChange(oldAttribute, newAttribute) {
519
- const details = [];
520
- const oldConstraints = oldAttribute.typia.constraints;
521
- const nextConstraints = newAttribute.typia.constraints;
522
- if (newAttribute.wp.enum && JSON.stringify(newAttribute.wp.enum) !== JSON.stringify(oldAttribute.wp.enum)) {
523
- details.push("enum changed");
524
- }
525
- for (const key of ["minLength", "maxLength", "minimum", "maximum", "pattern", "format", "typeTag"]) {
526
- if (oldConstraints[key] !== nextConstraints[key]) {
527
- details.push(`${key}: ${oldConstraints[key]} -> ${nextConstraints[key]}`);
528
- }
529
- }
530
- return details.join(", ");
531
- }
532
247
  function autoOutcome(pathLabel, kind, detail) {
533
248
  return { detail, kind, path: pathLabel, status: "auto" };
534
249
  }
@@ -0,0 +1,3 @@
1
+ import type { GeneratedMigrationEntry, MigrationProjectState } from "./migration-types.js";
2
+ export declare function collectGeneratedMigrationEntries(state: MigrationProjectState): GeneratedMigrationEntry[];
3
+ export declare function regenerateGeneratedArtifacts(projectDir: string): void;
@@ -0,0 +1,41 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { ROOT_PHP_MIGRATION_REGISTRY } from "./migration-constants.js";
4
+ import { createMigrationDiff } from "./migration-diff.js";
5
+ import { createMigrationFuzzPlan } from "./migration-fuzz-plan.js";
6
+ import { discoverMigrationEntries, getGeneratedDirForBlock, getSnapshotManifestPath, loadMigrationProject, } from "./migration-project.js";
7
+ import { renderFuzzFile, renderGeneratedDeprecatedFile, renderGeneratedMigrationIndexFile, renderMigrationRegistryFile, renderPhpMigrationRegistryFile, renderVerifyFile, } from "./migration-render.js";
8
+ import { createMigrationRiskSummary } from "./migration-risk.js";
9
+ import { readJson } from "./migration-utils.js";
10
+ export function collectGeneratedMigrationEntries(state) {
11
+ return discoverMigrationEntries(state).map((entry) => {
12
+ const block = state.blocks.find((target) => target.key === entry.block.key);
13
+ if (!block) {
14
+ throw new Error(`Unknown migration block target: ${entry.block.key}`);
15
+ }
16
+ const diff = createMigrationDiff(state, entry.block, entry.fromVersion, entry.toVersion);
17
+ const legacyManifest = readJson(getSnapshotManifestPath(state.projectDir, entry.block, entry.fromVersion));
18
+ return {
19
+ diff,
20
+ entry,
21
+ fuzzPlan: createMigrationFuzzPlan(legacyManifest, block.currentManifest, diff),
22
+ riskSummary: createMigrationRiskSummary(diff),
23
+ };
24
+ });
25
+ }
26
+ export function regenerateGeneratedArtifacts(projectDir) {
27
+ const state = loadMigrationProject(projectDir);
28
+ const generatedEntries = collectGeneratedMigrationEntries(state);
29
+ for (const block of state.blocks) {
30
+ const blockGeneratedEntries = generatedEntries.filter(({ entry }) => entry.block.key === block.key);
31
+ const entries = blockGeneratedEntries.map(({ entry }) => entry);
32
+ const generatedDir = getGeneratedDirForBlock(state.paths, block);
33
+ fs.mkdirSync(generatedDir, { recursive: true });
34
+ fs.writeFileSync(path.join(generatedDir, "registry.ts"), renderMigrationRegistryFile(state, block.key, blockGeneratedEntries), "utf8");
35
+ fs.writeFileSync(path.join(generatedDir, "deprecated.ts"), renderGeneratedDeprecatedFile(state, block.key, entries), "utf8");
36
+ fs.writeFileSync(path.join(generatedDir, "verify.ts"), renderVerifyFile(state, block.key, entries), "utf8");
37
+ fs.writeFileSync(path.join(generatedDir, "fuzz.ts"), renderFuzzFile(state, block.key, blockGeneratedEntries), "utf8");
38
+ }
39
+ fs.writeFileSync(path.join(state.paths.generatedDir, "index.ts"), renderGeneratedMigrationIndexFile(state, generatedEntries.map(({ entry }) => entry)), "utf8");
40
+ fs.writeFileSync(path.join(projectDir, ROOT_PHP_MIGRATION_REGISTRY), renderPhpMigrationRegistryFile(state, generatedEntries.map(({ entry }) => entry)), "utf8");
41
+ }
@@ -0,0 +1,23 @@
1
+ import type { FixturesOptions, FuzzOptions } from './migration-command-surface.js';
2
+ /**
3
+ * Generate or refresh migration fixtures for one or more legacy edges.
4
+ *
5
+ * @param projectDir Absolute or relative project directory containing the migration workspace.
6
+ * @param options Fixture generation scope and refresh options.
7
+ * @returns Generated and skipped legacy versions.
8
+ */
9
+ export declare function fixturesProjectMigrations(projectDir: string, { all, confirmOverwrite, force, fromMigrationVersion, isInteractive, renderLine, toMigrationVersion, }?: FixturesOptions): {
10
+ generatedVersions: string[];
11
+ skippedVersions: string[];
12
+ };
13
+ /**
14
+ * Run seeded migration fuzz verification against generated fuzz artifacts.
15
+ *
16
+ * @param projectDir Absolute or relative project directory containing the migration workspace.
17
+ * @param options Fuzz scope, iteration count, seed, and console rendering options.
18
+ * @returns Fuzzed legacy versions and the effective seed.
19
+ */
20
+ export declare function fuzzProjectMigrations(projectDir: string, { all, fromMigrationVersion, iterations, renderLine, seed, }?: FuzzOptions): {
21
+ fuzzedVersions: string[];
22
+ seed: number | undefined;
23
+ };
@@ -0,0 +1,126 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { execFileSync } from 'node:child_process';
4
+ import { createMigrationDiff } from './migration-diff.js';
5
+ import { ensureEdgeFixtureFile, } from './migration-fixtures.js';
6
+ import { collectFixtureTargets, formatScaffoldCommand, getSelectedEntriesByBlock, isLegacySingleBlockProject, resolveLegacyVersions, } from './migration-planning.js';
7
+ import { assertRuleHasNoTodos, getGeneratedDirForBlock, loadMigrationProject, } from './migration-project.js';
8
+ import { getLocalTsxBinary, isInteractiveTerminal, resolveTargetMigrationVersion, } from './migration-utils.js';
9
+ /**
10
+ * Generate or refresh migration fixtures for one or more legacy edges.
11
+ *
12
+ * @param projectDir Absolute or relative project directory containing the migration workspace.
13
+ * @param options Fixture generation scope and refresh options.
14
+ * @returns Generated and skipped legacy versions.
15
+ */
16
+ export function fixturesProjectMigrations(projectDir, { all = false, confirmOverwrite, force = false, fromMigrationVersion, isInteractive = isInteractiveTerminal(), renderLine = console.log, toMigrationVersion = 'current', } = {}) {
17
+ const state = loadMigrationProject(projectDir);
18
+ const targetMigrationVersion = resolveTargetMigrationVersion(state.config.currentMigrationVersion, toMigrationVersion);
19
+ const targetVersions = resolveLegacyVersions(state, { all, fromMigrationVersion });
20
+ if (targetVersions.length === 0) {
21
+ renderLine('No legacy migration versions configured for fixture generation.');
22
+ return { generatedVersions: [], skippedVersions: [] };
23
+ }
24
+ const generatedVersions = [];
25
+ const skippedVersions = [];
26
+ const fixtureTargets = collectFixtureTargets(state, targetVersions, targetMigrationVersion);
27
+ if (force) {
28
+ const overwriteTargets = fixtureTargets.filter(({ fixturePath }) => fs.existsSync(fixturePath));
29
+ if (isInteractive && overwriteTargets.length > 0) {
30
+ const confirmed = confirmOverwrite?.(`About to overwrite ${overwriteTargets.length} existing migration fixture file(s). Continue?`) ??
31
+ promptForConfirmation(`About to overwrite ${overwriteTargets.length} existing migration fixture file(s). Continue?`);
32
+ if (!confirmed) {
33
+ renderLine(`Cancelled fixture refresh. Kept ${overwriteTargets.length} existing fixture file(s).`);
34
+ return {
35
+ generatedVersions,
36
+ skippedVersions: overwriteTargets.map(({ scopedLabel }) => scopedLabel),
37
+ };
38
+ }
39
+ }
40
+ }
41
+ for (const { block, fixturePath, scopedLabel, version } of fixtureTargets) {
42
+ const existed = fs.existsSync(fixturePath);
43
+ const diff = createMigrationDiff(state, block, version, targetMigrationVersion);
44
+ const result = ensureEdgeFixtureFile(projectDir, block, version, targetMigrationVersion, diff, { force });
45
+ if (result.written) {
46
+ generatedVersions.push(scopedLabel);
47
+ renderLine(`${existed ? 'Refreshed' : 'Generated'} fixture ${path.relative(projectDir, fixturePath)}`);
48
+ }
49
+ else {
50
+ skippedVersions.push(scopedLabel);
51
+ renderLine(`Preserved existing fixture ${path.relative(projectDir, fixturePath)} (use --force to refresh)`);
52
+ }
53
+ }
54
+ return {
55
+ generatedVersions,
56
+ skippedVersions,
57
+ };
58
+ }
59
+ /**
60
+ * Run seeded migration fuzz verification against generated fuzz artifacts.
61
+ *
62
+ * @param projectDir Absolute or relative project directory containing the migration workspace.
63
+ * @param options Fuzz scope, iteration count, seed, and console rendering options.
64
+ * @returns Fuzzed legacy versions and the effective seed.
65
+ */
66
+ export function fuzzProjectMigrations(projectDir, { all = false, fromMigrationVersion, iterations = 25, renderLine = console.log, seed, } = {}) {
67
+ const state = loadMigrationProject(projectDir);
68
+ const targetVersions = resolveLegacyVersions(state, { all, fromMigrationVersion });
69
+ const blockEntries = getSelectedEntriesByBlock(state, targetVersions, 'fuzz');
70
+ const legacySingleBlock = isLegacySingleBlockProject(state);
71
+ if (targetVersions.length === 0) {
72
+ renderLine('No legacy migration versions configured for fuzzing.');
73
+ return { fuzzedVersions: [], seed };
74
+ }
75
+ const tsxBinary = getLocalTsxBinary(projectDir);
76
+ for (const [blockKey, entries] of Object.entries(blockEntries)) {
77
+ const block = state.blocks.find((entry) => entry.key === blockKey);
78
+ if (!block || entries.length === 0) {
79
+ continue;
80
+ }
81
+ for (const entry of entries) {
82
+ assertRuleHasNoTodos(projectDir, block, entry.fromVersion, state.config.currentMigrationVersion);
83
+ }
84
+ const fuzzScriptPath = path.join(getGeneratedDirForBlock(state.paths, block), 'fuzz.ts');
85
+ if (!fs.existsSync(fuzzScriptPath)) {
86
+ const selectedVersionsForBlock = entries.map((entry) => entry.fromVersion);
87
+ throw new Error(`Generated fuzz script is missing for ${block.blockName} (${selectedVersionsForBlock.join(', ')}). ` +
88
+ `Run \`${formatScaffoldCommand(selectedVersionsForBlock)}\` first, then \`wp-typia migrate doctor --all\` if the workspace should already be scaffolded.`);
89
+ }
90
+ const selectedVersionsForBlock = entries.map((entry) => entry.fromVersion);
91
+ const args = [
92
+ fuzzScriptPath,
93
+ ...(all ? ['--all'] : ['--from-migration-version', selectedVersionsForBlock[0]]),
94
+ '--iterations',
95
+ String(iterations),
96
+ ...(seed === undefined ? [] : ['--seed', String(seed)]),
97
+ ];
98
+ execFileSync(tsxBinary, args, {
99
+ cwd: projectDir,
100
+ shell: process.platform === 'win32',
101
+ stdio: 'inherit',
102
+ });
103
+ renderLine(legacySingleBlock
104
+ ? `Fuzzed migrations for ${selectedVersionsForBlock.join(', ')}`
105
+ : `Fuzzed ${block.blockName} migrations for ${selectedVersionsForBlock.join(', ')}`);
106
+ }
107
+ return { fuzzedVersions: targetVersions, seed };
108
+ }
109
+ function promptForConfirmation(message) {
110
+ process.stdout.write(`${message} [y/N]: `);
111
+ const buffer = Buffer.alloc(1);
112
+ let answer = '';
113
+ while (true) {
114
+ const bytesRead = fs.readSync(process.stdin.fd, buffer, 0, 1, null);
115
+ if (bytesRead === 0) {
116
+ break;
117
+ }
118
+ const char = buffer.toString('utf8', 0, bytesRead);
119
+ if (char === '\n' || char === '\r') {
120
+ break;
121
+ }
122
+ answer += char;
123
+ }
124
+ const normalized = answer.trim().toLowerCase();
125
+ return normalized === 'y' || normalized === 'yes';
126
+ }
@@ -0,0 +1,26 @@
1
+ import type { VerifyOptions } from './migration-command-surface.js';
2
+ /**
3
+ * Run deterministic migration verification against generated fixtures.
4
+ *
5
+ * @param projectDir Absolute or relative project directory containing the migration workspace.
6
+ * @param options Verification scope and console rendering options.
7
+ * @returns Verified legacy versions.
8
+ */
9
+ export declare function verifyProjectMigrations(projectDir: string, { all, fromMigrationVersion, renderLine, }?: VerifyOptions): {
10
+ verifiedVersions: string[];
11
+ };
12
+ /**
13
+ * Validate the migration workspace without mutating files.
14
+ *
15
+ * @param projectDir Absolute or relative project directory containing the migration workspace.
16
+ * @param options Doctor scope and console rendering options.
17
+ * @returns Structured doctor check results for the selected legacy versions.
18
+ */
19
+ export declare function doctorProjectMigrations(projectDir: string, { all, fromMigrationVersion, renderLine, }?: VerifyOptions): {
20
+ checkedVersions: string[];
21
+ checks: {
22
+ detail: string;
23
+ label: string;
24
+ status: "fail" | "pass";
25
+ }[];
26
+ };