@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.
- package/README.md +43 -0
- package/dist/cli.js +2492 -0
- package/dist/runtime/cli-core.js +222 -0
- package/dist/runtime/index.js +4 -0
- package/dist/runtime/migration-constants.js +14 -0
- package/dist/runtime/migration-diff.js +521 -0
- package/dist/runtime/migration-fixtures.js +89 -0
- package/dist/runtime/migration-manifest.js +129 -0
- package/dist/runtime/migration-project.js +167 -0
- package/dist/runtime/migration-render.js +267 -0
- package/dist/runtime/migration-types.js +1 -0
- package/dist/runtime/migration-utils.js +184 -0
- package/dist/runtime/migrations.js +232 -0
- package/dist/runtime/package-managers.js +135 -0
- package/dist/runtime/scaffold.js +334 -0
- package/dist/runtime/template-registry.js +75 -0
- package/package.json +65 -0
- package/templates/advanced/README.md.mustache +150 -0
- package/templates/advanced/block.json.mustache +43 -0
- package/templates/advanced/index.js +21 -0
- package/templates/advanced/package.json.mustache +47 -0
- package/templates/advanced/render.php.mustache +83 -0
- package/templates/advanced/scripts/lib/typia-metadata-core.ts +1413 -0
- package/templates/advanced/scripts/sync-types-to-block-json.ts.mustache +32 -0
- package/templates/advanced/src/admin/migration-dashboard.tsx.mustache +315 -0
- package/templates/advanced/src/components/ErrorBoundary.tsx.mustache +47 -0
- package/templates/advanced/src/deprecated.ts.mustache +2 -0
- package/templates/advanced/src/edit.tsx.mustache +97 -0
- package/templates/advanced/src/hooks/useDebounce.ts.mustache +20 -0
- package/templates/advanced/src/hooks/useLocalStorage.ts.mustache +31 -0
- package/templates/advanced/src/hooks.ts.mustache +56 -0
- package/templates/advanced/src/index.tsx.mustache +18 -0
- package/templates/advanced/src/migration-detector.ts.mustache +9 -0
- package/templates/advanced/src/migrations/config.ts.mustache +8 -0
- package/templates/advanced/src/migrations/examples/rename-transform-union/README.md.mustache +23 -0
- package/templates/advanced/src/migrations/examples/rename-transform-union/fixture.example.json.mustache +36 -0
- package/templates/advanced/src/migrations/examples/rename-transform-union/rule.example.ts.mustache +47 -0
- package/templates/advanced/src/migrations/fixtures/README.md.mustache +3 -0
- package/templates/advanced/src/migrations/generated/deprecated.ts.mustache +3 -0
- package/templates/advanced/src/migrations/generated/registry.ts.mustache +9 -0
- package/templates/advanced/src/migrations/generated/verify.ts.mustache +1 -0
- package/templates/advanced/src/migrations/helpers.ts.mustache +354 -0
- package/templates/advanced/src/migrations/index.ts.mustache +616 -0
- package/templates/advanced/src/migrations/rules/README.md.mustache +3 -0
- package/templates/advanced/src/migrations/versions/README.md.mustache +3 -0
- package/templates/advanced/src/save.tsx.mustache +12 -0
- package/templates/advanced/src/style.scss.mustache +84 -0
- package/templates/advanced/src/types.ts.mustache +46 -0
- package/templates/advanced/src/utils/classnames.ts.mustache +51 -0
- package/templates/advanced/src/utils/debounce.ts.mustache +37 -0
- package/templates/advanced/src/utils/index.ts.mustache +7 -0
- package/templates/advanced/src/utils/uuid.ts.mustache +17 -0
- package/templates/advanced/src/validators.ts.mustache +39 -0
- package/templates/advanced/src/view.ts.mustache +59 -0
- package/templates/advanced/tsconfig.json.mustache +20 -0
- package/templates/advanced/webpack.config.js.mustache +95 -0
- package/templates/basic/package.json.mustache +39 -0
- package/templates/basic/scripts/lib/typia-metadata-core.ts +1413 -0
- package/templates/basic/scripts/sync-types-to-block-json.ts +25 -0
- package/templates/basic/src/block.json +51 -0
- package/templates/basic/src/edit.tsx +85 -0
- package/templates/basic/src/hooks.ts +75 -0
- package/templates/basic/src/index.tsx +37 -0
- package/templates/basic/src/save.tsx +27 -0
- package/templates/basic/src/style.scss +42 -0
- package/templates/basic/src/types.ts +48 -0
- package/templates/basic/src/validators.ts +39 -0
- package/templates/basic/tsconfig.json +20 -0
- package/templates/basic/webpack.config.js +89 -0
- package/templates/full/package.json.mustache +40 -0
- package/templates/full/scripts/lib/typia-metadata-core.ts +1413 -0
- package/templates/full/scripts/sync-types-to-block-json.ts.mustache +32 -0
- package/templates/full/src/block.json.mustache +120 -0
- package/templates/full/src/edit.tsx.mustache +300 -0
- package/templates/full/src/editor.scss.mustache +251 -0
- package/templates/full/src/hooks.ts.mustache +141 -0
- package/templates/full/src/index.tsx.mustache +27 -0
- package/templates/full/src/save.tsx.mustache +39 -0
- package/templates/full/src/style.scss.mustache +224 -0
- package/templates/full/src/types.ts.mustache +35 -0
- package/templates/full/src/validators.ts.mustache +84 -0
- package/templates/full/tsconfig.json.mustache +20 -0
- package/templates/full/webpack.config.js.mustache +89 -0
- package/templates/interactivity/package.json.mustache +41 -0
- package/templates/interactivity/scripts/lib/typia-metadata-core.ts +1413 -0
- package/templates/interactivity/scripts/sync-types-to-block-json.ts.mustache +32 -0
- package/templates/interactivity/src/block.json.mustache +74 -0
- package/templates/interactivity/src/edit.tsx.mustache +206 -0
- package/templates/interactivity/src/index.tsx.mustache +20 -0
- package/templates/interactivity/src/interactivity.ts.mustache +183 -0
- package/templates/interactivity/src/save.tsx.mustache +87 -0
- package/templates/interactivity/src/style.scss.mustache +60 -0
- package/templates/interactivity/src/types.ts.mustache +30 -0
- package/templates/interactivity/tsconfig.json.mustache +20 -0
- package/templates/interactivity/webpack.config.js.mustache +89 -0
|
@@ -0,0 +1,616 @@
|
|
|
1
|
+
import apiFetch from "@wordpress/api-fetch";
|
|
2
|
+
import { parse, serialize } from "@wordpress/blocks";
|
|
3
|
+
|
|
4
|
+
import { validators } from "../validators";
|
|
5
|
+
import migrationConfig from "./config";
|
|
6
|
+
import { deprecated } from "./generated/deprecated";
|
|
7
|
+
import migrationRegistry from "./generated/registry";
|
|
8
|
+
import {
|
|
9
|
+
type ManifestAttribute,
|
|
10
|
+
type ManifestDocument,
|
|
11
|
+
manifestMatchesDocument,
|
|
12
|
+
summarizeVersionDelta,
|
|
13
|
+
} from "./helpers";
|
|
14
|
+
|
|
15
|
+
export interface MigrationAnalysis {
|
|
16
|
+
needsMigration: boolean;
|
|
17
|
+
currentVersion: string;
|
|
18
|
+
targetVersion: string;
|
|
19
|
+
confidence: number;
|
|
20
|
+
reasons: string[];
|
|
21
|
+
warnings: string[];
|
|
22
|
+
affectedFields: {
|
|
23
|
+
added: string[];
|
|
24
|
+
changed: string[];
|
|
25
|
+
removed: string[];
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface UnionBranchPreview {
|
|
30
|
+
field: string;
|
|
31
|
+
legacyBranch: string | null;
|
|
32
|
+
nextBranch: string | null;
|
|
33
|
+
status: "auto" | "current" | "manual" | "unknown";
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface MigrationPreview {
|
|
37
|
+
after: Record<string, unknown> | null;
|
|
38
|
+
before: Record<string, unknown>;
|
|
39
|
+
changedFields: string[];
|
|
40
|
+
unresolved: string[];
|
|
41
|
+
unionBranches: UnionBranchPreview[];
|
|
42
|
+
validationErrors: string[];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface BlockScanResult {
|
|
46
|
+
analysis: MigrationAnalysis;
|
|
47
|
+
attributes: Record<string, unknown>;
|
|
48
|
+
blockName: string;
|
|
49
|
+
blockPath: number[];
|
|
50
|
+
postId: number;
|
|
51
|
+
postTitle: string;
|
|
52
|
+
postType: string;
|
|
53
|
+
preview: MigrationPreview;
|
|
54
|
+
rawContent: string;
|
|
55
|
+
restBase: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface BatchMigrationBlockResult {
|
|
59
|
+
blockPath: number[];
|
|
60
|
+
currentVersion: string;
|
|
61
|
+
preview: MigrationPreview;
|
|
62
|
+
reason?: string;
|
|
63
|
+
status: "failed" | "success";
|
|
64
|
+
targetVersion: string;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface BatchMigrationPostResult {
|
|
68
|
+
postId: number;
|
|
69
|
+
postTitle: string;
|
|
70
|
+
postType: string;
|
|
71
|
+
previews: BatchMigrationBlockResult[];
|
|
72
|
+
reason?: string;
|
|
73
|
+
status: "failed" | "success";
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface BatchMigrationResult {
|
|
77
|
+
errors: Array<{ postId: number; reason: string }>;
|
|
78
|
+
failed: number;
|
|
79
|
+
posts: BatchMigrationPostResult[];
|
|
80
|
+
successful: number;
|
|
81
|
+
total: number;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
interface EditablePostType {
|
|
85
|
+
rest_base: string;
|
|
86
|
+
slug: string;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
interface EditablePostRecord {
|
|
90
|
+
content?: {
|
|
91
|
+
raw?: string;
|
|
92
|
+
};
|
|
93
|
+
id: number;
|
|
94
|
+
title?: {
|
|
95
|
+
rendered?: string;
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
interface GroupedScanResult {
|
|
100
|
+
postId: number;
|
|
101
|
+
postTitle: string;
|
|
102
|
+
postType: string;
|
|
103
|
+
rawContent: string;
|
|
104
|
+
restBase: string;
|
|
105
|
+
results: BlockScanResult[];
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
interface ParsedBlock {
|
|
109
|
+
attributes?: Record<string, unknown>;
|
|
110
|
+
innerBlocks?: ParsedBlock[];
|
|
111
|
+
name?: string;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
interface MigrationResolution {
|
|
115
|
+
analysis: MigrationAnalysis;
|
|
116
|
+
preview: MigrationPreview;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function detectBlockMigration(attributes: Record<string, unknown>): MigrationAnalysis {
|
|
120
|
+
return resolveMigrationState(attributes).analysis;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function autoMigrate(attributes: Record<string, unknown>) {
|
|
124
|
+
const resolution = resolveMigrationState(attributes);
|
|
125
|
+
if (!resolution.preview.after) {
|
|
126
|
+
throw new Error(
|
|
127
|
+
resolution.preview.validationErrors[0]
|
|
128
|
+
?? resolution.preview.unresolved[0]
|
|
129
|
+
?? "Unable to migrate block attributes because no supported snapshot matched.",
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return resolution.preview.after;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export async function scanSiteForMigrations(
|
|
137
|
+
blockName: string = migrationConfig.blockName,
|
|
138
|
+
): Promise<BlockScanResult[]> {
|
|
139
|
+
const postTypes = await fetchEditablePostTypes();
|
|
140
|
+
const results: BlockScanResult[] = [];
|
|
141
|
+
|
|
142
|
+
for (const postType of postTypes) {
|
|
143
|
+
const posts = await fetchAllPosts(postType.rest_base);
|
|
144
|
+
for (const post of posts) {
|
|
145
|
+
const content = post?.content?.raw;
|
|
146
|
+
if (typeof content !== "string" || content.length === 0) {
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const blocks = parse(content);
|
|
151
|
+
walkBlocks(blocks, [], (block, blockPath) => {
|
|
152
|
+
if (block.name !== blockName) {
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const attributes = (block.attributes ?? {}) as Record<string, unknown>;
|
|
157
|
+
const resolution = resolveMigrationState(attributes);
|
|
158
|
+
|
|
159
|
+
if (
|
|
160
|
+
resolution.analysis.needsMigration
|
|
161
|
+
|| resolution.preview.changedFields.length > 0
|
|
162
|
+
|| resolution.preview.unresolved.length > 0
|
|
163
|
+
|| resolution.preview.validationErrors.length > 0
|
|
164
|
+
) {
|
|
165
|
+
results.push({
|
|
166
|
+
analysis: resolution.analysis,
|
|
167
|
+
attributes,
|
|
168
|
+
blockName,
|
|
169
|
+
blockPath,
|
|
170
|
+
postId: post.id,
|
|
171
|
+
postTitle: post?.title?.rendered ?? `Post ${post.id}`,
|
|
172
|
+
postType: postType.slug,
|
|
173
|
+
preview: resolution.preview,
|
|
174
|
+
rawContent: content,
|
|
175
|
+
restBase: postType.rest_base,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return results;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export async function batchMigrateScanResults(
|
|
186
|
+
results: BlockScanResult[],
|
|
187
|
+
{ dryRun = false }: { dryRun?: boolean } = {},
|
|
188
|
+
): Promise<BatchMigrationResult> {
|
|
189
|
+
const grouped = groupResultsByPost(results);
|
|
190
|
+
const summary: BatchMigrationResult = {
|
|
191
|
+
errors: [],
|
|
192
|
+
failed: 0,
|
|
193
|
+
posts: [],
|
|
194
|
+
successful: 0,
|
|
195
|
+
total: Object.keys(grouped).length,
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
for (const group of Object.values(grouped)) {
|
|
199
|
+
const blockPreviews = group.results.map((result) => {
|
|
200
|
+
const resolution = resolveMigrationState(result.attributes);
|
|
201
|
+
const reason =
|
|
202
|
+
resolution.preview.validationErrors[0]
|
|
203
|
+
?? resolution.preview.unresolved[0]
|
|
204
|
+
?? undefined;
|
|
205
|
+
|
|
206
|
+
return {
|
|
207
|
+
blockPath: result.blockPath,
|
|
208
|
+
currentVersion: resolution.analysis.currentVersion,
|
|
209
|
+
preview: resolution.preview,
|
|
210
|
+
reason,
|
|
211
|
+
status: resolution.preview.after ? "success" : "failed",
|
|
212
|
+
targetVersion: resolution.analysis.targetVersion,
|
|
213
|
+
} satisfies BatchMigrationBlockResult;
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
const failedPreview = blockPreviews.find((preview) => preview.status === "failed");
|
|
217
|
+
if (failedPreview) {
|
|
218
|
+
summary.failed += 1;
|
|
219
|
+
summary.errors.push({
|
|
220
|
+
postId: group.postId,
|
|
221
|
+
reason: failedPreview.reason ?? "One or more blocks could not be migrated.",
|
|
222
|
+
});
|
|
223
|
+
summary.posts.push({
|
|
224
|
+
postId: group.postId,
|
|
225
|
+
postTitle: group.postTitle,
|
|
226
|
+
postType: group.postType,
|
|
227
|
+
previews: blockPreviews,
|
|
228
|
+
reason: failedPreview.reason,
|
|
229
|
+
status: "failed",
|
|
230
|
+
});
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const migratedContent = migratePostContent(blockPreviews, group.rawContent);
|
|
235
|
+
if (!dryRun) {
|
|
236
|
+
await apiFetch({
|
|
237
|
+
body: {
|
|
238
|
+
content: migratedContent,
|
|
239
|
+
},
|
|
240
|
+
method: "POST",
|
|
241
|
+
path: `/wp/v2/${group.restBase}/${group.postId}`,
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
summary.successful += 1;
|
|
246
|
+
summary.posts.push({
|
|
247
|
+
postId: group.postId,
|
|
248
|
+
postTitle: group.postTitle,
|
|
249
|
+
postType: group.postType,
|
|
250
|
+
previews: blockPreviews,
|
|
251
|
+
status: "success",
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return summary;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export function generateMigrationReport(scanResults: BlockScanResult[]): string {
|
|
259
|
+
let report = `# ${migrationConfig.blockName} Migration Report\n\n`;
|
|
260
|
+
report += `- Current version: ${migrationRegistry.currentVersion}\n`;
|
|
261
|
+
report += `- Supported deprecated entries: ${deprecated.length}\n`;
|
|
262
|
+
report += `- Scan results needing attention: ${scanResults.length}\n\n`;
|
|
263
|
+
|
|
264
|
+
for (const entry of scanResults) {
|
|
265
|
+
report += `## ${entry.postTitle} (#${entry.postId})\n`;
|
|
266
|
+
report += `- Version: ${entry.analysis.currentVersion} -> ${entry.analysis.targetVersion}\n`;
|
|
267
|
+
report += `- Confidence: ${entry.analysis.confidence}\n`;
|
|
268
|
+
if (entry.preview.changedFields.length > 0) {
|
|
269
|
+
report += `- Changed fields: ${entry.preview.changedFields.join(", ")}\n`;
|
|
270
|
+
}
|
|
271
|
+
if (entry.preview.unionBranches.length > 0) {
|
|
272
|
+
report += `- Union branches:\n`;
|
|
273
|
+
for (const branch of entry.preview.unionBranches) {
|
|
274
|
+
report += ` - ${branch.field}: ${branch.legacyBranch ?? "unknown"} -> ${branch.nextBranch ?? "unknown"} (${branch.status})\n`;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
if (entry.preview.unresolved.length > 0) {
|
|
278
|
+
report += `- Unresolved: ${entry.preview.unresolved.join(", ")}\n`;
|
|
279
|
+
}
|
|
280
|
+
if (entry.preview.validationErrors.length > 0) {
|
|
281
|
+
report += `- Validation errors: ${entry.preview.validationErrors.join(", ")}\n`;
|
|
282
|
+
}
|
|
283
|
+
report += "\n### Before\n\n```json\n";
|
|
284
|
+
report += `${JSON.stringify(entry.preview.before, null, 2)}\n`;
|
|
285
|
+
report += "```\n\n";
|
|
286
|
+
report += "### After\n\n```json\n";
|
|
287
|
+
report += `${JSON.stringify(entry.preview.after, null, 2)}\n`;
|
|
288
|
+
report += "```\n\n";
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
return report;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
export const migrationUtils = {
|
|
295
|
+
getStats() {
|
|
296
|
+
return {
|
|
297
|
+
blockName: migrationConfig.blockName,
|
|
298
|
+
currentVersion: migrationRegistry.currentVersion,
|
|
299
|
+
deprecatedEntries: deprecated.length,
|
|
300
|
+
supportedVersions: migrationRegistry.entries.map((entry) => entry.fromVersion),
|
|
301
|
+
};
|
|
302
|
+
},
|
|
303
|
+
testMigration(attributes: Record<string, unknown>): Record<string, unknown> {
|
|
304
|
+
return autoMigrate(attributes);
|
|
305
|
+
},
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
async function fetchEditablePostTypes(): Promise<EditablePostType[]> {
|
|
309
|
+
const response = (await apiFetch({
|
|
310
|
+
parse: false,
|
|
311
|
+
path: "/wp/v2/types?context=edit",
|
|
312
|
+
})) as Response;
|
|
313
|
+
const payload = (await response.json()) as Record<string, { rest_base?: string; slug?: string; viewable?: boolean }>;
|
|
314
|
+
|
|
315
|
+
return Object.values(payload)
|
|
316
|
+
.filter((postType) => postType?.viewable && postType?.rest_base)
|
|
317
|
+
.map((postType) => ({
|
|
318
|
+
rest_base: postType.rest_base!,
|
|
319
|
+
slug: postType.slug!,
|
|
320
|
+
}));
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
async function fetchAllPosts(restBase: string): Promise<EditablePostRecord[]> {
|
|
324
|
+
let page = 1;
|
|
325
|
+
let totalPages = 1;
|
|
326
|
+
const entries: EditablePostRecord[] = [];
|
|
327
|
+
|
|
328
|
+
do {
|
|
329
|
+
const response = (await apiFetch({
|
|
330
|
+
parse: false,
|
|
331
|
+
path: `/wp/v2/${restBase}?context=edit&per_page=100&page=${page}`,
|
|
332
|
+
})) as Response;
|
|
333
|
+
totalPages = Number.parseInt(response.headers.get("X-WP-TotalPages") ?? "1", 10);
|
|
334
|
+
entries.push(...((await response.json()) as EditablePostRecord[]));
|
|
335
|
+
page += 1;
|
|
336
|
+
} while (page <= totalPages);
|
|
337
|
+
|
|
338
|
+
return entries;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function walkBlocks(
|
|
342
|
+
blocks: ParsedBlock[],
|
|
343
|
+
pathPrefix: number[],
|
|
344
|
+
visitor: (block: ParsedBlock, path: number[]) => void,
|
|
345
|
+
): void {
|
|
346
|
+
blocks.forEach((block, index) => {
|
|
347
|
+
const blockPath = [...pathPrefix, index];
|
|
348
|
+
visitor(block, blockPath);
|
|
349
|
+
if (Array.isArray(block.innerBlocks) && block.innerBlocks.length > 0) {
|
|
350
|
+
walkBlocks(block.innerBlocks, blockPath, visitor);
|
|
351
|
+
}
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function groupResultsByPost(results: BlockScanResult[]): Record<string, GroupedScanResult> {
|
|
356
|
+
return results.reduce<Record<string, GroupedScanResult>>(
|
|
357
|
+
(accumulator, result) => {
|
|
358
|
+
const key = `${result.restBase}:${result.postId}`;
|
|
359
|
+
if (!accumulator[key]) {
|
|
360
|
+
accumulator[key] = {
|
|
361
|
+
postId: result.postId,
|
|
362
|
+
postTitle: result.postTitle,
|
|
363
|
+
postType: result.postType,
|
|
364
|
+
rawContent: result.rawContent,
|
|
365
|
+
restBase: result.restBase,
|
|
366
|
+
results: [],
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
accumulator[key].results.push(result);
|
|
370
|
+
return accumulator;
|
|
371
|
+
},
|
|
372
|
+
{},
|
|
373
|
+
);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function migratePostContent(results: BatchMigrationBlockResult[], rawContent: string): string {
|
|
377
|
+
const replacements = new Map(
|
|
378
|
+
results
|
|
379
|
+
.filter((result) => result.preview.after)
|
|
380
|
+
.map((result) => [result.blockPath.join("."), result.preview.after as Record<string, unknown>]),
|
|
381
|
+
);
|
|
382
|
+
const blocks = parse(rawContent) as ParsedBlock[];
|
|
383
|
+
const nextBlocks = replaceBlocks(blocks, [], replacements);
|
|
384
|
+
return serialize(nextBlocks);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
function replaceBlocks(
|
|
388
|
+
blocks: ParsedBlock[],
|
|
389
|
+
pathPrefix: number[],
|
|
390
|
+
replacements: Map<string, Record<string, unknown>>,
|
|
391
|
+
): ParsedBlock[] {
|
|
392
|
+
return blocks.map((block, index) => {
|
|
393
|
+
const blockPath = [...pathPrefix, index];
|
|
394
|
+
const replacement = replacements.get(blockPath.join("."));
|
|
395
|
+
const innerBlocks = Array.isArray(block.innerBlocks)
|
|
396
|
+
? replaceBlocks(block.innerBlocks, blockPath, replacements)
|
|
397
|
+
: [];
|
|
398
|
+
|
|
399
|
+
if (!replacement) {
|
|
400
|
+
return {
|
|
401
|
+
...block,
|
|
402
|
+
innerBlocks,
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
return {
|
|
407
|
+
...block,
|
|
408
|
+
attributes: replacement,
|
|
409
|
+
innerBlocks,
|
|
410
|
+
};
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
function resolveMigrationState(attributes: Record<string, unknown>): MigrationResolution {
|
|
415
|
+
const currentValidation = validators.validate(attributes as any);
|
|
416
|
+
if (currentValidation.success) {
|
|
417
|
+
return {
|
|
418
|
+
analysis: {
|
|
419
|
+
affectedFields: {
|
|
420
|
+
added: [],
|
|
421
|
+
changed: [],
|
|
422
|
+
removed: [],
|
|
423
|
+
},
|
|
424
|
+
confidence: 1,
|
|
425
|
+
currentVersion: migrationRegistry.currentVersion,
|
|
426
|
+
needsMigration: false,
|
|
427
|
+
reasons: ["Current Typia validator accepted the attributes."],
|
|
428
|
+
targetVersion: migrationRegistry.currentVersion,
|
|
429
|
+
warnings: [],
|
|
430
|
+
} satisfies MigrationAnalysis,
|
|
431
|
+
preview: createPreview({
|
|
432
|
+
after: attributes,
|
|
433
|
+
before: attributes,
|
|
434
|
+
currentManifest: migrationRegistry.currentManifest as ManifestDocument,
|
|
435
|
+
legacyManifest: null,
|
|
436
|
+
status: "current",
|
|
437
|
+
unresolved: [],
|
|
438
|
+
validationErrors: [],
|
|
439
|
+
}),
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
for (const entry of migrationRegistry.entries) {
|
|
444
|
+
if (manifestMatchesDocument(entry.manifest as ManifestDocument, attributes)) {
|
|
445
|
+
const migrated = entry.rule.migrate(attributes);
|
|
446
|
+
const migratedValidation = validators.validate(migrated as any);
|
|
447
|
+
const unresolved = Array.isArray(entry.rule.unresolved) ? [...entry.rule.unresolved] : [];
|
|
448
|
+
const validationErrors = migratedValidation.success ? [] : formatValidationErrors(migratedValidation.errors);
|
|
449
|
+
const preview = createPreview({
|
|
450
|
+
after: migratedValidation.success ? (migrated as Record<string, unknown>) : null,
|
|
451
|
+
before: attributes,
|
|
452
|
+
currentManifest: migrationRegistry.currentManifest as ManifestDocument,
|
|
453
|
+
legacyManifest: entry.manifest as ManifestDocument,
|
|
454
|
+
status: migratedValidation.success ? (unresolved.length > 0 ? "manual" : "auto") : "manual",
|
|
455
|
+
unresolved,
|
|
456
|
+
validationErrors,
|
|
457
|
+
});
|
|
458
|
+
const delta = summarizeVersionDelta(
|
|
459
|
+
entry.manifest as ManifestDocument,
|
|
460
|
+
migrationRegistry.currentManifest as ManifestDocument,
|
|
461
|
+
);
|
|
462
|
+
|
|
463
|
+
return {
|
|
464
|
+
analysis: {
|
|
465
|
+
affectedFields: delta,
|
|
466
|
+
confidence: unresolved.length > 0 ? 0.8 : 0.95,
|
|
467
|
+
currentVersion: entry.fromVersion,
|
|
468
|
+
needsMigration: true,
|
|
469
|
+
reasons: [
|
|
470
|
+
`Snapshot ${entry.fromVersion} matched this block.`,
|
|
471
|
+
...preview.unionBranches.map(
|
|
472
|
+
(branch) => `Union ${branch.field}: ${branch.legacyBranch ?? "unknown"} -> ${branch.nextBranch ?? "unknown"} (${branch.status})`,
|
|
473
|
+
),
|
|
474
|
+
],
|
|
475
|
+
targetVersion: migrationRegistry.currentVersion,
|
|
476
|
+
warnings: [...unresolved, ...validationErrors],
|
|
477
|
+
} satisfies MigrationAnalysis,
|
|
478
|
+
preview,
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
return {
|
|
484
|
+
analysis: {
|
|
485
|
+
affectedFields: {
|
|
486
|
+
added: [],
|
|
487
|
+
changed: [],
|
|
488
|
+
removed: [],
|
|
489
|
+
},
|
|
490
|
+
confidence: 0.2,
|
|
491
|
+
currentVersion: "unknown",
|
|
492
|
+
needsMigration: true,
|
|
493
|
+
reasons: ["No legacy snapshot matched and current Typia validator rejected the attributes."],
|
|
494
|
+
targetVersion: migrationRegistry.currentVersion,
|
|
495
|
+
warnings: formatValidationErrors(currentValidation.errors),
|
|
496
|
+
} satisfies MigrationAnalysis,
|
|
497
|
+
preview: createPreview({
|
|
498
|
+
after: null,
|
|
499
|
+
before: attributes,
|
|
500
|
+
currentManifest: migrationRegistry.currentManifest as ManifestDocument,
|
|
501
|
+
legacyManifest: null,
|
|
502
|
+
status: "unknown",
|
|
503
|
+
unresolved: ["Manual migration review is required because the block does not match any supported snapshot."],
|
|
504
|
+
validationErrors: formatValidationErrors(currentValidation.errors),
|
|
505
|
+
}),
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
function createPreview({
|
|
510
|
+
after,
|
|
511
|
+
before,
|
|
512
|
+
currentManifest,
|
|
513
|
+
legacyManifest,
|
|
514
|
+
status,
|
|
515
|
+
unresolved,
|
|
516
|
+
validationErrors,
|
|
517
|
+
}: {
|
|
518
|
+
after: Record<string, unknown> | null;
|
|
519
|
+
before: Record<string, unknown>;
|
|
520
|
+
currentManifest: ManifestDocument;
|
|
521
|
+
legacyManifest: ManifestDocument | null;
|
|
522
|
+
status: UnionBranchPreview["status"];
|
|
523
|
+
unresolved: string[];
|
|
524
|
+
validationErrors: string[];
|
|
525
|
+
}): MigrationPreview {
|
|
526
|
+
return {
|
|
527
|
+
after,
|
|
528
|
+
before,
|
|
529
|
+
changedFields: after ? collectChangedFieldPaths(before, after) : [],
|
|
530
|
+
unresolved,
|
|
531
|
+
unionBranches: collectUnionBranchPreview(legacyManifest, currentManifest, before, after, status),
|
|
532
|
+
validationErrors,
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
function collectChangedFieldPaths(
|
|
537
|
+
before: Record<string, unknown>,
|
|
538
|
+
after: Record<string, unknown>,
|
|
539
|
+
prefix = "",
|
|
540
|
+
): string[] {
|
|
541
|
+
const keys = new Set([...Object.keys(before), ...Object.keys(after)]);
|
|
542
|
+
const changes: string[] = [];
|
|
543
|
+
|
|
544
|
+
for (const key of keys) {
|
|
545
|
+
const nextPrefix = prefix ? `${prefix}.${key}` : key;
|
|
546
|
+
const left = before[key];
|
|
547
|
+
const right = after[key];
|
|
548
|
+
|
|
549
|
+
if (isPlainObject(left) && isPlainObject(right)) {
|
|
550
|
+
changes.push(...collectChangedFieldPaths(left, right, nextPrefix));
|
|
551
|
+
continue;
|
|
552
|
+
}
|
|
553
|
+
if (JSON.stringify(left) !== JSON.stringify(right)) {
|
|
554
|
+
changes.push(nextPrefix);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
return changes;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
function collectUnionBranchPreview(
|
|
562
|
+
legacyManifest: ManifestDocument | null,
|
|
563
|
+
currentManifest: ManifestDocument,
|
|
564
|
+
before: Record<string, unknown>,
|
|
565
|
+
after: Record<string, unknown> | null,
|
|
566
|
+
status: UnionBranchPreview["status"],
|
|
567
|
+
): UnionBranchPreview[] {
|
|
568
|
+
const fieldNames = new Set<string>();
|
|
569
|
+
|
|
570
|
+
for (const [field, attribute] of Object.entries(legacyManifest?.attributes ?? {})) {
|
|
571
|
+
if (attribute.ts.kind === "union") {
|
|
572
|
+
fieldNames.add(field);
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
for (const [field, attribute] of Object.entries(currentManifest.attributes ?? {})) {
|
|
576
|
+
if (attribute.ts.kind === "union") {
|
|
577
|
+
fieldNames.add(field);
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
return [...fieldNames].map((field) => {
|
|
582
|
+
const legacyAttribute = legacyManifest?.attributes?.[field] ?? null;
|
|
583
|
+
const currentAttribute = currentManifest.attributes?.[field] ?? null;
|
|
584
|
+
return {
|
|
585
|
+
field,
|
|
586
|
+
legacyBranch: resolveUnionBranchKey(legacyAttribute, before[field]),
|
|
587
|
+
nextBranch: resolveUnionBranchKey(currentAttribute, (after ?? before)[field]),
|
|
588
|
+
status,
|
|
589
|
+
};
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
function resolveUnionBranchKey(attribute: ManifestAttribute | null, value: unknown): string | null {
|
|
594
|
+
if (!attribute || attribute.ts.kind !== "union" || !attribute.ts.union || !isPlainObject(value)) {
|
|
595
|
+
return null;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
const discriminatorValue = value[attribute.ts.union.discriminator];
|
|
599
|
+
if (typeof discriminatorValue !== "string") {
|
|
600
|
+
return null;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
return discriminatorValue in attribute.ts.union.branches ? discriminatorValue : null;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
function formatValidationErrors(errors: Array<{ expected?: string; path?: string }> = []): string[] {
|
|
607
|
+
return errors.map((error) => {
|
|
608
|
+
const pathLabel = error.path ?? "$";
|
|
609
|
+
const expectedLabel = error.expected ?? "unknown";
|
|
610
|
+
return `${pathLabel}: ${expectedLabel}`;
|
|
611
|
+
});
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
615
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
616
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { useBlockProps } from '@wordpress/block-editor';
|
|
2
|
+
import { {{titleCase}}Attributes } from './types';
|
|
3
|
+
|
|
4
|
+
interface SaveProps {
|
|
5
|
+
attributes: {{titleCase}}Attributes;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export default function Save({ attributes }: SaveProps) {
|
|
9
|
+
useBlockProps.save();
|
|
10
|
+
void attributes;
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* {{title}} Block Styles
|
|
3
|
+
* Generated with Typia validation
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
.wp-block-{{slugKebabCase}} {
|
|
7
|
+
/* Default block styles */
|
|
8
|
+
padding: 1rem;
|
|
9
|
+
margin: 1rem 0;
|
|
10
|
+
|
|
11
|
+
/* Alignment support */
|
|
12
|
+
&.has-text-align-left {
|
|
13
|
+
text-align: left;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
&.has-text-align-center {
|
|
17
|
+
text-align: center;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
&.has-text-align-right {
|
|
21
|
+
text-align: right;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
&.has-text-align-justify {
|
|
25
|
+
text-align: justify;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/* Interactive states */
|
|
29
|
+
button {
|
|
30
|
+
background: #0073aa;
|
|
31
|
+
color: white;
|
|
32
|
+
border: none;
|
|
33
|
+
padding: 0.5rem 1rem;
|
|
34
|
+
border-radius: 4px;
|
|
35
|
+
cursor: pointer;
|
|
36
|
+
transition: all 0.2s ease;
|
|
37
|
+
|
|
38
|
+
&:hover {
|
|
39
|
+
background: #005a87;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
&.active {
|
|
43
|
+
background: #00a32a;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/* Validation error display */
|
|
48
|
+
.block-error {
|
|
49
|
+
background: #f8d7da;
|
|
50
|
+
color: #721c24;
|
|
51
|
+
padding: 0.75rem;
|
|
52
|
+
border-radius: 4px;
|
|
53
|
+
margin-bottom: 1rem;
|
|
54
|
+
border: 1px solid #f5c6cb;
|
|
55
|
+
|
|
56
|
+
pre {
|
|
57
|
+
background: rgba(0, 0, 0, 0.05);
|
|
58
|
+
padding: 0.5rem;
|
|
59
|
+
border-radius: 2px;
|
|
60
|
+
font-size: 0.8rem;
|
|
61
|
+
margin: 0.5rem 0 0 0;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/* Block info */
|
|
66
|
+
.block-info {
|
|
67
|
+
font-size: 0.9rem;
|
|
68
|
+
color: #666;
|
|
69
|
+
font-style: italic;
|
|
70
|
+
margin-top: 0.5rem;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/* Editor-specific styles */
|
|
75
|
+
.wp-block[data-type="{{namespace}}/{{slug}}"] {
|
|
76
|
+
.wp-block-{{slugKebabCase}} {
|
|
77
|
+
border: 1px dashed #ddd;
|
|
78
|
+
border-radius: 4px;
|
|
79
|
+
|
|
80
|
+
&:hover {
|
|
81
|
+
border-color: #0073aa;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|