@wp-typia/create 0.1.0

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 (95) hide show
  1. package/README.md +43 -0
  2. package/dist/cli.js +2492 -0
  3. package/dist/runtime/cli-core.js +222 -0
  4. package/dist/runtime/index.js +4 -0
  5. package/dist/runtime/migration-constants.js +14 -0
  6. package/dist/runtime/migration-diff.js +521 -0
  7. package/dist/runtime/migration-fixtures.js +89 -0
  8. package/dist/runtime/migration-manifest.js +129 -0
  9. package/dist/runtime/migration-project.js +167 -0
  10. package/dist/runtime/migration-render.js +267 -0
  11. package/dist/runtime/migration-types.js +1 -0
  12. package/dist/runtime/migration-utils.js +184 -0
  13. package/dist/runtime/migrations.js +232 -0
  14. package/dist/runtime/package-managers.js +135 -0
  15. package/dist/runtime/scaffold.js +334 -0
  16. package/dist/runtime/template-registry.js +75 -0
  17. package/package.json +65 -0
  18. package/templates/advanced/README.md.mustache +150 -0
  19. package/templates/advanced/block.json.mustache +43 -0
  20. package/templates/advanced/index.js +21 -0
  21. package/templates/advanced/package.json.mustache +47 -0
  22. package/templates/advanced/render.php.mustache +83 -0
  23. package/templates/advanced/scripts/lib/typia-metadata-core.ts +1413 -0
  24. package/templates/advanced/scripts/sync-types-to-block-json.ts.mustache +32 -0
  25. package/templates/advanced/src/admin/migration-dashboard.tsx.mustache +315 -0
  26. package/templates/advanced/src/components/ErrorBoundary.tsx.mustache +47 -0
  27. package/templates/advanced/src/deprecated.ts.mustache +2 -0
  28. package/templates/advanced/src/edit.tsx.mustache +97 -0
  29. package/templates/advanced/src/hooks/useDebounce.ts.mustache +20 -0
  30. package/templates/advanced/src/hooks/useLocalStorage.ts.mustache +31 -0
  31. package/templates/advanced/src/hooks.ts.mustache +56 -0
  32. package/templates/advanced/src/index.tsx.mustache +18 -0
  33. package/templates/advanced/src/migration-detector.ts.mustache +9 -0
  34. package/templates/advanced/src/migrations/config.ts.mustache +8 -0
  35. package/templates/advanced/src/migrations/examples/rename-transform-union/README.md.mustache +23 -0
  36. package/templates/advanced/src/migrations/examples/rename-transform-union/fixture.example.json.mustache +36 -0
  37. package/templates/advanced/src/migrations/examples/rename-transform-union/rule.example.ts.mustache +47 -0
  38. package/templates/advanced/src/migrations/fixtures/README.md.mustache +3 -0
  39. package/templates/advanced/src/migrations/generated/deprecated.ts.mustache +3 -0
  40. package/templates/advanced/src/migrations/generated/registry.ts.mustache +9 -0
  41. package/templates/advanced/src/migrations/generated/verify.ts.mustache +1 -0
  42. package/templates/advanced/src/migrations/helpers.ts.mustache +354 -0
  43. package/templates/advanced/src/migrations/index.ts.mustache +616 -0
  44. package/templates/advanced/src/migrations/rules/README.md.mustache +3 -0
  45. package/templates/advanced/src/migrations/versions/README.md.mustache +3 -0
  46. package/templates/advanced/src/save.tsx.mustache +12 -0
  47. package/templates/advanced/src/style.scss.mustache +84 -0
  48. package/templates/advanced/src/types.ts.mustache +46 -0
  49. package/templates/advanced/src/utils/classnames.ts.mustache +51 -0
  50. package/templates/advanced/src/utils/debounce.ts.mustache +37 -0
  51. package/templates/advanced/src/utils/index.ts.mustache +7 -0
  52. package/templates/advanced/src/utils/uuid.ts.mustache +17 -0
  53. package/templates/advanced/src/validators.ts.mustache +39 -0
  54. package/templates/advanced/src/view.ts.mustache +59 -0
  55. package/templates/advanced/tsconfig.json.mustache +20 -0
  56. package/templates/advanced/webpack.config.js.mustache +95 -0
  57. package/templates/basic/package.json.mustache +39 -0
  58. package/templates/basic/scripts/lib/typia-metadata-core.ts +1413 -0
  59. package/templates/basic/scripts/sync-types-to-block-json.ts +25 -0
  60. package/templates/basic/src/block.json +51 -0
  61. package/templates/basic/src/edit.tsx +85 -0
  62. package/templates/basic/src/hooks.ts +75 -0
  63. package/templates/basic/src/index.tsx +37 -0
  64. package/templates/basic/src/save.tsx +27 -0
  65. package/templates/basic/src/style.scss +42 -0
  66. package/templates/basic/src/types.ts +48 -0
  67. package/templates/basic/src/validators.ts +39 -0
  68. package/templates/basic/tsconfig.json +20 -0
  69. package/templates/basic/webpack.config.js +89 -0
  70. package/templates/full/package.json.mustache +40 -0
  71. package/templates/full/scripts/lib/typia-metadata-core.ts +1413 -0
  72. package/templates/full/scripts/sync-types-to-block-json.ts.mustache +32 -0
  73. package/templates/full/src/block.json.mustache +120 -0
  74. package/templates/full/src/edit.tsx.mustache +300 -0
  75. package/templates/full/src/editor.scss.mustache +251 -0
  76. package/templates/full/src/hooks.ts.mustache +141 -0
  77. package/templates/full/src/index.tsx.mustache +27 -0
  78. package/templates/full/src/save.tsx.mustache +39 -0
  79. package/templates/full/src/style.scss.mustache +224 -0
  80. package/templates/full/src/types.ts.mustache +35 -0
  81. package/templates/full/src/validators.ts.mustache +84 -0
  82. package/templates/full/tsconfig.json.mustache +20 -0
  83. package/templates/full/webpack.config.js.mustache +89 -0
  84. package/templates/interactivity/package.json.mustache +41 -0
  85. package/templates/interactivity/scripts/lib/typia-metadata-core.ts +1413 -0
  86. package/templates/interactivity/scripts/sync-types-to-block-json.ts.mustache +32 -0
  87. package/templates/interactivity/src/block.json.mustache +74 -0
  88. package/templates/interactivity/src/edit.tsx.mustache +206 -0
  89. package/templates/interactivity/src/index.tsx.mustache +20 -0
  90. package/templates/interactivity/src/interactivity.ts.mustache +183 -0
  91. package/templates/interactivity/src/save.tsx.mustache +87 -0
  92. package/templates/interactivity/src/style.scss.mustache +60 -0
  93. package/templates/interactivity/src/types.ts.mustache +30 -0
  94. package/templates/interactivity/tsconfig.json.mustache +20 -0
  95. package/templates/interactivity/webpack.config.js.mustache +89 -0
