@jigx/sdk-tools 1.2.0-next.2 → 1.2.0-next.4

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 (200) hide show
  1. package/README.md +35 -44
  2. package/dist/cli.js +83 -0
  3. package/dist/cli.js.map +1 -1
  4. package/dist/index.d.ts +2 -0
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +1 -0
  7. package/dist/index.js.map +1 -1
  8. package/dist/migrator/converters/app-index.d.ts +53 -0
  9. package/dist/migrator/converters/app-index.d.ts.map +1 -0
  10. package/dist/migrator/converters/app-index.js +206 -0
  11. package/dist/migrator/converters/app-index.js.map +1 -0
  12. package/dist/migrator/converters/assets.d.ts +7 -0
  13. package/dist/migrator/converters/assets.d.ts.map +1 -0
  14. package/dist/migrator/converters/assets.js +58 -0
  15. package/dist/migrator/converters/assets.js.map +1 -0
  16. package/dist/migrator/converters/components.d.ts +4 -0
  17. package/dist/migrator/converters/components.d.ts.map +1 -0
  18. package/dist/migrator/converters/components.js +7 -0
  19. package/dist/migrator/converters/components.js.map +1 -0
  20. package/dist/migrator/converters/databases.d.ts +9 -0
  21. package/dist/migrator/converters/databases.d.ts.map +1 -0
  22. package/dist/migrator/converters/databases.js +45 -0
  23. package/dist/migrator/converters/databases.js.map +1 -0
  24. package/dist/migrator/converters/datasources.d.ts +46 -0
  25. package/dist/migrator/converters/datasources.d.ts.map +1 -0
  26. package/dist/migrator/converters/datasources.js +445 -0
  27. package/dist/migrator/converters/datasources.js.map +1 -0
  28. package/dist/migrator/converters/functions.d.ts +13 -0
  29. package/dist/migrator/converters/functions.d.ts.map +1 -0
  30. package/dist/migrator/converters/functions.js +444 -0
  31. package/dist/migrator/converters/functions.js.map +1 -0
  32. package/dist/migrator/converters/index.d.ts +11 -0
  33. package/dist/migrator/converters/index.d.ts.map +1 -0
  34. package/dist/migrator/converters/index.js +9 -0
  35. package/dist/migrator/converters/index.js.map +1 -0
  36. package/dist/migrator/converters/scripts.d.ts +10 -0
  37. package/dist/migrator/converters/scripts.d.ts.map +1 -0
  38. package/dist/migrator/converters/scripts.js +28 -0
  39. package/dist/migrator/converters/scripts.js.map +1 -0
  40. package/dist/migrator/converters/translations.d.ts +5 -0
  41. package/dist/migrator/converters/translations.d.ts.map +1 -0
  42. package/dist/migrator/converters/translations.js +25 -0
  43. package/dist/migrator/converters/translations.js.map +1 -0
  44. package/dist/migrator/converters/types.d.ts +35 -0
  45. package/dist/migrator/converters/types.d.ts.map +1 -0
  46. package/dist/migrator/converters/types.js +2 -0
  47. package/dist/migrator/converters/types.js.map +1 -0
  48. package/dist/migrator/errors.d.ts +5 -0
  49. package/dist/migrator/errors.d.ts.map +1 -0
  50. package/dist/migrator/errors.js +8 -0
  51. package/dist/migrator/errors.js.map +1 -0
  52. package/dist/migrator/index.d.ts +6 -0
  53. package/dist/migrator/index.d.ts.map +1 -0
  54. package/dist/migrator/index.js +4 -0
  55. package/dist/migrator/index.js.map +1 -0
  56. package/dist/migrator/migrate.d.ts +75 -0
  57. package/dist/migrator/migrate.d.ts.map +1 -0
  58. package/dist/migrator/migrate.js +439 -0
  59. package/dist/migrator/migrate.js.map +1 -0
  60. package/dist/migrator/module-writer.d.ts +26 -0
  61. package/dist/migrator/module-writer.d.ts.map +1 -0
  62. package/dist/migrator/module-writer.js +26 -0
  63. package/dist/migrator/module-writer.js.map +1 -0
  64. package/dist/migrator/nodes/action-definition.d.ts +11 -0
  65. package/dist/migrator/nodes/action-definition.d.ts.map +1 -0
  66. package/dist/migrator/nodes/action-definition.js +75 -0
  67. package/dist/migrator/nodes/action-definition.js.map +1 -0
  68. package/dist/migrator/nodes/app-events.d.ts +29 -0
  69. package/dist/migrator/nodes/app-events.d.ts.map +1 -0
  70. package/dist/migrator/nodes/app-events.js +40 -0
  71. package/dist/migrator/nodes/app-events.js.map +1 -0
  72. package/dist/migrator/nodes/attach.d.ts +61 -0
  73. package/dist/migrator/nodes/attach.d.ts.map +1 -0
  74. package/dist/migrator/nodes/attach.js +174 -0
  75. package/dist/migrator/nodes/attach.js.map +1 -0
  76. package/dist/migrator/nodes/builder-access.d.ts +16 -0
  77. package/dist/migrator/nodes/builder-access.d.ts.map +1 -0
  78. package/dist/migrator/nodes/builder-access.js +81 -0
  79. package/dist/migrator/nodes/builder-access.js.map +1 -0
  80. package/dist/migrator/nodes/codegen.d.ts +33 -0
  81. package/dist/migrator/nodes/codegen.d.ts.map +1 -0
  82. package/dist/migrator/nodes/codegen.js +77 -0
  83. package/dist/migrator/nodes/codegen.js.map +1 -0
  84. package/dist/migrator/nodes/index.d.ts +6 -0
  85. package/dist/migrator/nodes/index.d.ts.map +1 -0
  86. package/dist/migrator/nodes/index.js +6 -0
  87. package/dist/migrator/nodes/index.js.map +1 -0
  88. package/dist/migrator/nodes/node-shape.d.ts +39 -0
  89. package/dist/migrator/nodes/node-shape.d.ts.map +1 -0
  90. package/dist/migrator/nodes/node-shape.js +65 -0
  91. package/dist/migrator/nodes/node-shape.js.map +1 -0
  92. package/dist/migrator/nodes/probe.d.ts +66 -0
  93. package/dist/migrator/nodes/probe.d.ts.map +1 -0
  94. package/dist/migrator/nodes/probe.js +46 -0
  95. package/dist/migrator/nodes/probe.js.map +1 -0
  96. package/dist/migrator/nodes/rules/action-list.d.ts +4 -0
  97. package/dist/migrator/nodes/rules/action-list.d.ts.map +1 -0
  98. package/dist/migrator/nodes/rules/action-list.js +14 -0
  99. package/dist/migrator/nodes/rules/action-list.js.map +1 -0
  100. package/dist/migrator/nodes/rules/action-slots.d.ts +13 -0
  101. package/dist/migrator/nodes/rules/action-slots.d.ts.map +1 -0
  102. package/dist/migrator/nodes/rules/action-slots.js +34 -0
  103. package/dist/migrator/nodes/rules/action-slots.js.map +1 -0
  104. package/dist/migrator/nodes/rules/custom-component.d.ts +7 -0
  105. package/dist/migrator/nodes/rules/custom-component.d.ts.map +1 -0
  106. package/dist/migrator/nodes/rules/custom-component.js +8 -0
  107. package/dist/migrator/nodes/rules/custom-component.js.map +1 -0
  108. package/dist/migrator/nodes/rules/dropdown.d.ts +4 -0
  109. package/dist/migrator/nodes/rules/dropdown.d.ts.map +1 -0
  110. package/dist/migrator/nodes/rules/dropdown.js +3 -0
  111. package/dist/migrator/nodes/rules/dropdown.js.map +1 -0
  112. package/dist/migrator/nodes/rules/execute-entity.d.ts +7 -0
  113. package/dist/migrator/nodes/rules/execute-entity.d.ts.map +1 -0
  114. package/dist/migrator/nodes/rules/execute-entity.js +40 -0
  115. package/dist/migrator/nodes/rules/execute-entity.js.map +1 -0
  116. package/dist/migrator/nodes/rules/field-row.d.ts +4 -0
  117. package/dist/migrator/nodes/rules/field-row.d.ts.map +1 -0
  118. package/dist/migrator/nodes/rules/field-row.js +3 -0
  119. package/dist/migrator/nodes/rules/field-row.js.map +1 -0
  120. package/dist/migrator/nodes/rules/index.d.ts +16 -0
  121. package/dist/migrator/nodes/rules/index.d.ts.map +1 -0
  122. package/dist/migrator/nodes/rules/index.js +42 -0
  123. package/dist/migrator/nodes/rules/index.js.map +1 -0
  124. package/dist/migrator/nodes/rules/list-item.d.ts +7 -0
  125. package/dist/migrator/nodes/rules/list-item.d.ts.map +1 -0
  126. package/dist/migrator/nodes/rules/list-item.js +40 -0
  127. package/dist/migrator/nodes/rules/list-item.js.map +1 -0
  128. package/dist/migrator/nodes/rules/list.d.ts +4 -0
  129. package/dist/migrator/nodes/rules/list.d.ts.map +1 -0
  130. package/dist/migrator/nodes/rules/list.js +7 -0
  131. package/dist/migrator/nodes/rules/list.js.map +1 -0
  132. package/dist/migrator/nodes/rules/method-names.d.ts +10 -0
  133. package/dist/migrator/nodes/rules/method-names.d.ts.map +1 -0
  134. package/dist/migrator/nodes/rules/method-names.js +33 -0
  135. package/dist/migrator/nodes/rules/method-names.js.map +1 -0
  136. package/dist/migrator/nodes/rules/screen-slots.d.ts +10 -0
  137. package/dist/migrator/nodes/rules/screen-slots.d.ts.map +1 -0
  138. package/dist/migrator/nodes/rules/screen-slots.js +9 -0
  139. package/dist/migrator/nodes/rules/screen-slots.js.map +1 -0
  140. package/dist/migrator/nodes/rules/set-jig-state.d.ts +4 -0
  141. package/dist/migrator/nodes/rules/set-jig-state.d.ts.map +1 -0
  142. package/dist/migrator/nodes/rules/set-jig-state.js +9 -0
  143. package/dist/migrator/nodes/rules/set-jig-state.js.map +1 -0
  144. package/dist/migrator/nodes/rules/state-key.d.ts +7 -0
  145. package/dist/migrator/nodes/rules/state-key.d.ts.map +1 -0
  146. package/dist/migrator/nodes/rules/state-key.js +16 -0
  147. package/dist/migrator/nodes/rules/state-key.js.map +1 -0
  148. package/dist/migrator/nodes/rules/summary.d.ts +7 -0
  149. package/dist/migrator/nodes/rules/summary.d.ts.map +1 -0
  150. package/dist/migrator/nodes/rules/summary.js +16 -0
  151. package/dist/migrator/nodes/rules/summary.js.map +1 -0
  152. package/dist/migrator/nodes/rules/sync-entities.d.ts +7 -0
  153. package/dist/migrator/nodes/rules/sync-entities.d.ts.map +1 -0
  154. package/dist/migrator/nodes/rules/sync-entities.js +17 -0
  155. package/dist/migrator/nodes/rules/sync-entities.js.map +1 -0
  156. package/dist/migrator/nodes/rules/types.d.ts +35 -0
  157. package/dist/migrator/nodes/rules/types.d.ts.map +1 -0
  158. package/dist/migrator/nodes/rules/types.js +7 -0
  159. package/dist/migrator/nodes/rules/types.js.map +1 -0
  160. package/dist/migrator/nodes/screen.d.ts +13 -0
  161. package/dist/migrator/nodes/screen.d.ts.map +1 -0
  162. package/dist/migrator/nodes/screen.js +403 -0
  163. package/dist/migrator/nodes/screen.js.map +1 -0
  164. package/dist/migrator/normalize.d.ts +42 -0
  165. package/dist/migrator/normalize.d.ts.map +1 -0
  166. package/dist/migrator/normalize.js +167 -0
  167. package/dist/migrator/normalize.js.map +1 -0
  168. package/dist/migrator/project-writer.d.ts +34 -0
  169. package/dist/migrator/project-writer.d.ts.map +1 -0
  170. package/dist/migrator/project-writer.js +65 -0
  171. package/dist/migrator/project-writer.js.map +1 -0
  172. package/dist/migrator/prune.d.ts +58 -0
  173. package/dist/migrator/prune.d.ts.map +1 -0
  174. package/dist/migrator/prune.js +171 -0
  175. package/dist/migrator/prune.js.map +1 -0
  176. package/dist/migrator/read-solution.d.ts +38 -0
  177. package/dist/migrator/read-solution.d.ts.map +1 -0
  178. package/dist/migrator/read-solution.js +144 -0
  179. package/dist/migrator/read-solution.js.map +1 -0
  180. package/dist/migrator/report.d.ts +76 -0
  181. package/dist/migrator/report.d.ts.map +1 -0
  182. package/dist/migrator/report.js +53 -0
  183. package/dist/migrator/report.js.map +1 -0
  184. package/dist/migrator/ts-printer.d.ts +19 -0
  185. package/dist/migrator/ts-printer.d.ts.map +1 -0
  186. package/dist/migrator/ts-printer.js +70 -0
  187. package/dist/migrator/ts-printer.js.map +1 -0
  188. package/dist/migrator/verify-entity.d.ts +21 -0
  189. package/dist/migrator/verify-entity.d.ts.map +1 -0
  190. package/dist/migrator/verify-entity.js +22 -0
  191. package/dist/migrator/verify-entity.js.map +1 -0
  192. package/dist/scaffolder/index.d.ts +1 -0
  193. package/dist/scaffolder/index.d.ts.map +1 -1
  194. package/dist/scaffolder/index.js +1 -0
  195. package/dist/scaffolder/index.js.map +1 -1
  196. package/dist/scaffolder/templates.d.ts +11 -2
  197. package/dist/scaffolder/templates.d.ts.map +1 -1
  198. package/dist/scaffolder/templates.js +7 -3
  199. package/dist/scaffolder/templates.js.map +1 -1
  200. package/package.json +2 -1
