create-wp-typia 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 (90) hide show
  1. package/README.md +33 -0
  2. package/dist/cli.js +87837 -0
  3. package/dist/highlights-eq9cgrbb.scm +604 -0
  4. package/dist/highlights-ghv9g403.scm +205 -0
  5. package/dist/highlights-hk7bwhj4.scm +284 -0
  6. package/dist/highlights-r812a2qc.scm +150 -0
  7. package/dist/highlights-x6tmsnaa.scm +115 -0
  8. package/dist/injections-73j83es3.scm +27 -0
  9. package/dist/tree-sitter-javascript-nd0q4pe9.wasm +0 -0
  10. package/dist/tree-sitter-markdown-411r6y9b.wasm +0 -0
  11. package/dist/tree-sitter-markdown_inline-j5349f42.wasm +0 -0
  12. package/dist/tree-sitter-typescript-zxjzwt75.wasm +0 -0
  13. package/dist/tree-sitter-zig-e78zbjpm.wasm +0 -0
  14. package/lib/entry.js +29 -0
  15. package/lib/node-cli.js +326 -0
  16. package/lib/package-managers.d.ts +29 -0
  17. package/lib/package-managers.js +170 -0
  18. package/lib/scaffold.d.ts +64 -0
  19. package/lib/scaffold.js +565 -0
  20. package/lib/template-registry.d.ts +18 -0
  21. package/lib/template-registry.js +58 -0
  22. package/package.json +64 -0
  23. package/src/cli.ts +329 -0
  24. package/templates/advanced/README.md.mustache +70 -0
  25. package/templates/advanced/block.json.mustache +42 -0
  26. package/templates/advanced/index.js +21 -0
  27. package/templates/advanced/package.json.mustache +48 -0
  28. package/templates/advanced/scripts/generate-migrations.ts.mustache +267 -0
  29. package/templates/advanced/scripts/lib/typia-metadata-core.ts +806 -0
  30. package/templates/advanced/scripts/migration-cli.ts.mustache +260 -0
  31. package/templates/advanced/scripts/sync-types-to-block-json.ts.mustache +25 -0
  32. package/templates/advanced/src/admin/migration-dashboard.tsx.mustache +450 -0
  33. package/templates/advanced/src/components/ErrorBoundary.tsx.mustache +47 -0
  34. package/templates/advanced/src/deprecated.ts.mustache +184 -0
  35. package/templates/advanced/src/edit.tsx.mustache +93 -0
  36. package/templates/advanced/src/hooks/useDebounce.ts.mustache +20 -0
  37. package/templates/advanced/src/hooks/useLocalStorage.ts.mustache +31 -0
  38. package/templates/advanced/src/hooks.ts.mustache +56 -0
  39. package/templates/advanced/src/index.tsx.mustache +16 -0
  40. package/templates/advanced/src/migration-detector.ts.mustache +417 -0
  41. package/templates/advanced/src/migrations/index.ts.mustache +361 -0
  42. package/templates/advanced/src/save.tsx.mustache +40 -0
  43. package/templates/advanced/src/style.scss.mustache +84 -0
  44. package/templates/advanced/src/types/versions.ts.mustache +108 -0
  45. package/templates/advanced/src/types.ts.mustache +45 -0
  46. package/templates/advanced/src/utils/classnames.ts.mustache +51 -0
  47. package/templates/advanced/src/utils/debounce.ts.mustache +37 -0
  48. package/templates/advanced/src/utils/index.ts.mustache +7 -0
  49. package/templates/advanced/src/utils/uuid.ts.mustache +17 -0
  50. package/templates/advanced/src/validators.ts.mustache +39 -0
  51. package/templates/advanced/src/view.ts.mustache +59 -0
  52. package/templates/advanced/tsconfig.json.mustache +9 -0
  53. package/templates/advanced/webpack.config.js.mustache +85 -0
  54. package/templates/basic/package.json.mustache +39 -0
  55. package/templates/basic/scripts/lib/typia-metadata-core.ts +806 -0
  56. package/templates/basic/scripts/sync-types-to-block-json.ts +25 -0
  57. package/templates/basic/src/block.json +51 -0
  58. package/templates/basic/src/edit.tsx +85 -0
  59. package/templates/basic/src/hooks.ts +75 -0
  60. package/templates/basic/src/index.tsx +37 -0
  61. package/templates/basic/src/save.tsx +27 -0
  62. package/templates/basic/src/style.scss +42 -0
  63. package/templates/basic/src/types.ts +47 -0
  64. package/templates/basic/src/validators.ts +39 -0
  65. package/templates/basic/tsconfig.json +20 -0
  66. package/templates/basic/webpack.config.js +85 -0
  67. package/templates/full/package.json.mustache +40 -0
  68. package/templates/full/scripts/lib/typia-metadata-core.ts +806 -0
  69. package/templates/full/scripts/sync-types-to-block-json.ts.mustache +25 -0
  70. package/templates/full/src/block.json.mustache +121 -0
  71. package/templates/full/src/edit.tsx.mustache +300 -0
  72. package/templates/full/src/editor.scss.mustache +251 -0
  73. package/templates/full/src/hooks.ts.mustache +140 -0
  74. package/templates/full/src/index.tsx.mustache +27 -0
  75. package/templates/full/src/save.tsx.mustache +39 -0
  76. package/templates/full/src/style.scss.mustache +224 -0
  77. package/templates/full/src/types.ts.mustache +34 -0
  78. package/templates/full/src/validators.ts.mustache +84 -0
  79. package/templates/full/tsconfig.json.mustache +9 -0
  80. package/templates/full/webpack.config.js.mustache +85 -0
  81. package/templates/interactivity/package.json.mustache +41 -0
  82. package/templates/interactivity/scripts/lib/typia-metadata-core.ts +806 -0
  83. package/templates/interactivity/scripts/sync-types-to-block-json.ts.mustache +25 -0
  84. package/templates/interactivity/src/block.json.mustache +75 -0
  85. package/templates/interactivity/src/edit.tsx.mustache +206 -0
  86. package/templates/interactivity/src/interactivity.ts.mustache +183 -0
  87. package/templates/interactivity/src/save.tsx.mustache +87 -0
  88. package/templates/interactivity/src/types.ts.mustache +29 -0
  89. package/templates/interactivity/tsconfig.json.mustache +9 -0
  90. package/templates/interactivity/webpack.config.js.mustache +85 -0