@@ -0,0 +1,36 @@
1
+ {
2
+ "fromVersion": "1.0.0",
3
+ "toVersion": "2.0.0",
4
+ "cases": [
5
+ {
6
+ "name": "rename-and-transform",
7
+ "input": {
8
+ "headline": "Legacy title",
9
+ "count": "42",
10
+ "settings": {
11
+ "title": "Legacy label"
12
+ },
13
+ "linkTarget": {
14
+ "kind": "url",
15
+ "href": "https://example.com"
16
+ }
17
+ }
18
+ },
19
+ {
20
+ "name": "union-branch-review",
21
+ "input": {
22
+ "headline": "Needs branch migration",
23
+ "settings": {
24
+ "title": "Legacy label"
25
+ },
26
+ "cta": {
27
+ "href": "https://example.com/cta"
28
+ },
29
+ "linkTarget": {
30
+ "kind": "post",
31
+ "postId": 12
32
+ }
33
+ }
34
+ }
35
+ ]
36
+ }
@@ -0,0 +1,47 @@
1
+ import type { {{titleCase}}Attributes } from "../../../types";
2
+ import currentManifest from "../../../../typia.manifest.json";
3
+ import {
4
+ type RenameMap,
5
+ type TransformMap,
6
+ resolveMigrationAttribute,
7
+ } from "../../helpers";
8
+
9
+ export const renameMap: RenameMap = {
10
+ "content": "headline",
11
+ "settings.label": "settings.title",
12
+ };
13
+
14
+ export const transforms: TransformMap = {
15
+ "count": (legacyValue) => {
16
+ const numericValue =
17
+ typeof legacyValue === "number" ? legacyValue : Number(legacyValue ?? 0);
18
+ return Number.isNaN(numericValue) ? undefined : numericValue;
19
+ },
20
+ // Branch changes still need human review. The generated scaffold will keep this unresolved
21
+ // until you decide how to map the legacy branch contract.
22
+ "linkTarget.url.href": (legacyValue, legacyInput) => {
23
+ if (typeof legacyValue === "string") {
24
+ return legacyValue;
25
+ }
26
+
27
+ const fallbackHref =
28
+ typeof legacyInput.cta === "object" && legacyInput.cta && "href" in legacyInput.cta
29
+ ? legacyInput.cta.href
30
+ : undefined;
31
+
32
+ return typeof fallbackHref === "string" ? fallbackHref : undefined;
33
+ },
34
+ };
35
+
36
+ export const unresolved = [
37
+ "linkTarget: union-branch-removal (branch post was removed)",
38
+ ] as const;
39
+
40
+ export function migrate(input: Record<string, unknown>): {{titleCase}}Attributes {
41
+ return {
42
+ content: resolveMigrationAttribute(currentManifest.attributes.content, "content", "content", input, renameMap, transforms),
43
+ settings: resolveMigrationAttribute(currentManifest.attributes.settings, "settings", "settings", input, renameMap, transforms),
44
+ count: resolveMigrationAttribute(currentManifest.attributes.count, "count", "count", input, renameMap, transforms),
45
+ linkTarget: resolveMigrationAttribute(currentManifest.attributes.linkTarget, "linkTarget", "linkTarget", input, renameMap, transforms),
46
+ } as {{titleCase}}Attributes;
47
+ }
@@ -0,0 +1,3 @@
1
+ # Migration Fixtures
2
+
3
+ Generated fixtures are used by `wp-typia migrations verify`.
@@ -0,0 +1,3 @@
1
+ import type { BlockConfiguration } from "@wordpress/blocks";
2
+
3
+ export const deprecated: NonNullable<BlockConfiguration["deprecated"]> = [];
@@ -0,0 +1,9 @@
1
+ import currentManifest from "../../../typia.manifest.json";
2
+
3
+ export const migrationRegistry = {
4
+ currentVersion: "1.0.0",
5
+ currentManifest,
6
+ entries: [],
7
+ } as const;
8
+
9
+ export default migrationRegistry;
@@ -0,0 +1 @@
1
+ console.log("Run `wp-typia migrations scaffold --from <semver>` before verify.");
@@ -0,0 +1,354 @@
1
+ export interface ManifestUnion {
2
+ branches: Record<string, ManifestAttribute>;
3
+ discriminator: string;
4
+ }
5
+
6
+ export interface ManifestAttribute {
7
+ typia: {
8
+ constraints: {
9
+ format: string | null;
10
+ maxLength: number | null;
11
+ maximum: number | null;
12
+ minLength: number | null;
13
+ minimum: number | null;
14
+ pattern: string | null;
15
+ typeTag: string | null;
16
+ };
17
+ defaultValue: unknown;
18
+ hasDefault: boolean;
19
+ };
20
+ ts: {
21
+ items: ManifestAttribute | null;
22
+ kind: "string" | "number" | "boolean" | "array" | "object" | "union";
23
+ properties: Record<string, ManifestAttribute> | null;
24
+ required: boolean;
25
+ union: ManifestUnion | null;
26
+ };
27
+ wp: {
28
+ defaultValue: unknown;
29
+ enum: Array<string | number | boolean> | null;
30
+ hasDefault: boolean;
31
+ type: "string" | "number" | "boolean" | "array" | "object";
32
+ };
33
+ }
34
+
35
+ export interface ManifestDocument {
36
+ attributes: Record<string, ManifestAttribute>;
37
+ manifestVersion: 2;
38
+ sourceType: string;
39
+ }
40
+
41
+ export type RenameMap = Record<string, string>;
42
+ export type TransformMap = Record<
43
+ string,
44
+ (legacyValue: unknown, legacyInput: Record<string, unknown>) => unknown
45
+ >;
46
+
47
+ function getSourcePath(currentPath: string, fallbackPath: string, renameMap: RenameMap): string {
48
+ return renameMap[currentPath] ?? fallbackPath;
49
+ }
50
+
51
+ export function createDefaultValue(attribute: ManifestAttribute): unknown {
52
+ if (attribute.typia.hasDefault) {
53
+ return attribute.typia.defaultValue;
54
+ }
55
+ if (attribute.wp.enum && attribute.wp.enum.length > 0) {
56
+ return attribute.wp.enum[0];
57
+ }
58
+
59
+ switch (attribute.ts.kind) {
60
+ case "string":
61
+ return "";
62
+ case "number":
63
+ return 0;
64
+ case "boolean":
65
+ return false;
66
+ case "array":
67
+ return [];
68
+ case "object": {
69
+ const result: Record<string, unknown> = {};
70
+ for (const [key, property] of Object.entries(attribute.ts.properties ?? {})) {
71
+ result[key] = createDefaultValue(property);
72
+ }
73
+ return result;
74
+ }
75
+ case "union": {
76
+ const firstBranch = Object.values(attribute.ts.union?.branches ?? {})[0];
77
+ return firstBranch ? createDefaultValue(firstBranch) : null;
78
+ }
79
+ default:
80
+ return null;
81
+ }
82
+ }
83
+
84
+ export function getValueAtPath(input: Record<string, unknown>, path: string): unknown {
85
+ if (!path) {
86
+ return undefined;
87
+ }
88
+
89
+ return path.split(".").reduce<unknown>((value, segment) => {
90
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
91
+ return undefined;
92
+ }
93
+ return (value as Record<string, unknown>)[segment];
94
+ }, input);
95
+ }
96
+
97
+ export function resolveMigrationValue(
98
+ attribute: ManifestAttribute,
99
+ currentKey: string,
100
+ fallbackPath: string,
101
+ input: Record<string, unknown>,
102
+ renameMap: RenameMap,
103
+ transforms: TransformMap,
104
+ ): unknown {
105
+ const sourcePath = getSourcePath(currentKey, fallbackPath, renameMap);
106
+ const legacyValue = getValueAtPath(input, sourcePath);
107
+ const transformedValue = transforms[currentKey]
108
+ ? transforms[currentKey](legacyValue, input)
109
+ : legacyValue;
110
+
111
+ return coerceValueFromManifest(attribute, transformedValue);
112
+ }
113
+
114
+ export function resolveMigrationAttribute(
115
+ attribute: ManifestAttribute,
116
+ currentPath: string,
117
+ fallbackPath: string,
118
+ input: Record<string, unknown>,
119
+ renameMap: RenameMap,
120
+ transforms: TransformMap,
121
+ ): unknown {
122
+ const sourcePath = getSourcePath(currentPath, fallbackPath, renameMap);
123
+
124
+ switch (attribute.ts.kind) {
125
+ case "object": {
126
+ const nextInput = Object.fromEntries(
127
+ Object.entries(attribute.ts.properties ?? {}).map(([key, property]) => [
128
+ key,
129
+ resolveMigrationAttribute(
130
+ property,
131
+ `${currentPath}.${key}`,
132
+ `${sourcePath}.${key}`,
133
+ input,
134
+ renameMap,
135
+ transforms,
136
+ ),
137
+ ]),
138
+ );
139
+
140
+ return coerceValueFromManifest(attribute, nextInput);
141
+ }
142
+ case "union": {
143
+ const legacyValue = getValueAtPath(input, sourcePath);
144
+ if (!isPlainObject(legacyValue) || !attribute.ts.union) {
145
+ return createDefaultValue(attribute);
146
+ }
147
+
148
+ const { discriminator, branches } = attribute.ts.union;
149
+ const branchKey = legacyValue[discriminator];
150
+ if (typeof branchKey !== "string" || !(branchKey in branches)) {
151
+ return createDefaultValue(attribute);
152
+ }
153
+
154
+ const branchAttribute = branches[branchKey];
155
+ const properties = branchAttribute.ts.properties ?? {};
156
+ const nextInput = Object.fromEntries(
157
+ Object.entries(properties)
158
+ .filter(([key]) => key !== discriminator)
159
+ .map(([key, property]) => [
160
+ key,
161
+ resolveMigrationAttribute(
162
+ property,
163
+ `${currentPath}.${branchKey}.${key}`,
164
+ `${sourcePath}.${key}`,
165
+ input,
166
+ renameMap,
167
+ transforms,
168
+ ),
169
+ ]),
170
+ );
171
+
172
+ return coerceValueFromManifest(attribute, {
173
+ ...nextInput,
174
+ [discriminator]: branchKey,
175
+ });
176
+ }
177
+ default:
178
+ return resolveMigrationValue(attribute, currentPath, sourcePath, input, renameMap, transforms);
179
+ }
180
+ }
181
+
182
+ export function coerceValueFromManifest(attribute: ManifestAttribute, value: unknown): unknown {
183
+ if (value === undefined || value === null) {
184
+ return createDefaultValue(attribute);
185
+ }
186
+
187
+ switch (attribute.ts.kind) {
188
+ case "string":
189
+ return isValidString(attribute, value) ? value : createDefaultValue(attribute);
190
+ case "number":
191
+ return isValidNumber(attribute, value) ? value : createDefaultValue(attribute);
192
+ case "boolean":
193
+ return typeof value === "boolean" ? value : createDefaultValue(attribute);
194
+ case "array":
195
+ if (!Array.isArray(value) || !attribute.ts.items) {
196
+ return createDefaultValue(attribute);
197
+ }
198
+ return value.map((item) => coerceValueFromManifest(attribute.ts.items as ManifestAttribute, item));
199
+ case "object":
200
+ if (!isPlainObject(value)) {
201
+ return createDefaultValue(attribute);
202
+ }
203
+ return Object.fromEntries(
204
+ Object.entries(attribute.ts.properties ?? {}).map(([key, property]) => [
205
+ key,
206
+ coerceValueFromManifest(property, (value as Record<string, unknown>)[key]),
207
+ ]),
208
+ );
209
+ case "union":
210
+ return coerceUnionValue(attribute, value);
211
+ default:
212
+ return createDefaultValue(attribute);
213
+ }
214
+ }
215
+
216
+ export function manifestMatchesDocument(manifest: ManifestDocument, attributes: Record<string, unknown>): boolean {
217
+ for (const [key, attribute] of Object.entries(manifest.attributes)) {
218
+ const value = attributes[key];
219
+ if ((value === undefined || value === null) && (!attribute.ts.required || attribute.typia.hasDefault)) {
220
+ continue;
221
+ }
222
+ if (!manifestMatchesAttribute(attribute, value)) {
223
+ return false;
224
+ }
225
+ }
226
+
227
+ return true;
228
+ }
229
+
230
+ export function summarizeVersionDelta(
231
+ legacyManifest: ManifestDocument,
232
+ currentManifest: ManifestDocument,
233
+ ): { added: string[]; removed: string[]; changed: string[] } {
234
+ const added = Object.keys(currentManifest.attributes).filter((key) => !(key in legacyManifest.attributes));
235
+ const removed = Object.keys(legacyManifest.attributes).filter((key) => !(key in currentManifest.attributes));
236
+ const changed = Object.keys(currentManifest.attributes).filter((key) => {
237
+ if (!(key in legacyManifest.attributes)) {
238
+ return false;
239
+ }
240
+ return JSON.stringify(currentManifest.attributes[key]) !== JSON.stringify(legacyManifest.attributes[key]);
241
+ });
242
+
243
+ return { added, changed, removed };
244
+ }
245
+
246
+ function manifestMatchesAttribute(attribute: ManifestAttribute, value: unknown): boolean {
247
+ if ((value === undefined || value === null) && (!attribute.ts.required || attribute.typia.hasDefault)) {
248
+ return true;
249
+ }
250
+
251
+ switch (attribute.ts.kind) {
252
+ case "string":
253
+ return isValidString(attribute, value);
254
+ case "number":
255
+ return isValidNumber(attribute, value);
256
+ case "boolean":
257
+ return typeof value === "boolean";
258
+ case "array":
259
+ return Array.isArray(value)
260
+ && Boolean(attribute.ts.items)
261
+ && value.every((item) => manifestMatchesAttribute(attribute.ts.items as ManifestAttribute, item));
262
+ case "object":
263
+ return isPlainObject(value)
264
+ && Object.entries(attribute.ts.properties ?? {}).every(([key, property]) =>
265
+ manifestMatchesAttribute(property, (value as Record<string, unknown>)[key]),
266
+ );
267
+ case "union":
268
+ return matchesUnionAttribute(attribute, value);
269
+ default:
270
+ return false;
271
+ }
272
+ }
273
+
274
+ function matchesUnionAttribute(attribute: ManifestAttribute, value: unknown): boolean {
275
+ if (!isPlainObject(value) || !attribute.ts.union) {
276
+ return false;
277
+ }
278
+
279
+ const { discriminator, branches } = attribute.ts.union;
280
+ const branchKey = (value as Record<string, unknown>)[discriminator];
281
+
282
+ return typeof branchKey === "string"
283
+ && branchKey in branches
284
+ && manifestMatchesAttribute(branches[branchKey], value);
285
+ }
286
+
287
+ function coerceUnionValue(attribute: ManifestAttribute, value: unknown): unknown {
288
+ if (!isPlainObject(value) || !attribute.ts.union) {
289
+ return createDefaultValue(attribute);
290
+ }
291
+
292
+ const { discriminator, branches } = attribute.ts.union;
293
+ const branchKey = (value as Record<string, unknown>)[discriminator];
294
+ if (typeof branchKey !== "string" || !(branchKey in branches)) {
295
+ return createDefaultValue(attribute);
296
+ }
297
+
298
+ return coerceValueFromManifest(branches[branchKey], value);
299
+ }
300
+
301
+ function isValidString(attribute: ManifestAttribute, value: unknown): value is string {
302
+ if (typeof value !== "string") {
303
+ return false;
304
+ }
305
+
306
+ const { enum: enumValues } = attribute.wp;
307
+ const { format, maxLength, minLength, pattern } = attribute.typia.constraints;
308
+
309
+ if (enumValues && !enumValues.includes(value)) {
310
+ return false;
311
+ }
312
+ if (typeof minLength === "number" && value.length < minLength) {
313
+ return false;
314
+ }
315
+ if (typeof maxLength === "number" && value.length > maxLength) {
316
+ return false;
317
+ }
318
+ if (pattern && !(new RegExp(pattern).test(value))) {
319
+ return false;
320
+ }
321
+ if (format === "uuid" && !/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value)) {
322
+ return false;
323
+ }
324
+
325
+ return true;
326
+ }
327
+
328
+ function isValidNumber(attribute: ManifestAttribute, value: unknown): value is number {
329
+ if (typeof value !== "number" || Number.isNaN(value)) {
330
+ return false;
331
+ }
332
+
333
+ const { enum: enumValues } = attribute.wp;
334
+ const { maximum, minimum, typeTag } = attribute.typia.constraints;
335
+
336
+ if (enumValues && !enumValues.includes(value)) {
337
+ return false;
338
+ }
339
+ if (typeof minimum === "number" && value < minimum) {
340
+ return false;
341
+ }
342
+ if (typeof maximum === "number" && value > maximum) {
343
+ return false;
344
+ }
345
+ if (typeTag === "uint32" && (!Number.isInteger(value) || value < 0 || value > 4294967295)) {
346
+ return false;
347
+ }
348
+
349
+ return true;
350
+ }
351
+
352
+ function isPlainObject(value: unknown): value is Record<string, unknown> {
353
+ return typeof value === "object" && value !== null && !Array.isArray(value);
354
+ }