@@ -0,0 +1,25 @@
1
+ import { countNodes } from '../report.js';
2
+ import { lit, str } from '../ts-printer.js';
3
+ /** `translations/<locale>.jigx` → `app.translation('<locale>', { … })` (node-free data only). */
4
+ export function convertTranslation(entry) {
5
+ const { id, entity } = entry;
6
+ if (countNodes({ value: entity, asRoot: false }) > 0) {
7
+ return {
8
+ type: 'skip',
9
+ gapId: 'translations:contains-nodes',
10
+ reason: 'app.translation() takes untyped data, which must not carry component or action nodes',
11
+ };
12
+ }
13
+ return {
14
+ type: 'statements',
15
+ statements: [
16
+ {
17
+ code: `app.translation(${str(id)}, ${lit(entity, ' ')})`,
18
+ run: (app) => {
19
+ app.translation(id, entity);
20
+ },
21
+ },
22
+ ],
23
+ };
24
+ }
25
+ //# sourceMappingURL=translations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"translations.js","sourceRoot":"","sources":["../../../src/migrator/converters/translations.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AACzC,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,kBAAkB,CAAA;AAG3C,iGAAiG;AACjG,MAAM,UAAU,kBAAkB,CAAC,KAAkB;IACnD,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,GAAG,KAAK,CAAA;IAC5B,IAAI,UAAU,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC;QACrD,OAAO;YACL,IAAI,EAAE,MAAM;YACZ,KAAK,EAAE,6BAA6B;YACpC,MAAM,EACJ,sFAAsF;SACzF,CAAA;IACH,CAAC;IACD,OAAO;QACL,IAAI,EAAE,YAAY;QAClB,UAAU,EAAE;YACV;gBACE,IAAI,EAAE,mBAAmB,GAAG,CAAC,EAAE,CAAC,KAAK,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,GAAG;gBACzD,GAAG,EAAE,CAAC,GAAG,EAAE,EAAE;oBACX,GAAG,CAAC,WAAW,CAAC,EAAE,EAAE,MAAM,CAAC,CAAA;gBAC7B,CAAC;aACF;SACF;KACF,CAAA;AACH,CAAC"}
@@ -0,0 +1,35 @@
1
+ import { type ApplicationBuilder } from '@jigx/core-sdk';
2
+ import { type LegacyEntry } from '../read-solution.js';
3
+ import { type NodeGap } from '../report.js';
4
+ /**
5
+ * One typed core-sdk call: its generated source and the same call, replayed in-process.
6
+ *
7
+ * `code` is written at the register-body indent; continuation lines carry their own indent.
8
+ */
9
+ export interface Statement {
10
+ readonly code: string;
11
+ readonly run: (app: ApplicationBuilder) => void;
12
+ }
13
+ /** An entity with no exact typed mapping: no module is generated. */
14
+ interface EntitySkip {
15
+ readonly type: 'skip';
16
+ readonly gapId: string;
17
+ readonly reason: string;
18
+ /** Node type of the gap; defaults to the entity kind. */
19
+ readonly nodeType?: string;
20
+ }
21
+ /** An entity migrated into typed statements. */
22
+ interface EntityStatements {
23
+ readonly type: 'statements';
24
+ readonly statements: readonly Statement[];
25
+ /** Extra import lines the statements need. */
26
+ readonly imports?: readonly string[];
27
+ /** Files copied verbatim into the project, keyed by project relPath. */
28
+ readonly copies?: Readonly<Record<string, string>>;
29
+ /** Nodes skipped inside the entity (node-level gaps). */
30
+ readonly gaps?: readonly NodeGap[];
31
+ }
32
+ export type EntityConversion = EntitySkip | EntityStatements;
33
+ export type EntityConverter = (entry: LegacyEntry) => EntityConversion;
34
+ export {};
35
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/migrator/converters/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,kBAAkB,EAAE,MAAM,gBAAgB,CAAA;AACxD,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,qBAAqB,CAAA;AACtD,OAAO,EAAE,KAAK,OAAO,EAAE,MAAM,cAAc,CAAA;AAE3C;;;;GAIG;AACH,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,kBAAkB,KAAK,IAAI,CAAA;CAChD;AAED,qEAAqE;AACrE,UAAU,UAAU;IAClB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,yDAAyD;IACzD,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAC3B;AAED,gDAAgD;AAChD,UAAU,gBAAgB;IACxB,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAA;IAC3B,QAAQ,CAAC,UAAU,EAAE,SAAS,SAAS,EAAE,CAAA;IACzC,8CAA8C;IAC9C,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACpC,wEAAwE;IACxE,QAAQ,CAAC,MAAM,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;IAClD,yDAAyD;IACzD,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,OAAO,EAAE,CAAA;CACnC;AAED,MAAM,MAAM,gBAAgB,GAAG,UAAU,GAAG,gBAAgB,CAAA;AAE5D,MAAM,MAAM,eAAe,GAAG,CAAC,KAAK,EAAE,WAAW,KAAK,gBAAgB,CAAA"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/migrator/converters/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,5 @@
1
+ /** Thrown when a legacy solution cannot be migrated (invalid input, unsafe output location, unreadable YAML). */
2
+ export declare class MigrateError extends Error {
3
+ constructor(message: string);
4
+ }
5
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/migrator/errors.ts"],"names":[],"mappings":"AAAA,iHAAiH;AACjH,qBAAa,YAAa,SAAQ,KAAK;IACrC,YAAY,OAAO,EAAE,MAAM,EAG1B;CACF"}
@@ -0,0 +1,8 @@
1
+ /** Thrown when a legacy solution cannot be migrated (invalid input, unsafe output location, unreadable YAML). */
2
+ export class MigrateError extends Error {
3
+ constructor(message) {
4
+ super(message);
5
+ this.name = 'MigrateError';
6
+ }
7
+ }
8
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/migrator/errors.ts"],"names":[],"mappings":"AAAA,iHAAiH;AACjH,MAAM,OAAO,YAAa,SAAQ,KAAK;IACrC,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAA;QACd,IAAI,CAAC,IAAI,GAAG,cAAc,CAAA;IAC5B,CAAC;CACF"}
@@ -0,0 +1,6 @@
1
+ export { MigrateError } from './errors.js';
2
+ export { checkBuild, migrate } from './migrate.js';
3
+ export type { CheckBuildOptions, CheckBuildResult, EntityMismatch, MigrateOptions, MigrateResult, } from './migrate.js';
4
+ export { summarizeGaps } from './report.js';
5
+ export type { GapRecord, MigrationReport } from './report.js';
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/migrator/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAC1C,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAClD,YAAY,EACV,iBAAiB,EACjB,gBAAgB,EAChB,cAAc,EACd,cAAc,EACd,aAAa,GACd,MAAM,cAAc,CAAA;AACrB,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAC3C,YAAY,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA"}
@@ -0,0 +1,4 @@
1
+ export { MigrateError } from './errors.js';
2
+ export { checkBuild, migrate } from './migrate.js';
3
+ export { summarizeGaps } from './report.js';
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/migrator/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAC1C,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAQlD,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA"}
@@ -0,0 +1,75 @@
1
+ import { type ProjectFile } from './project-writer.js';
2
+ import { type EntityKind, type LegacyEntry } from './read-solution.js';
3
+ import { type MigrationReport } from './report.js';
4
+ export interface MigrateOptions {
5
+ /** Legacy solution folder (holds `index.jigx` and the kind folders). */
6
+ readonly solutionDir: string;
7
+ /** Project dir to write. Defaults to `<cwd>/<index name>`. */
8
+ readonly outDir?: string;
9
+ /** Overwrite an existing out dir (its `src/` is replaced; other files are kept). */
10
+ readonly force?: boolean;
11
+ /** Base for relative paths. Defaults to `process.cwd()`. */
12
+ readonly cwd?: string;
13
+ }
14
+ export interface MigrateResult {
15
+ readonly outDir: string;
16
+ readonly report: MigrationReport;
17
+ /** Absolute paths of every file written. */
18
+ readonly filesWritten: readonly string[];
19
+ }
20
+ export interface PlanMigrationOptions {
21
+ readonly entries: readonly LegacyEntry[];
22
+ readonly ignored?: readonly string[];
23
+ }
24
+ export interface MigrationPlan {
25
+ readonly applicationName: string;
26
+ /** Every project file, including `migration-report.json`. */
27
+ readonly files: readonly ProjectFile[];
28
+ readonly report: MigrationReport;
29
+ /** The in-process build of the replayed, verified calls. */
30
+ readonly built: Readonly<Record<string, unknown>>;
31
+ }
32
+ /**
33
+ * Plans a migration in memory: converts every entry, replays the typed calls on one
34
+ * ApplicationBuilder, verifies each entity against the pruned legacy (demoting
35
+ * mismatches to `<kind>:composition-differs`) and renders the project files.
36
+ * @throws MigrateError when there is no valid `index.jigx` entry
37
+ */
38
+ export declare function planMigration(options: PlanMigrationOptions): MigrationPlan;
39
+ /**
40
+ * Migrates a legacy YAML solution into a typed core-sdk TypeScript project.
41
+ *
42
+ * Writes `package.json`, `tsconfig.json`, `.gitignore`, `src/app.ts`, one module per
43
+ * migrated entity, verbatim scripts and `migration-report.json` into `outDir`.
44
+ * @throws MigrateError when the solution is invalid, or `outDir` exists (without `force`)
45
+ * or overlaps the solution dir
46
+ */
47
+ export declare function migrate(options: MigrateOptions): Promise<MigrateResult>;
48
+ export interface CheckBuildOptions {
49
+ /** Legacy solution folder the project was migrated from. */
50
+ readonly solutionDir: string;
51
+ /** Migrated project dir holding `migration-report.json` and `build/output.json` (after `jigx build`). */
52
+ readonly projectDir: string;
53
+ /** Base for relative paths. Defaults to `process.cwd()`. */
54
+ readonly cwd?: string;
55
+ }
56
+ /** One entity whose built output differs from the pruned legacy. */
57
+ export interface EntityMismatch {
58
+ readonly kind: EntityKind;
59
+ readonly id: string;
60
+ /** Top-level keys whose values differ; empty when the entity is missing on one side. */
61
+ readonly differingKeys: readonly string[];
62
+ }
63
+ export interface CheckBuildResult {
64
+ /** Entities compared (both sides, index included). */
65
+ readonly checked: number;
66
+ readonly mismatches: readonly EntityMismatch[];
67
+ }
68
+ /**
69
+ * Checks an already built migrated project: compares its `build/output.json` with the
70
+ * pruned legacy solution, entity by entity, using the gaps in its `migration-report.json`.
71
+ * Proves the generated source builds to the same result as the in-process replay. Writes nothing.
72
+ * @throws MigrateError when the solution is invalid, or the report or build output is missing or malformed
73
+ */
74
+ export declare function checkBuild(options: CheckBuildOptions): Promise<CheckBuildResult>;
75
+ //# sourceMappingURL=migrate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migrate.d.ts","sourceRoot":"","sources":["../../src/migrator/migrate.ts"],"names":[],"mappings":"AA8BA,OAAO,EAAiB,KAAK,WAAW,EAAsB,MAAM,qBAAqB,CAAA;AAEzF,OAAO,EAGL,KAAK,UAAU,EACf,KAAK,WAAW,EAEjB,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EAGL,KAAK,eAAe,EAGrB,MAAM,aAAa,CAAA;AAGpB,MAAM,WAAW,cAAc;IAC7B,wEAAwE;IACxE,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,8DAA8D;IAC9D,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;IACxB,oFAAoF;IACpF,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAA;IACxB,4DAA4D;IAC5D,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CACtB;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,MAAM,EAAE,eAAe,CAAA;IAChC,4CAA4C;IAC5C,QAAQ,CAAC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAA;CACzC;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,OAAO,EAAE,SAAS,WAAW,EAAE,CAAA;IACxC,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CACrC;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAA;IAChC,6DAA6D;IAC7D,QAAQ,CAAC,KAAK,EAAE,SAAS,WAAW,EAAE,CAAA;IACtC,QAAQ,CAAC,MAAM,EAAE,eAAe,CAAA;IAChC,4DAA4D;IAC5D,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;CAClD;AAyQD;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,oBAAoB,GAAG,aAAa,CA+C1E;AAgCD;;;;;;;GAOG;AACH,wBAAsB,OAAO,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC,CA8B7E;AAED,MAAM,WAAW,iBAAiB;IAChC,4DAA4D;IAC5D,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,yGAAyG;IACzG,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,4DAA4D;IAC5D,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CACtB;AAED,oEAAoE;AACpE,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAA;IACzB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,wFAAwF;IACxF,QAAQ,CAAC,aAAa,EAAE,SAAS,MAAM,EAAE,CAAA;CAC1C;AAED,MAAM,WAAW,gBAAgB;IAC/B,sDAAsD;IACtD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,UAAU,EAAE,SAAS,cAAc,EAAE,CAAA;CAC/C;AA2CD;;;;;GAKG;AACH,wBAAsB,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CA8CtF"}
@@ -0,0 +1,439 @@
1
+ import { mkdir, readFile, realpath, rm, stat, writeFile } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { ApplicationBuilder, PACKAGE } from '@jigx/core-sdk';
4
+ import { applicationInput, convertAsset, convertComponent, convertDatabase, convertDatasource, convertFunction, convertIndex, convertScript, convertTranslation, indexKeyGapId, renderIndexModule, SCRIPTS_MODULE_PATH, } from './converters/index.js';
5
+ import { MigrateError } from './errors.js';
6
+ import { renderModule } from './module-writer.js';
7
+ import { convertAction, convertJig } from './nodes/index.js';
8
+ import { assembleLegacy, isPlainObject, toComparable, } from './normalize.js';
9
+ import { renderProjectFiles } from './project-writer.js';
10
+ import { prune, pruneBuilt } from './prune.js';
11
+ import { compareText, ENTITY_KINDS, readSolution, } from './read-solution.js';
12
+ import { countNodes, sortGaps, } from './report.js';
13
+ import { verifyEntity } from './verify-entity.js';
14
+ const REPORT_FILE = 'migration-report.json';
15
+ const CONVERTERS = {
16
+ databases: convertDatabase,
17
+ functions: convertFunction,
18
+ datasources: convertDatasource,
19
+ actions: convertAction,
20
+ components: convertComponent,
21
+ jigs: convertJig,
22
+ translations: convertTranslation,
23
+ assets: convertAsset,
24
+ scripts: convertScript,
25
+ };
26
+ function compositionGapId(kind) {
27
+ return `${kind}:composition-differs`;
28
+ }
29
+ function firstLine(error) {
30
+ const message = error instanceof Error ? error.message : String(error);
31
+ return message.split('\n')[0] ?? message;
32
+ }
33
+ function demoteEntity(states, position, reason) {
34
+ const state = states.at(position);
35
+ if (!state)
36
+ return;
37
+ states[position] = {
38
+ entry: state.entry,
39
+ conversion: { type: 'skip', gapId: compositionGapId(state.entry.kind), reason },
40
+ };
41
+ }
42
+ function demoteIndexKey(keys, key, reason) {
43
+ const position = keys.findIndex((item) => item.key === key);
44
+ if (position === -1)
45
+ return false;
46
+ const current = keys.at(position);
47
+ if (!current || current.type === 'skip' || key === 'name')
48
+ return false;
49
+ keys[position] = { type: 'skip', key, gapId: indexKeyGapId(key), reason };
50
+ return true;
51
+ }
52
+ function collectGaps(options) {
53
+ const { indexEntry, indexKeys, states } = options;
54
+ const legacyIndex = isPlainObject(indexEntry.entity) ? indexEntry.entity : {};
55
+ const gaps = [];
56
+ const nodeGaps = (relPath, list = []) => {
57
+ for (const { path, ...gap } of list)
58
+ gaps.push({ ...gap, location: { relPath, path } });
59
+ };
60
+ for (const key of indexKeys) {
61
+ if (key.type === 'statements')
62
+ nodeGaps(indexEntry.relPath, key.gaps);
63
+ if (key.type !== 'skip')
64
+ continue;
65
+ gaps.push({
66
+ gapId: key.gapId,
67
+ nodeType: key.nodeType ?? 'index',
68
+ reason: key.reason,
69
+ skippedNodes: countNodes({ value: legacyIndex[key.key], asRoot: false }),
70
+ location: { relPath: indexEntry.relPath, path: [key.key] },
71
+ });
72
+ }
73
+ for (const { entry, conversion } of states) {
74
+ if (conversion.type === 'statements') {
75
+ nodeGaps(entry.relPath, conversion.gaps);
76
+ continue;
77
+ }
78
+ gaps.push({
79
+ gapId: conversion.gapId,
80
+ nodeType: conversion.nodeType ?? entry.kind,
81
+ reason: conversion.reason,
82
+ skippedNodes: countNodes({ value: entry.entity, asRoot: true }),
83
+ location: { relPath: entry.relPath, path: [] },
84
+ });
85
+ }
86
+ return sortGaps(gaps);
87
+ }
88
+ /** Replays the index and every verified entity on one ApplicationBuilder; demotes what throws. */
89
+ function compose(options) {
90
+ const { index, indexKeys, states } = options;
91
+ const app = new ApplicationBuilder(applicationInput({ ...index, keys: indexKeys }));
92
+ for (const key of indexKeys) {
93
+ if (key.type !== 'statements')
94
+ continue;
95
+ try {
96
+ for (const statement of key.statements)
97
+ statement.run(app);
98
+ }
99
+ catch (error) {
100
+ demoteIndexKey(indexKeys, key.key, `replaying the typed calls throws: ${firstLine(error)}`);
101
+ return undefined;
102
+ }
103
+ }
104
+ for (const [position, { conversion }] of states.entries()) {
105
+ if (conversion.type !== 'statements')
106
+ continue;
107
+ try {
108
+ for (const statement of conversion.statements)
109
+ statement.run(app);
110
+ }
111
+ catch (error) {
112
+ demoteEntity(states, position, `replaying the typed calls throws: ${firstLine(error)}`);
113
+ return undefined;
114
+ }
115
+ }
116
+ return app;
117
+ }
118
+ /**
119
+ * Builds the composed Application and verifies every entity against the pruned legacy.
120
+ * Returns the build when everything verifies; demotes the first mismatches otherwise.
121
+ */
122
+ function verifyComposition(options) {
123
+ const { app, entries, indexEntry, indexKeys, states } = options;
124
+ let built;
125
+ try {
126
+ built = app.build();
127
+ }
128
+ catch (error) {
129
+ throw new MigrateError(`Building the migrated Application failed: ${firstLine(error)}`);
130
+ }
131
+ const gaps = collectGaps({ indexEntry, indexKeys, states });
132
+ const builtComparable = pruneBuilt({ built: toComparable(built), entries, gaps });
133
+ const legacyComparable = toComparable(assembleLegacy(prune({ entries, gaps })));
134
+ let demoted = false;
135
+ const index = verifyEntity({
136
+ kind: 'index',
137
+ id: 'index',
138
+ built: builtComparable,
139
+ legacy: legacyComparable,
140
+ });
141
+ for (const key of index.differingKeys) {
142
+ if (demoteIndexKey(indexKeys, key, 'the composed index differs from the legacy index')) {
143
+ demoted = true;
144
+ }
145
+ else {
146
+ throw new MigrateError(`${indexEntry.relPath}: the built index differs from the legacy index in "${key}", which cannot be skipped`);
147
+ }
148
+ }
149
+ if (!index.equal && index.differingKeys.length === 0) {
150
+ throw new MigrateError(`${indexEntry.relPath}: the built index differs from the legacy index`);
151
+ }
152
+ for (const [position, { entry, conversion }] of states.entries()) {
153
+ if (conversion.type === 'skip')
154
+ continue;
155
+ const { kind, id } = entry;
156
+ const { equal, differingKeys } = verifyEntity({
157
+ kind,
158
+ id,
159
+ built: builtComparable,
160
+ legacy: legacyComparable,
161
+ });
162
+ if (!equal) {
163
+ const where = differingKeys.length > 0 ? ` in: ${differingKeys.join(', ')}` : '';
164
+ demoteEntity(states, position, `the composed entity differs from the legacy entity${where}`);
165
+ demoted = true;
166
+ }
167
+ }
168
+ return demoted ? undefined : built;
169
+ }
170
+ /** Module path under `src/` for a legacy relPath: extension dropped, URL-unsafe characters replaced. */
171
+ function modulePathFor(relPath) {
172
+ return relPath
173
+ .replace(/\.jigx$/, '')
174
+ .split('/')
175
+ .map((segment) => segment.replace(/[^A-Za-z0-9._ -]/g, '-'))
176
+ .join('/');
177
+ }
178
+ function uniqueModulePath(relPath, used) {
179
+ const base = modulePathFor(relPath);
180
+ let candidate = base;
181
+ for (let suffix = 2; used.has(candidate.toLowerCase()); suffix++)
182
+ candidate = `${base}-${suffix}`;
183
+ used.add(candidate.toLowerCase());
184
+ return candidate;
185
+ }
186
+ function renderFiles(options) {
187
+ const { indexEntry, index, states, report } = options;
188
+ const files = [
189
+ {
190
+ relPath: 'src/index.ts',
191
+ content: renderIndexModule({ relPath: indexEntry.relPath, conversion: index }),
192
+ },
193
+ ];
194
+ const appEntries = [{ type: 'module', modulePath: 'index' }];
195
+ const usedPaths = new Set(['index', 'app', SCRIPTS_MODULE_PATH]);
196
+ const scriptSources = [];
197
+ const scriptImports = new Set();
198
+ const scriptBody = [];
199
+ for (const { entry, conversion } of states) {
200
+ if (conversion.type === 'skip') {
201
+ appEntries.push({
202
+ type: 'todo',
203
+ gapId: conversion.gapId,
204
+ reason: conversion.reason,
205
+ relPath: entry.relPath,
206
+ });
207
+ continue;
208
+ }
209
+ for (const [relPath, content] of Object.entries(conversion.copies ?? {})) {
210
+ files.push({ relPath, content });
211
+ }
212
+ const body = conversion.statements.map((statement) => statement.code);
213
+ if (entry.kind === 'scripts') {
214
+ if (scriptSources.length === 0)
215
+ appEntries.push({ type: 'module', modulePath: SCRIPTS_MODULE_PATH });
216
+ scriptSources.push(entry.relPath);
217
+ for (const line of conversion.imports ?? [])
218
+ scriptImports.add(line);
219
+ scriptBody.push(...body);
220
+ continue;
221
+ }
222
+ const modulePath = uniqueModulePath(entry.relPath, usedPaths);
223
+ appEntries.push({ type: 'module', modulePath });
224
+ files.push({
225
+ relPath: `src/${modulePath}.ts`,
226
+ content: renderModule({ sources: [entry.relPath], imports: conversion.imports, body }),
227
+ });
228
+ }
229
+ if (scriptSources.length > 0) {
230
+ files.push({
231
+ relPath: `src/${SCRIPTS_MODULE_PATH}.ts`,
232
+ content: renderModule({
233
+ sources: scriptSources,
234
+ imports: [...scriptImports],
235
+ body: scriptBody,
236
+ }),
237
+ });
238
+ }
239
+ return [
240
+ ...renderProjectFiles({
241
+ applicationName: index.name,
242
+ coreSdkVersion: report.coreSdkVersion,
243
+ entries: appEntries,
244
+ }),
245
+ ...files,
246
+ { relPath: REPORT_FILE, content: `${JSON.stringify(report, null, 2)}\n` },
247
+ ];
248
+ }
249
+ /**
250
+ * Plans a migration in memory: converts every entry, replays the typed calls on one
251
+ * ApplicationBuilder, verifies each entity against the pruned legacy (demoting
252
+ * mismatches to `<kind>:composition-differs`) and renders the project files.
253
+ * @throws MigrateError when there is no valid `index.jigx` entry
254
+ */
255
+ export function planMigration(options) {
256
+ const { entries, ignored = [] } = options;
257
+ const indexEntry = entries.find((entry) => entry.kind === 'index');
258
+ if (!indexEntry)
259
+ throw new MigrateError('Missing index.jigx');
260
+ const index = convertIndex(indexEntry);
261
+ const indexKeys = [...index.keys];
262
+ const states = entries
263
+ .filter((entry) => entry.kind !== 'index')
264
+ .map((entry) => ({ entry, conversion: CONVERTERS[entry.kind](entry) }));
265
+ // Each round either verifies everything or demotes at least one key or entity.
266
+ const maxRounds = indexKeys.length + states.length + 1;
267
+ let built;
268
+ for (let round = 0; round < maxRounds && !built; round++) {
269
+ const app = compose({ index, indexKeys, states });
270
+ if (app)
271
+ built = verifyComposition({ app, entries, indexEntry, indexKeys, states });
272
+ }
273
+ if (!built)
274
+ throw new MigrateError('Entity verification did not converge');
275
+ const gaps = collectGaps({ indexEntry, indexKeys, states });
276
+ const totalNodes = entries.reduce((sum, entry) => sum + countNodes({ value: entry.entity, asRoot: true }), 0);
277
+ const skippedNodes = gaps.reduce((sum, gap) => sum + gap.skippedNodes, 0);
278
+ const skippedEntities = states.filter((state) => state.conversion.type === 'skip').length;
279
+ const report = {
280
+ coreSdkVersion: PACKAGE.VERSION,
281
+ entities: {
282
+ total: entries.length,
283
+ migrated: entries.length - skippedEntities,
284
+ skipped: skippedEntities,
285
+ },
286
+ nodes: { total: totalNodes, typed: totalNodes - skippedNodes, skipped: skippedNodes },
287
+ gaps,
288
+ ignored,
289
+ };
290
+ const finalIndex = { ...index, keys: indexKeys };
291
+ return {
292
+ applicationName: index.name,
293
+ files: renderFiles({ indexEntry, index: finalIndex, states, report }),
294
+ report,
295
+ built,
296
+ };
297
+ }
298
+ async function exists(filePath) {
299
+ try {
300
+ await stat(filePath);
301
+ return true;
302
+ }
303
+ catch (error) {
304
+ if (error instanceof Error && 'code' in error && error.code === 'ENOENT')
305
+ return false;
306
+ throw error;
307
+ }
308
+ }
309
+ /** Resolves symlinks of the longest existing prefix of `target` (the rest may not exist yet). */
310
+ async function realpathLoose(target) {
311
+ const rest = [];
312
+ let current = target;
313
+ for (;;) {
314
+ try {
315
+ return path.join(await realpath(current), ...rest.reverse());
316
+ }
317
+ catch {
318
+ const parent = path.dirname(current);
319
+ if (parent === current)
320
+ return target;
321
+ rest.push(path.basename(current));
322
+ current = parent;
323
+ }
324
+ }
325
+ }
326
+ function overlaps(a, b) {
327
+ return a === b || a.startsWith(`${b}${path.sep}`) || b.startsWith(`${a}${path.sep}`);
328
+ }
329
+ /**
330
+ * Migrates a legacy YAML solution into a typed core-sdk TypeScript project.
331
+ *
332
+ * Writes `package.json`, `tsconfig.json`, `.gitignore`, `src/app.ts`, one module per
333
+ * migrated entity, verbatim scripts and `migration-report.json` into `outDir`.
334
+ * @throws MigrateError when the solution is invalid, or `outDir` exists (without `force`)
335
+ * or overlaps the solution dir
336
+ */
337
+ export async function migrate(options) {
338
+ const { force = false, cwd = process.cwd() } = options;
339
+ const solutionDir = path.resolve(cwd, options.solutionDir);
340
+ const solution = await readSolution({ solutionDir });
341
+ const plan = planMigration({ entries: solution.entries, ignored: solution.ignored });
342
+ const outDir = path.resolve(cwd, options.outDir ?? plan.applicationName);
343
+ const [realSolution, realOut] = await Promise.all([
344
+ realpathLoose(solutionDir),
345
+ realpathLoose(outDir),
346
+ ]);
347
+ if (overlaps(solutionDir, outDir) || overlaps(realSolution, realOut)) {
348
+ throw new MigrateError(`Out dir ${outDir} must not be, contain, or sit inside the solution dir ${solutionDir}`);
349
+ }
350
+ if (await exists(outDir)) {
351
+ if (!force)
352
+ throw new MigrateError(`Out dir already exists: ${outDir}. Pass --force to overwrite it.`);
353
+ await rm(path.join(outDir, 'src'), { recursive: true, force: true });
354
+ }
355
+ const filesWritten = [];
356
+ for (const file of plan.files) {
357
+ const absPath = path.join(outDir, file.relPath);
358
+ await mkdir(path.dirname(absPath), { recursive: true });
359
+ await writeFile(absPath, file.content, 'utf8');
360
+ filesWritten.push(absPath);
361
+ }
362
+ return { outDir, report: plan.report, filesWritten };
363
+ }
364
+ function isGapLocationRecord(value) {
365
+ if (!isPlainObject(value) || !isPlainObject(value.location))
366
+ return false;
367
+ const { relPath, path: nodePath } = value.location;
368
+ return (typeof relPath === 'string' &&
369
+ Array.isArray(nodePath) &&
370
+ nodePath.every((segment) => typeof segment === 'string' || typeof segment === 'number'));
371
+ }
372
+ async function readJson(options) {
373
+ const { filePath, hint } = options;
374
+ let text;
375
+ try {
376
+ text = await readFile(filePath, 'utf8');
377
+ }
378
+ catch (error) {
379
+ if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {
380
+ throw new MigrateError(`Missing ${filePath}. ${hint}`);
381
+ }
382
+ throw error;
383
+ }
384
+ try {
385
+ return JSON.parse(text);
386
+ }
387
+ catch (error) {
388
+ throw new MigrateError(`${filePath} is not valid JSON: ${firstLine(error)}`);
389
+ }
390
+ }
391
+ function entityKeys(comparable) {
392
+ return ENTITY_KINDS.flatMap((kind) => Object.keys(comparable[kind] ?? {}).map((id) => [kind, id]));
393
+ }
394
+ /**
395
+ * Checks an already built migrated project: compares its `build/output.json` with the
396
+ * pruned legacy solution, entity by entity, using the gaps in its `migration-report.json`.
397
+ * Proves the generated source builds to the same result as the in-process replay. Writes nothing.
398
+ * @throws MigrateError when the solution is invalid, or the report or build output is missing or malformed
399
+ */
400
+ export async function checkBuild(options) {
401
+ const { cwd = process.cwd() } = options;
402
+ const solutionDir = path.resolve(cwd, options.solutionDir);
403
+ const projectDir = path.resolve(cwd, options.projectDir);
404
+ const report = await readJson({
405
+ filePath: path.join(projectDir, REPORT_FILE),
406
+ hint: 'Run jigx migrate first.',
407
+ });
408
+ if (!isPlainObject(report) || !Array.isArray(report.gaps)) {
409
+ throw new MigrateError(`${path.join(projectDir, REPORT_FILE)} has no gaps list`);
410
+ }
411
+ const gaps = report.gaps.filter(isGapLocationRecord);
412
+ if (gaps.length !== report.gaps.length) {
413
+ throw new MigrateError(`${path.join(projectDir, REPORT_FILE)} has a gap without a valid location`);
414
+ }
415
+ const output = await readJson({
416
+ filePath: path.join(projectDir, 'build', 'output.json'),
417
+ hint: 'Run jigx build in the project first.',
418
+ });
419
+ if (!isPlainObject(output)) {
420
+ throw new MigrateError(`${path.join(projectDir, 'build', 'output.json')} is not an Application object`);
421
+ }
422
+ const solution = await readSolution({ solutionDir });
423
+ const built = pruneBuilt({ built: toComparable(output), entries: solution.entries, gaps });
424
+ const legacy = toComparable(assembleLegacy(prune({ entries: solution.entries, gaps })));
425
+ const seen = new Set();
426
+ const mismatches = [];
427
+ for (const [kind, id] of [...entityKeys(legacy), ...entityKeys(built)]) {
428
+ const key = JSON.stringify([kind, id]);
429
+ if (seen.has(key))
430
+ continue;
431
+ seen.add(key);
432
+ const { equal, differingKeys } = verifyEntity({ kind, id, built, legacy });
433
+ if (!equal)
434
+ mismatches.push({ kind, id, differingKeys });
435
+ }
436
+ mismatches.sort((a, b) => ENTITY_KINDS.indexOf(a.kind) - ENTITY_KINDS.indexOf(b.kind) || compareText(a.id, b.id));
437
+ return { checked: seen.size, mismatches };
438
+ }
439
+ //# sourceMappingURL=migrate.js.map