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.
- package/README.md +33 -0
- package/dist/cli.js +87837 -0
- package/dist/highlights-eq9cgrbb.scm +604 -0
- package/dist/highlights-ghv9g403.scm +205 -0
- package/dist/highlights-hk7bwhj4.scm +284 -0
- package/dist/highlights-r812a2qc.scm +150 -0
- package/dist/highlights-x6tmsnaa.scm +115 -0
- package/dist/injections-73j83es3.scm +27 -0
- package/dist/tree-sitter-javascript-nd0q4pe9.wasm +0 -0
- package/dist/tree-sitter-markdown-411r6y9b.wasm +0 -0
- package/dist/tree-sitter-markdown_inline-j5349f42.wasm +0 -0
- package/dist/tree-sitter-typescript-zxjzwt75.wasm +0 -0
- package/dist/tree-sitter-zig-e78zbjpm.wasm +0 -0
- package/lib/entry.js +29 -0
- package/lib/node-cli.js +326 -0
- package/lib/package-managers.d.ts +29 -0
- package/lib/package-managers.js +170 -0
- package/lib/scaffold.d.ts +64 -0
- package/lib/scaffold.js +565 -0
- package/lib/template-registry.d.ts +18 -0
- package/lib/template-registry.js +58 -0
- package/package.json +64 -0
- package/src/cli.ts +329 -0
- package/templates/advanced/README.md.mustache +70 -0
- package/templates/advanced/block.json.mustache +42 -0
- package/templates/advanced/index.js +21 -0
- package/templates/advanced/package.json.mustache +48 -0
- package/templates/advanced/scripts/generate-migrations.ts.mustache +267 -0
- package/templates/advanced/scripts/lib/typia-metadata-core.ts +806 -0
- package/templates/advanced/scripts/migration-cli.ts.mustache +260 -0
- package/templates/advanced/scripts/sync-types-to-block-json.ts.mustache +25 -0
- package/templates/advanced/src/admin/migration-dashboard.tsx.mustache +450 -0
- package/templates/advanced/src/components/ErrorBoundary.tsx.mustache +47 -0
- package/templates/advanced/src/deprecated.ts.mustache +184 -0
- package/templates/advanced/src/edit.tsx.mustache +93 -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 +16 -0
- package/templates/advanced/src/migration-detector.ts.mustache +417 -0
- package/templates/advanced/src/migrations/index.ts.mustache +361 -0
- package/templates/advanced/src/save.tsx.mustache +40 -0
- package/templates/advanced/src/style.scss.mustache +84 -0
- package/templates/advanced/src/types/versions.ts.mustache +108 -0
- package/templates/advanced/src/types.ts.mustache +45 -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 +9 -0
- package/templates/advanced/webpack.config.js.mustache +85 -0
- package/templates/basic/package.json.mustache +39 -0
- package/templates/basic/scripts/lib/typia-metadata-core.ts +806 -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 +47 -0
- package/templates/basic/src/validators.ts +39 -0
- package/templates/basic/tsconfig.json +20 -0
- package/templates/basic/webpack.config.js +85 -0
- package/templates/full/package.json.mustache +40 -0
- package/templates/full/scripts/lib/typia-metadata-core.ts +806 -0
- package/templates/full/scripts/sync-types-to-block-json.ts.mustache +25 -0
- package/templates/full/src/block.json.mustache +121 -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 +140 -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 +34 -0
- package/templates/full/src/validators.ts.mustache +84 -0
- package/templates/full/tsconfig.json.mustache +9 -0
- package/templates/full/webpack.config.js.mustache +85 -0
- package/templates/interactivity/package.json.mustache +41 -0
- package/templates/interactivity/scripts/lib/typia-metadata-core.ts +806 -0
- package/templates/interactivity/scripts/sync-types-to-block-json.ts.mustache +25 -0
- package/templates/interactivity/src/block.json.mustache +75 -0
- package/templates/interactivity/src/edit.tsx.mustache +206 -0
- package/templates/interactivity/src/interactivity.ts.mustache +183 -0
- package/templates/interactivity/src/save.tsx.mustache +87 -0
- package/templates/interactivity/src/types.ts.mustache +29 -0
- package/templates/interactivity/tsconfig.json.mustache +9 -0
- 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
|
+
};
|