@@ -0,0 +1,450 @@
1
+ import React, { useState, useEffect } from 'react';
2
+ import { Button, Notice, Card, CardBody, Flex, FlexItem, ProgressBar } from '@wordpress/components';
3
+ import { __ } from '@wordpress/i18n';
4
+ import { scanSiteForMigrations, generateMigrationReport, BlockScanResult } from '../migration-detector';
5
+ import { autoMigrate } from '../migrations';
6
+
7
+ /**
8
+ * WordPress Admin Dashboard for {{title}} Migration Management
9
+ */
10
+
11
+ interface MigrationStats {
12
+ total: number;
13
+ needsMigration: number;
14
+ hasWarnings: number;
15
+ byVersion: Record<string, number>;
16
+ }
17
+
18
+ export function MigrationDashboard() {
19
+ const [scanResults, setScanResults] = useState<BlockScanResult[]>([]);
20
+ const [isScanning, setIsScanning] = useState(false);
21
+ const [isMigrating, setIsMigrating] = useState(false);
22
+ const [stats, setStats] = useState<MigrationStats | null>(null);
23
+ const [lastScan, setLastScan] = useState<Date | null>(null);
24
+
25
+ const runSiteScan = async () => {
26
+ setIsScanning(true);
27
+ try {
28
+ const results = await scanSiteForMigrations('{{namespace}}/{{slug}}');
29
+ setScanResults(results);
30
+ setLastScan(new Date());
31
+
32
+ // Calculate stats
33
+ const stats: MigrationStats = {
34
+ total: results.length,
35
+ needsMigration: results.filter(r => r.analysis.needsMigration).length,
36
+ hasWarnings: results.filter(r => r.analysis.warnings.length > 0).length,
37
+ byVersion: {}
38
+ };
39
+
40
+ results.forEach(r => {
41
+ const version = r.analysis.currentVersion;
42
+ stats.byVersion[version] = (stats.byVersion[version] || 0) + 1;
43
+ });
44
+
45
+ setStats(stats);
46
+
47
+ // Show admin notice
48
+ if (stats.needsMigration > 0) {
49
+ wp.data.dispatch('core/notices').createWarningNotice(
50
+ `Found ${stats.needsMigration} {{title}} blocks that need migration`,
51
+ {
52
+ type: 'snackbar',
53
+ actions: [
54
+ {
55
+ label: 'View Details',
56
+ onClick: () => {
57
+ // Focus migration section
58
+ document.getElementById('migration-results')?.scrollIntoView();
59
+ }
60
+ }
61
+ ]
62
+ }
63
+ );
64
+ }
65
+
66
+ } catch (error) {
67
+ console.error('Site scan failed:', error);
68
+ wp.data.dispatch('core/notices').createErrorNotice(
69
+ `Scan failed: ${error.message}`,
70
+ { type: 'snackbar' }
71
+ );
72
+ } finally {
73
+ setIsScanning(false);
74
+ }
75
+ };
76
+
77
+ const runMigration = async (blockResult: BlockScanResult) => {
78
+ try {
79
+ const migrationResult = autoMigrate(blockResult.attributes);
80
+ const migrated = migrationResult.data;
81
+
82
+ // Here you would save the migrated attributes back to WordPress
83
+ // Implementation depends on whether this is in admin or REST API
84
+ console.log('Migrated block:', migrated);
85
+
86
+ wp.data.dispatch('core/notices').createSuccessNotice(
87
+ `Block in "${blockResult.postTitle}" migrated successfully`,
88
+ { type: 'snackbar' }
89
+ );
90
+
91
+ // Refresh scan results
92
+ await runSiteScan();
93
+
94
+ } catch (error) {
95
+ console.error('Migration failed:', error);
96
+ wp.data.dispatch('core/notices').createErrorNotice(
97
+ `Migration failed: ${error.message}`,
98
+ { type: 'snackbar' }
99
+ );
100
+ }
101
+ };
102
+
103
+ const batchMigrate = async () => {
104
+ setIsMigrating(true);
105
+ const needsMigration = scanResults.filter(r => r.analysis.needsMigration);
106
+ let success = 0;
107
+ let failed = 0;
108
+
109
+ for (const blockResult of needsMigration) {
110
+ try {
111
+ await runMigration(blockResult);
112
+ success++;
113
+ } catch (error) {
114
+ failed++;
115
+ }
116
+ }
117
+
118
+ wp.data.dispatch('core/notices').createInfoNotice(
119
+ `Batch migration complete: ${success} success, ${failed} failed`,
120
+ { type: 'snackbar' }
121
+ );
122
+
123
+ setIsMigrating(false);
124
+ };
125
+
126
+ const downloadReport = () => {
127
+ const report = generateMigrationReport(scanResults);
128
+ const blob = new Blob([report], { type: 'text/markdown' });
129
+ const url = URL.createObjectURL(blob);
130
+ const a = document.createElement('a');
131
+ a.href = url;
132
+ a.download = `{{slug}}-migration-report-${new Date().toISOString().split('T')[0]}.md`;
133
+ document.body.appendChild(a);
134
+ a.click();
135
+ document.body.removeChild(a);
136
+ URL.revokeObjectURL(url);
137
+ };
138
+
139
+ // Auto-scan on component mount
140
+ useEffect(() => {
141
+ runSiteScan();
142
+ }, []);
143
+
144
+ return (
145
+ <div className="{{slug}}-migration-dashboard">
146
+ <div className="wrap">
147
+ <h1>{__('{{title}} Migration Manager', '{{textdomain}}')}</h1>
148
+
149
+ {/* Scan Controls */}
150
+ <Card>
151
+ <CardBody>
152
+ <Flex justify="space-between" align="center">
153
+ <FlexItem>
154
+ <h2>{__('Site Scan', '{{textdomain}}')}</h2>
155
+ {lastScan && (
156
+ <p className="description">
157
+ {__('Last scan:', '{{textdomain}}')} {lastScan.toLocaleString()}
158
+ </p>
159
+ )}
160
+ </FlexItem>
161
+ <FlexItem>
162
+ <Flex gap={2}>
163
+ <Button
164
+ variant="secondary"
165
+ onClick={runSiteScan}
166
+ isBusy={isScanning}
167
+ disabled={isScanning}
168
+ >
169
+ {isScanning ? __('Scanning...', '{{textdomain}}') : __('Scan Site', '{{textdomain}}')}
170
+ </Button>
171
+ {scanResults.length > 0 && (
172
+ <Button
173
+ variant="secondary"
174
+ onClick={downloadReport}
175
+ >
176
+ {__('Download Report', '{{textdomain}}')}
177
+ </Button>
178
+ )}
179
+ </Flex>
180
+ </FlexItem>
181
+ </Flex>
182
+ </CardBody>
183
+ </Card>
184
+
185
+ {/* Statistics */}
186
+ {stats && (
187
+ <Card>
188
+ <CardBody>
189
+ <h2>{__('Migration Status', '{{textdomain}}')}</h2>
190
+ <Flex wrap gap={4}>
191
+ <FlexItem>
192
+ <div className="migration-stat">
193
+ <div className="stat-number">{stats.total}</div>
194
+ <div className="stat-label">{__('Total Blocks', '{{textdomain}}')}</div>
195
+ </div>
196
+ </FlexItem>
197
+ <FlexItem>
198
+ <div className="migration-stat migration-needed">
199
+ <div className="stat-number">{stats.needsMigration}</div>
200
+ <div className="stat-label">{__('Need Migration', '{{textdomain}}')}</div>
201
+ </div>
202
+ </FlexItem>
203
+ <FlexItem>
204
+ <div className="migration-stat has-warnings">
205
+ <div className="stat-number">{stats.hasWarnings}</div>
206
+ <div className="stat-label">{__('Has Warnings', '{{textdomain}}')}</div>
207
+ </div>
208
+ </FlexItem>
209
+ </Flex>
210
+
211
+ {/* Version breakdown */}
212
+ {Object.keys(stats.byVersion).length > 0 && (
213
+ <div className="version-breakdown">
214
+ <h3>{__('Version Distribution', '{{textdomain}}')}</h3>
215
+ {Object.entries(stats.byVersion).map(([version, count]) => (
216
+ <div key={version} className="version-bar">
217
+ <span className="version-label">v{version}</span>
218
+ <ProgressBar value={count} max={stats.total} />
219
+ <span className="version-count">{count}</span>
220
+ </div>
221
+ ))}
222
+ </div>
223
+ )}
224
+ </CardBody>
225
+ </Card>
226
+ )}
227
+
228
+ {/* Migration Actions */}
229
+ {stats && stats.needsMigration > 0 && (
230
+ <Card>
231
+ <CardBody>
232
+ <Flex justify="space-between" align="center">
233
+ <FlexItem>
234
+ <h2>{__('Migration Actions', '{{textdomain}}')}</h2>
235
+ <p>
236
+ {__('Found {{needsMigration}} blocks that need migration to the latest version.', '{{textdomain}}')
237
+ .replace('{{needsMigration}}', stats.needsMigration.toString())}
238
+ </p>
239
+ </FlexItem>
240
+ <FlexItem>
241
+ <Button
242
+ variant="primary"
243
+ onClick={batchMigrate}
244
+ isBusy={isMigrating}
245
+ disabled={isMigrating}
246
+ >
247
+ {isMigrating
248
+ ? __('Migrating...', '{{textdomain}}')
249
+ : __(`Migrate All (${stats.needsMigration})`, '{{textdomain}}')
250
+ }
251
+ </Button>
252
+ </FlexItem>
253
+ </Flex>
254
+ </CardBody>
255
+ </Card>
256
+ )}
257
+
258
+ {/* Results Table */}
259
+ {scanResults.length > 0 && (
260
+ <Card id="migration-results">
261
+ <CardBody>
262
+ <h2>{__('Scan Results', '{{textdomain}}')}</h2>
263
+ <div className="migration-results-table">
264
+ {scanResults.map((result, index) => (
265
+ <div key={index} className="migration-result-row">
266
+ <div className="result-header">
267
+ <h4>"{result.postTitle}" <span className="post-id">(#{result.postId})</span></h4>
268
+ <div className="result-meta">
269
+ <span className={`version-badge version-${result.analysis.currentVersion.replace(/\./g, '-')}`}>
270
+ v{result.analysis.currentVersion}
271
+ </span>
272
+ <span className="confidence">
273
+ {(result.analysis.confidence * 100).toFixed(0)}% confidence
274
+ </span>
275
+ </div>
276
+ </div>
277
+
278
+ {result.analysis.needsMigration && (
279
+ <Notice status="warning" isDismissible={false}>
280
+ <strong>{__('Migration Required', '{{textdomain}}')}</strong>
281
+ <ul>
282
+ {result.analysis.reasons.map((reason, i) => (
283
+ <li key={i}>{reason}</li>
284
+ ))}
285
+ </ul>
286
+ <Button
287
+ variant="secondary"
288
+ size="small"
289
+ onClick={() => runMigration(result)}
290
+ >
291
+ {__('Migrate This Block', '{{textdomain}}')}
292
+ </Button>
293
+ </Notice>
294
+ )}
295
+
296
+ {result.analysis.warnings.length > 0 && (
297
+ <Notice status="info" isDismissible={false}>
298
+ <strong>{__('Warnings', '{{textdomain}}')}</strong>
299
+ <ul>
300
+ {result.analysis.warnings.map((warning, i) => (
301
+ <li key={i}>{warning}</li>
302
+ ))}
303
+ </ul>
304
+ </Notice>
305
+ )}
306
+
307
+ {result.analysis.affectedFields.added.length > 0 && (
308
+ <div className="affected-fields">
309
+ <strong>{__('New fields to be added:', '{{textdomain}}')}</strong> {' '}
310
+ {result.analysis.affectedFields.added.join(', ')}
311
+ </div>
312
+ )}
313
+ </div>
314
+ ))}
315
+ </div>
316
+ </CardBody>
317
+ </Card>
318
+ )}
319
+
320
+ {/* No Results */}
321
+ {!isScanning && scanResults.length === 0 && lastScan && (
322
+ <Card>
323
+ <CardBody>
324
+ <Notice status="success" isDismissible={false}>
325
+ <p>{__('๐ŸŽ‰ No {{title}} blocks found that need migration. All blocks are up to date!', '{{textdomain}}')}</p>
326
+ </Notice>
327
+ </CardBody>
328
+ </Card>
329
+ )}
330
+ </div>
331
+
332
+ <style jsx>{`
333
+ .{{slug}}-migration-dashboard {
334
+ margin: 20px 0;
335
+ }
336
+
337
+ .migration-stat {
338
+ text-align: center;
339
+ padding: 15px;
340
+ border: 1px solid #ddd;
341
+ border-radius: 4px;
342
+ min-width: 120px;
343
+ }
344
+
345
+ .migration-stat.migration-needed {
346
+ border-color: #f56565;
347
+ background: #fef5e7;
348
+ }
349
+
350
+ .migration-stat.has-warnings {
351
+ border-color: #ed8936;
352
+ background: #fffaf0;
353
+ }
354
+
355
+ .stat-number {
356
+ font-size: 24px;
357
+ font-weight: bold;
358
+ }
359
+
360
+ .stat-label {
361
+ font-size: 12px;
362
+ color: #666;
363
+ }
364
+
365
+ .version-breakdown {
366
+ margin-top: 20px;
367
+ }
368
+
369
+ .version-bar {
370
+ display: flex;
371
+ align-items: center;
372
+ gap: 10px;
373
+ margin: 5px 0;
374
+ }
375
+
376
+ .version-label {
377
+ min-width: 60px;
378
+ font-family: monospace;
379
+ }
380
+
381
+ .version-count {
382
+ min-width: 30px;
383
+ text-align: right;
384
+ }
385
+
386
+ .migration-result-row {
387
+ border: 1px solid #ddd;
388
+ border-radius: 4px;
389
+ padding: 15px;
390
+ margin: 10px 0;
391
+ }
392
+
393
+ .result-header {
394
+ display: flex;
395
+ justify-content: space-between;
396
+ align-items: center;
397
+ margin-bottom: 10px;
398
+ }
399
+
400
+ .result-header h4 {
401
+ margin: 0;
402
+ }
403
+
404
+ .post-id {
405
+ color: #666;
406
+ font-weight: normal;
407
+ }
408
+
409
+ .result-meta {
410
+ display: flex;
411
+ gap: 10px;
412
+ align-items: center;
413
+ }
414
+
415
+ .version-badge {
416
+ background: #e2e8f0;
417
+ color: #2d3748;
418
+ padding: 2px 8px;
419
+ border-radius: 12px;
420
+ font-size: 11px;
421
+ font-weight: bold;
422
+ }
423
+
424
+ .version-badge.version-1-0-0 {
425
+ background: #fed7d7;
426
+ color: #c53030;
427
+ }
428
+
429
+ .version-badge.version-2-0-0 {
430
+ background: #feebc8;
431
+ color: #dd6b20;
432
+ }
433
+
434
+ .confidence {
435
+ font-size: 12px;
436
+ color: #666;
437
+ }
438
+
439
+ .affected-fields {
440
+ font-size: 13px;
441
+ color: #2d3748;
442
+ margin-top: 10px;
443
+ padding: 8px;
444
+ background: #f7fafc;
445
+ border-left: 3px solid #4299e1;
446
+ }
447
+ `}</style>
448
+ </div>
449
+ );
450
+ }
@@ -0,0 +1,47 @@
1
+ import React, { Component, ErrorInfo, ReactNode } from 'react';
2
+
3
+ interface Props {
4
+ children: ReactNode;
5
+ fallback?: ReactNode;
6
+ }
7
+
8
+ interface State {
9
+ hasError: boolean;
10
+ error?: Error;
11
+ }
12
+
13
+ /**
14
+ * Error Boundary component for {{title}}
15
+ */
16
+ export class ErrorBoundary extends Component<Props, State> {
17
+ public state: State = {
18
+ hasError: false
19
+ };
20
+
21
+ public static getDerivedStateFromError(error: Error): State {
22
+ return { hasError: true, error };
23
+ }
24
+
25
+ public componentDidCatch(error: Error, errorInfo: ErrorInfo) {
26
+ console.error('{{title}} Error:', error, errorInfo);
27
+ }
28
+
29
+ public render() {
30
+ if (this.state.hasError) {
31
+ return this.props.fallback || (
32
+ <div className="{{dashCase}}-error-boundary">
33
+ <h3>Something went wrong with {{title}}</h3>
34
+ <p>Please check the console for more details.</p>
35
+ {this.state.error && (
36
+ <details>
37
+ <summary>Error details</summary>
38
+ <pre>{this.state.error.stack}</pre>
39
+ </details>
40
+ )}
41
+ </div>
42
+ );
43
+ }
44
+
45
+ return this.props.children;
46
+ }
47
+ }
@@ -0,0 +1,184 @@
1
+ import { BlockConfiguration } from '@wordpress/blocks';
2
+ import { {{titleCase}}AttributesV1, {{titleCase}}AttributesV2, {{titleCase}}Attributes } from './types/versions';
3
+ import { autoMigrate, generateMigrationReport } from './migrations';
4
+
5
+ /**
6
+ * WordPress block deprecated versions with Typia-powered migrations
7
+ */
8
+
9
+ // V1 Deprecated (Original version)
10
+ const v1: BlockConfiguration['deprecated'][0] = {
11
+ attributes: {
12
+ content: {
13
+ type: 'string',
14
+ default: ''
15
+ },
16
+ alignment: {
17
+ type: 'string',
18
+ default: 'left'
19
+ }
20
+ },
21
+
22
+ save({ attributes }: { attributes: {{titleCase}}AttributesV1 }) {
23
+ return (
24
+ <p style={{ textAlign: attributes.alignment }}>
25
+ {attributes.content}
26
+ </p>
27
+ );
28
+ },
29
+
30
+ migrate(attributes: {{titleCase}}AttributesV1) {
31
+ console.log('๐Ÿ”„ WordPress migration: V1 detected, using Typia auto-migration');
32
+
33
+ const migrated = autoMigrate(attributes);
34
+ const report = generateMigrationReport(attributes, migrated);
35
+
36
+ console.log('๐Ÿ“Š Migration Report:', report);
37
+
38
+ // Log to WordPress admin if available
39
+ if (typeof wp !== 'undefined' && wp.data && wp.data.dispatch('core/notices')) {
40
+ wp.data.dispatch('core/notices').createSuccessNotice(
41
+ `{{title}} block migrated from v${report.fromVersion} to v${report.toVersion}`,
42
+ { type: 'snackbar' }
43
+ );
44
+ }
45
+
46
+ return migrated;
47
+ },
48
+
49
+ isEligible(attributes: any) {
50
+ // Check if this looks like a V1 block
51
+ return (
52
+ typeof attributes.content === 'string' &&
53
+ !('isVisible' in attributes) && // No V2+ fields
54
+ !('id' in attributes) && // No V3+ fields
55
+ !('version' in attributes) // No version tracking
56
+ );
57
+ }
58
+ };
59
+
60
+ // V2 Deprecated (Added visibility and className)
61
+ const v2: BlockConfiguration['deprecated'][0] = {
62
+ attributes: {
63
+ content: {
64
+ type: 'string',
65
+ default: ''
66
+ },
67
+ alignment: {
68
+ type: 'string',
69
+ default: 'left'
70
+ },
71
+ isVisible: {
72
+ type: 'boolean',
73
+ default: true
74
+ },
75
+ className: {
76
+ type: 'string'
77
+ }
78
+ },
79
+
80
+ save({ attributes }: { attributes: {{titleCase}}AttributesV2 }) {
81
+ return attributes.isVisible ? (
82
+ <p
83
+ className={attributes.className}
84
+ style={{ textAlign: attributes.alignment }}
85
+ >
86
+ {attributes.content}
87
+ </p>
88
+ ) : null;
89
+ },
90
+
91
+ migrate(attributes: {{titleCase}}AttributesV2) {
92
+ console.log('๐Ÿ”„ WordPress migration: V2 detected, using Typia auto-migration');
93
+
94
+ const migrated = autoMigrate(attributes);
95
+ const report = generateMigrationReport(attributes, migrated);
96
+
97
+ console.log('๐Ÿ“Š Migration Report:', report);
98
+
99
+ // Enhanced migration notice for V2
100
+ if (typeof wp !== 'undefined' && wp.data && wp.data.dispatch('core/notices')) {
101
+ const changes = report.changes.length;
102
+ wp.data.dispatch('core/notices').createInfoNotice(
103
+ `{{title}} block updated: ${changes} new feature${changes !== 1 ? 's' : ''} added`,
104
+ {
105
+ type: 'snackbar',
106
+ actions: [
107
+ {
108
+ label: 'View Changes',
109
+ onClick: () => console.table(report)
110
+ }
111
+ ]
112
+ }
113
+ );
114
+ }
115
+
116
+ return migrated;
117
+ },
118
+
119
+ isEligible(attributes: any) {
120
+ // Check if this looks like a V2 block
121
+ return (
122
+ typeof attributes.content === 'string' &&
123
+ ('isVisible' in attributes || 'className' in attributes) && // Has V2 fields
124
+ !('id' in attributes) && // No V3+ fields
125
+ !('version' in attributes) && // No version tracking
126
+ !('theme' in attributes) // No V3 theme field
127
+ );
128
+ }
129
+ };
130
+
131
+ /**
132
+ * All deprecated versions in reverse chronological order
133
+ * (WordPress processes them from newest to oldest)
134
+ */
135
+ export const deprecated: BlockConfiguration['deprecated'] = [v2, v1];
136
+
137
+ /**
138
+ * Migration utilities for development/debugging
139
+ */
140
+ export const migrationUtils = {
141
+ /**
142
+ * Test migration with sample data
143
+ */
144
+ testMigration() {
145
+ console.log('๐Ÿงช Testing {{title}} migrations...');
146
+
147
+ // Test V1 โ†’ Current
148
+ const v1Sample: {{titleCase}}AttributesV1 = {
149
+ content: "Hello World",
150
+ alignment: "center"
151
+ };
152
+
153
+ console.log('๐Ÿ“„ V1 Sample:', v1Sample);
154
+ const v1Migrated = autoMigrate(v1Sample);
155
+ console.log('โžก๏ธ V1 Migrated:', v1Migrated);
156
+
157
+ // Test V2 โ†’ Current
158
+ const v2Sample: {{titleCase}}AttributesV2 = {
159
+ content: "Hello World V2",
160
+ alignment: "right",
161
+ isVisible: true,
162
+ className: "custom-class"
163
+ };
164
+
165
+ console.log('๐Ÿ“„ V2 Sample:', v2Sample);
166
+ const v2Migrated = autoMigrate(v2Sample);
167
+ console.log('โžก๏ธ V2 Migrated:', v2Migrated);
168
+
169
+ console.log('โœ… Migration tests complete');
170
+ },
171
+
172
+ /**
173
+ * Get migration statistics
174
+ */
175
+ getStats() {
176
+ return {
177
+ supportedVersions: ['1.0.0', '2.0.0', '3.0.0'],
178
+ currentVersion: '3.0.0',
179
+ totalMigrations: deprecated.length,
180
+ autoMigrationEnabled: true,
181
+ typiaValidation: true
182
+ };
183
+ }
184
+ };