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,93 @@
|
|
|
1
|
+
import { BlockEditProps } from '@wordpress/blocks';
|
|
2
|
+
import { useBlockProps, InspectorControls, RichText } from '@wordpress/block-editor';
|
|
3
|
+
import { PanelBody, ToggleControl, SelectControl } from '@wordpress/components';
|
|
4
|
+
import { __ } from '@wordpress/i18n';
|
|
5
|
+
import { {{titleCase}}Attributes } from './types';
|
|
6
|
+
import { validators } from './validators';
|
|
7
|
+
import { useTypiaValidation, useAttributeLogger, useDebounce } from './hooks';
|
|
8
|
+
import { ErrorBoundary } from './components/ErrorBoundary';
|
|
9
|
+
import { classNames } from './utils';
|
|
10
|
+
|
|
11
|
+
type EditProps = BlockEditProps<{{titleCase}}Attributes>;
|
|
12
|
+
|
|
13
|
+
export default function Edit({ attributes, setAttributes }: EditProps) {
|
|
14
|
+
const blockProps = useBlockProps();
|
|
15
|
+
const { isValid, errors } = useTypiaValidation(attributes, validators.validate);
|
|
16
|
+
const debouncedAttributes = useDebounce(attributes, 300);
|
|
17
|
+
|
|
18
|
+
// Log attribute changes in development
|
|
19
|
+
useAttributeLogger(debouncedAttributes);
|
|
20
|
+
|
|
21
|
+
const updateAttribute = <K extends keyof {{titleCase}}Attributes>(
|
|
22
|
+
key: K,
|
|
23
|
+
value: {{titleCase}}Attributes[K]
|
|
24
|
+
) => {
|
|
25
|
+
const newAttrs = { ...attributes, [key]: value };
|
|
26
|
+
const validation = validators.validate(newAttrs);
|
|
27
|
+
|
|
28
|
+
if (validation.success) {
|
|
29
|
+
setAttributes({ [key]: value } as Partial<{{titleCase}}Attributes>);
|
|
30
|
+
} else {
|
|
31
|
+
console.error(`Validation failed for ${String(key)}:`, validation.errors);
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<ErrorBoundary>
|
|
37
|
+
<InspectorControls>
|
|
38
|
+
<PanelBody title={__('Settings', '{{textdomain}}')}>
|
|
39
|
+
<ToggleControl
|
|
40
|
+
label={__('Visible', '{{textdomain}}')}
|
|
41
|
+
checked={attributes.isVisible ?? true}
|
|
42
|
+
onChange={(isVisible) => updateAttribute('isVisible', isVisible)}
|
|
43
|
+
/>
|
|
44
|
+
<SelectControl
|
|
45
|
+
label={__('Alignment', '{{textdomain}}')}
|
|
46
|
+
value={attributes.alignment ?? 'left'}
|
|
47
|
+
options={[
|
|
48
|
+
{ label: __('Left', '{{textdomain}}'), value: 'left' },
|
|
49
|
+
{ label: __('Center', '{{textdomain}}'), value: 'center' },
|
|
50
|
+
{ label: __('Right', '{{textdomain}}'), value: 'right' },
|
|
51
|
+
{ label: __('Justify', '{{textdomain}}'), value: 'justify' },
|
|
52
|
+
]}
|
|
53
|
+
onChange={(alignment) => updateAttribute('alignment', alignment as any)}
|
|
54
|
+
/>
|
|
55
|
+
{!isValid && (
|
|
56
|
+
<div className="components-notice is-error">
|
|
57
|
+
<p><strong>{__('Validation Errors:', '{{textdomain}}')}</strong></p>
|
|
58
|
+
<ul style={{ margin: 0, paddingLeft: '1em' }}>
|
|
59
|
+
{errors.map((error, index) => (
|
|
60
|
+
<li key={index}>
|
|
61
|
+
<code>{error.path}</code>: {error.message}
|
|
62
|
+
</li>
|
|
63
|
+
))}
|
|
64
|
+
</ul>
|
|
65
|
+
</div>
|
|
66
|
+
)}
|
|
67
|
+
</PanelBody>
|
|
68
|
+
</InspectorControls>
|
|
69
|
+
|
|
70
|
+
<div {...blockProps} className={classNames(blockProps.className, {
|
|
71
|
+
'has-validation-errors': !isValid,
|
|
72
|
+
'is-hidden': !attributes.isVisible
|
|
73
|
+
})}>
|
|
74
|
+
<RichText
|
|
75
|
+
tagName="p"
|
|
76
|
+
value={attributes.content}
|
|
77
|
+
onChange={(content) => updateAttribute('content', content)}
|
|
78
|
+
placeholder={__('Enter your text...', '{{textdomain}}')}
|
|
79
|
+
className={classNames('{{dashCase}}-content', `align-${attributes.alignment}`)}
|
|
80
|
+
style={{
|
|
81
|
+
textAlign: attributes.alignment ?? 'left'
|
|
82
|
+
}}
|
|
83
|
+
/>
|
|
84
|
+
|
|
85
|
+
<div className="block-info">
|
|
86
|
+
<small>
|
|
87
|
+
{__('{{title}} – Enhanced with utilities, hooks, and error handling!', '{{textdomain}}')}
|
|
88
|
+
</small>
|
|
89
|
+
</div>
|
|
90
|
+
</div>
|
|
91
|
+
</ErrorBoundary>
|
|
92
|
+
);
|
|
93
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { useState, useEffect } from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Hook for debounced value
|
|
5
|
+
*/
|
|
6
|
+
export function useDebounce<T>(value: T, delay: number): T {
|
|
7
|
+
const [debouncedValue, setDebouncedValue] = useState<T>(value);
|
|
8
|
+
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
const handler = setTimeout(() => {
|
|
11
|
+
setDebouncedValue(value);
|
|
12
|
+
}, delay);
|
|
13
|
+
|
|
14
|
+
return () => {
|
|
15
|
+
clearTimeout(handler);
|
|
16
|
+
};
|
|
17
|
+
}, [value, delay]);
|
|
18
|
+
|
|
19
|
+
return debouncedValue;
|
|
20
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { useState, useEffect } from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Hook for localStorage with type safety
|
|
5
|
+
*/
|
|
6
|
+
export function useLocalStorage<T>(
|
|
7
|
+
key: string,
|
|
8
|
+
initialValue: T
|
|
9
|
+
): [T, (value: T | ((val: T) => T)) => void] {
|
|
10
|
+
const [storedValue, setStoredValue] = useState<T>(() => {
|
|
11
|
+
try {
|
|
12
|
+
const item = window.localStorage.getItem(key);
|
|
13
|
+
return item ? JSON.parse(item) : initialValue;
|
|
14
|
+
} catch (error) {
|
|
15
|
+
console.error(`Error reading localStorage key "${key}":`, error);
|
|
16
|
+
return initialValue;
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const setValue = (value: T | ((val: T) => T)) => {
|
|
21
|
+
try {
|
|
22
|
+
const valueToStore = value instanceof Function ? value(storedValue) : value;
|
|
23
|
+
setStoredValue(valueToStore);
|
|
24
|
+
window.localStorage.setItem(key, JSON.stringify(valueToStore));
|
|
25
|
+
} catch (error) {
|
|
26
|
+
console.error(`Error setting localStorage key "${key}":`, error);
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
return [storedValue, setValue];
|
|
31
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { useEffect, useState } from '@wordpress/element';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Custom hooks for {{title}}
|
|
5
|
+
*/
|
|
6
|
+
export * from './hooks/useDebounce';
|
|
7
|
+
export * from './hooks/useLocalStorage';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Hook for Typia validation with real-time feedback
|
|
11
|
+
*/
|
|
12
|
+
export function useTypiaValidation<T>(
|
|
13
|
+
data: T,
|
|
14
|
+
validator: (value: T) => { success: boolean; errors?: any[] }
|
|
15
|
+
) {
|
|
16
|
+
const [isValid, setIsValid] = useState(true);
|
|
17
|
+
const [errors, setErrors] = useState<any[]>([]);
|
|
18
|
+
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
const result = validator(data);
|
|
21
|
+
setIsValid(result.success);
|
|
22
|
+
setErrors(result.errors || []);
|
|
23
|
+
}, [data, validator]);
|
|
24
|
+
|
|
25
|
+
return { isValid, errors };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Hook for generating UUID
|
|
30
|
+
*/
|
|
31
|
+
export function useUUID() {
|
|
32
|
+
const [uuid] = useState(() => {
|
|
33
|
+
if (typeof crypto !== 'undefined' && crypto.randomUUID) {
|
|
34
|
+
return crypto.randomUUID();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
|
|
38
|
+
const r = Math.random() * 16 | 0;
|
|
39
|
+
const v = c === 'x' ? r : (r & 0x3 | 0x8);
|
|
40
|
+
return v.toString(16);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
return uuid;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Hook for logging attribute changes in development
|
|
49
|
+
*/
|
|
50
|
+
export function useAttributeLogger(attributes: any) {
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
if (process.env.NODE_ENV === 'development') {
|
|
53
|
+
console.log('{{title}} attributes changed:', attributes);
|
|
54
|
+
}
|
|
55
|
+
}, [attributes]);
|
|
56
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { registerBlockType } from '@wordpress/blocks';
|
|
2
|
+
import { __ } from '@wordpress/i18n';
|
|
3
|
+
import Edit from './edit';
|
|
4
|
+
import Save from './save';
|
|
5
|
+
import { {{titleCase}}Attributes } from './types';
|
|
6
|
+
import { validators } from './validators';
|
|
7
|
+
import './style.scss';
|
|
8
|
+
|
|
9
|
+
registerBlockType<{{titleCase}}Attributes>('{{namespace}}/{{slug}}', {
|
|
10
|
+
// Use Typia to generate example data
|
|
11
|
+
example: {
|
|
12
|
+
attributes: validators.random()
|
|
13
|
+
},
|
|
14
|
+
edit: Edit,
|
|
15
|
+
save: Save,
|
|
16
|
+
});
|
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
import typia from "typia";
|
|
2
|
+
import {
|
|
3
|
+
{{titleCase}}AttributesV1,
|
|
4
|
+
{{titleCase}}AttributesV2,
|
|
5
|
+
{{titleCase}}AttributesV3,
|
|
6
|
+
{{titleCase}}Attributes,
|
|
7
|
+
BLOCK_VERSIONS
|
|
8
|
+
} from './types/versions';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* 🔍 WordPress Block Migration Detection System
|
|
12
|
+
*
|
|
13
|
+
* Automatically detects if blocks need migration and provides
|
|
14
|
+
* detailed analysis of required upgrades
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
export interface MigrationError extends Error {
|
|
18
|
+
name: string;
|
|
19
|
+
message: string;
|
|
20
|
+
stack?: string;
|
|
21
|
+
code: string;
|
|
22
|
+
details?: any;
|
|
23
|
+
timestamp: string;
|
|
24
|
+
blockData?: any;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface MigrationAnalysis {
|
|
28
|
+
needsMigration: boolean;
|
|
29
|
+
currentVersion: string;
|
|
30
|
+
targetVersion: string;
|
|
31
|
+
confidence: number; // 0-1
|
|
32
|
+
reasons: string[];
|
|
33
|
+
warnings: string[];
|
|
34
|
+
blockCount?: number;
|
|
35
|
+
affectedFields: {
|
|
36
|
+
added: string[];
|
|
37
|
+
modified: string[];
|
|
38
|
+
deprecated: string[];
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface BlockScanResult {
|
|
43
|
+
blockName: string;
|
|
44
|
+
postId?: number;
|
|
45
|
+
postTitle?: string;
|
|
46
|
+
analysis: MigrationAnalysis;
|
|
47
|
+
attributes: any;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Detect if a single block needs migration
|
|
52
|
+
*/
|
|
53
|
+
export function detectBlockMigration(attributes: any, blockName?: string): MigrationAnalysis {
|
|
54
|
+
const analysis: MigrationAnalysis = {
|
|
55
|
+
needsMigration: false,
|
|
56
|
+
currentVersion: "unknown",
|
|
57
|
+
targetVersion: "3.0.0",
|
|
58
|
+
confidence: 0,
|
|
59
|
+
reasons: [],
|
|
60
|
+
warnings: [],
|
|
61
|
+
affectedFields: {
|
|
62
|
+
added: [],
|
|
63
|
+
modified: [],
|
|
64
|
+
deprecated: []
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
// 1. Structure-based detection
|
|
70
|
+
const structureDetection = detectVersionByStructure(attributes);
|
|
71
|
+
analysis.currentVersion = structureDetection.version;
|
|
72
|
+
analysis.confidence = structureDetection.confidence;
|
|
73
|
+
|
|
74
|
+
// 2. Schema validation against current version
|
|
75
|
+
const currentValidation = typia.validate<{{titleCase}}Attributes>(attributes);
|
|
76
|
+
|
|
77
|
+
if (!currentValidation.success) {
|
|
78
|
+
analysis.needsMigration = true;
|
|
79
|
+
analysis.reasons.push(`Block doesn't match current schema (v${analysis.targetVersion})`);
|
|
80
|
+
|
|
81
|
+
// Analyze validation errors to understand what's missing/wrong
|
|
82
|
+
analyzeValidationErrors(currentValidation.errors, analysis);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// 3. Version-specific checks
|
|
86
|
+
if (analysis.currentVersion !== analysis.targetVersion) {
|
|
87
|
+
analysis.needsMigration = true;
|
|
88
|
+
analysis.reasons.push(`Block is at v${analysis.currentVersion}, current is v${analysis.targetVersion}`);
|
|
89
|
+
|
|
90
|
+
// Compare schemas to find changes
|
|
91
|
+
compareVersionSchemas(analysis.currentVersion, analysis.targetVersion, analysis);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// 4. Data quality checks
|
|
95
|
+
performDataQualityChecks(attributes, analysis);
|
|
96
|
+
|
|
97
|
+
// 5. Add migration recommendations
|
|
98
|
+
if (analysis.needsMigration) {
|
|
99
|
+
addMigrationRecommendations(analysis);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
} catch (error) {
|
|
103
|
+
// Enhanced error handling with detailed logging
|
|
104
|
+
const migrationError: MigrationError = {
|
|
105
|
+
name: error.name || 'MigrationDetectionError',
|
|
106
|
+
message: error.message || 'Unknown error during migration detection',
|
|
107
|
+
stack: error.stack,
|
|
108
|
+
code: 'MIGRATION_DETECTION_FAILED',
|
|
109
|
+
details: {
|
|
110
|
+
blockName,
|
|
111
|
+
attributes: typeof attributes === 'object' ? Object.keys(attributes) : 'Invalid attributes',
|
|
112
|
+
timestamp: new Date().toISOString()
|
|
113
|
+
},
|
|
114
|
+
timestamp: new Date().toISOString(),
|
|
115
|
+
blockData: attributes
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
analysis.warnings.push(`Detection error: ${migrationError.message} (Code: ${migrationError.code})`);
|
|
119
|
+
analysis.confidence = 0;
|
|
120
|
+
|
|
121
|
+
// Log detailed error for debugging
|
|
122
|
+
console.error('Migration Detection Error Details:', JSON.stringify(migrationError, null, 2));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return analysis;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Detect version based on block structure
|
|
130
|
+
*/
|
|
131
|
+
function detectVersionByStructure(attributes: any): { version: string; confidence: number } {
|
|
132
|
+
const checks = [
|
|
133
|
+
// V3 indicators (highest priority)
|
|
134
|
+
{
|
|
135
|
+
version: "3.0.0",
|
|
136
|
+
indicators: ['id', 'version', 'theme'],
|
|
137
|
+
required: 2, // Need at least 2 indicators for high confidence
|
|
138
|
+
weight: 0.95
|
|
139
|
+
},
|
|
140
|
+
// V2 indicators (medium priority)
|
|
141
|
+
{
|
|
142
|
+
version: "2.0.0",
|
|
143
|
+
indicators: ['isVisible', 'className'],
|
|
144
|
+
required: 1,
|
|
145
|
+
weight: 0.85
|
|
146
|
+
},
|
|
147
|
+
// Mixed V2+V3 indicators
|
|
148
|
+
{
|
|
149
|
+
version: "2.5.0", // Intermediate version
|
|
150
|
+
indicators: ['isVisible', 'className', 'id', 'version'],
|
|
151
|
+
required: 3,
|
|
152
|
+
weight: 0.9
|
|
153
|
+
},
|
|
154
|
+
// V1 indicators (basic structure)
|
|
155
|
+
{
|
|
156
|
+
version: "1.0.0",
|
|
157
|
+
indicators: ['content', 'alignment'],
|
|
158
|
+
required: 1,
|
|
159
|
+
weight: 0.7
|
|
160
|
+
}
|
|
161
|
+
];
|
|
162
|
+
|
|
163
|
+
let bestMatch = { version: "1.0.0", confidence: 0.3 }; // Default fallback
|
|
164
|
+
|
|
165
|
+
// Enhanced detection with weighted scoring
|
|
166
|
+
for (const check of checks) {
|
|
167
|
+
const presentIndicators = check.indicators.filter(indicator =>
|
|
168
|
+
indicator in attributes && attributes[indicator] !== undefined
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
if (presentIndicators.length >= check.required) {
|
|
172
|
+
// Calculate confidence based on proportion of indicators present and weight
|
|
173
|
+
const proportion = presentIndicators.length / check.indicators.length;
|
|
174
|
+
const confidence = proportion * check.weight;
|
|
175
|
+
|
|
176
|
+
// Bonus for exact match
|
|
177
|
+
const bonus = presentIndicators.length === check.indicators.length ? 0.1 : 0;
|
|
178
|
+
|
|
179
|
+
const finalConfidence = Math.min(confidence + bonus, 1.0);
|
|
180
|
+
|
|
181
|
+
if (finalConfidence > bestMatch.confidence) {
|
|
182
|
+
bestMatch = {
|
|
183
|
+
version: check.version,
|
|
184
|
+
confidence: finalConfidence
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Additional validation for V3 blocks
|
|
191
|
+
if (attributes.version && typeof attributes.version === 'number') {
|
|
192
|
+
if (attributes.version === 3 && bestMatch.version !== "3.0.0") {
|
|
193
|
+
// Override if version attribute explicitly states V3
|
|
194
|
+
bestMatch = { version: "3.0.0", confidence: Math.min(bestMatch.confidence + 0.2, 1.0) };
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return bestMatch;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Analyze Typia validation errors to understand migration needs
|
|
203
|
+
*/
|
|
204
|
+
function analyzeValidationErrors(errors: any[], analysis: MigrationAnalysis) {
|
|
205
|
+
for (const error of errors) {
|
|
206
|
+
const path = error.path || 'root';
|
|
207
|
+
|
|
208
|
+
if (error.expected && error.actual) {
|
|
209
|
+
analysis.affectedFields.modified.push(path);
|
|
210
|
+
analysis.reasons.push(`Field '${path}' type mismatch: expected ${error.expected}, got ${error.actual}`);
|
|
211
|
+
} else if (error.expected) {
|
|
212
|
+
analysis.affectedFields.added.push(path);
|
|
213
|
+
analysis.reasons.push(`Missing required field: '${path}'`);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Compare schemas between versions
|
|
220
|
+
*/
|
|
221
|
+
function compareVersionSchemas(fromVersion: string, toVersion: string, analysis: MigrationAnalysis) {
|
|
222
|
+
// This would be auto-generated based on type analysis
|
|
223
|
+
const versionChanges = {
|
|
224
|
+
"1.0.0->2.0.0": {
|
|
225
|
+
added: ['isVisible', 'className'],
|
|
226
|
+
modified: ['content'], // Added length validation
|
|
227
|
+
deprecated: [],
|
|
228
|
+
changes: [
|
|
229
|
+
"Added visibility toggle (isVisible) for showing/hiding blocks",
|
|
230
|
+
"Added custom CSS class support (className) for styling",
|
|
231
|
+
"Enhanced content validation with length constraints"
|
|
232
|
+
]
|
|
233
|
+
},
|
|
234
|
+
"2.0.0->3.0.0": {
|
|
235
|
+
added: ['id', 'version', 'theme'],
|
|
236
|
+
modified: ['content', 'className'], // Changed validation rules
|
|
237
|
+
deprecated: [],
|
|
238
|
+
changes: [
|
|
239
|
+
"Added unique ID generation for block identification",
|
|
240
|
+
"Added version tracking for future migrations",
|
|
241
|
+
"Added theme support for different visual styles",
|
|
242
|
+
"Enhanced content validation with increased max length (2000 chars)",
|
|
243
|
+
"Enhanced className validation with pattern matching"
|
|
244
|
+
]
|
|
245
|
+
},
|
|
246
|
+
"1.0.0->3.0.0": {
|
|
247
|
+
added: ['id', 'version', 'theme', 'isVisible', 'className'],
|
|
248
|
+
modified: ['content', 'alignment'],
|
|
249
|
+
deprecated: [],
|
|
250
|
+
changes: [
|
|
251
|
+
"Added visibility toggle (isVisible) for showing/hiding blocks",
|
|
252
|
+
"Added custom CSS class support (className) for styling",
|
|
253
|
+
"Added unique ID generation for block identification",
|
|
254
|
+
"Added version tracking for future migrations",
|
|
255
|
+
"Added theme support for different visual styles",
|
|
256
|
+
"Enhanced content validation with length constraints and increased max length",
|
|
257
|
+
"Extended alignment options to include 'justify'"
|
|
258
|
+
]
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
const changeKey = `${fromVersion}->${toVersion}`;
|
|
263
|
+
const changes = versionChanges[changeKey];
|
|
264
|
+
|
|
265
|
+
if (changes) {
|
|
266
|
+
analysis.affectedFields.added.push(...changes.added);
|
|
267
|
+
analysis.affectedFields.modified.push(...changes.modified);
|
|
268
|
+
analysis.affectedFields.deprecated.push(...changes.deprecated);
|
|
269
|
+
|
|
270
|
+
// Add change descriptions to reasons
|
|
271
|
+
if (changes.changes) {
|
|
272
|
+
analysis.reasons.push(...changes.changes.map(change => `📝 ${change}`));
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Perform data quality checks
|
|
279
|
+
*/
|
|
280
|
+
function performDataQualityChecks(attributes: any, analysis: MigrationAnalysis) {
|
|
281
|
+
// Check for suspicious data patterns
|
|
282
|
+
if (attributes.content === null) {
|
|
283
|
+
analysis.warnings.push('Content is null, may cause issues after migration');
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if (typeof attributes.content === 'object') {
|
|
287
|
+
analysis.warnings.push('Content appears to be an object, expected string');
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
if (attributes.className && attributes.className.length > 100) {
|
|
291
|
+
analysis.warnings.push('ClassName exceeds recommended length, will be truncated');
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Check for legacy WordPress patterns
|
|
295
|
+
if (attributes.content && typeof attributes.content === 'string') {
|
|
296
|
+
if (attributes.content.includes('{{')) {
|
|
297
|
+
analysis.warnings.push('Content contains template syntax, may need manual review');
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Add migration recommendations
|
|
304
|
+
*/
|
|
305
|
+
function addMigrationRecommendations(analysis: MigrationAnalysis) {
|
|
306
|
+
if (analysis.currentVersion === "1.0.0") {
|
|
307
|
+
analysis.reasons.push('💡 Upgrade will add visibility toggle and custom CSS classes');
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (analysis.currentVersion !== "3.0.0") {
|
|
311
|
+
analysis.reasons.push('💡 Latest version includes unique IDs and theme support');
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (analysis.affectedFields.added.length > 0) {
|
|
315
|
+
analysis.reasons.push(`🔧 ${analysis.affectedFields.added.length} new field(s) will be added with default values`);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Scan WordPress database for blocks needing migration
|
|
321
|
+
*/
|
|
322
|
+
export async function scanSiteForMigrations(blockName: string = '{{namespace}}/{{slug}}'): Promise<BlockScanResult[]> {
|
|
323
|
+
console.log(`🔍 Scanning site for ${blockName} blocks needing migration...`);
|
|
324
|
+
|
|
325
|
+
const results: BlockScanResult[] = [];
|
|
326
|
+
|
|
327
|
+
try {
|
|
328
|
+
// This would integrate with WordPress REST API or direct DB access
|
|
329
|
+
const posts = await fetchPostsWithBlocks(blockName);
|
|
330
|
+
|
|
331
|
+
for (const post of posts) {
|
|
332
|
+
const blocks = extractBlocksFromPost(post, blockName);
|
|
333
|
+
|
|
334
|
+
for (const block of blocks) {
|
|
335
|
+
const analysis = detectBlockMigration(block.attributes, blockName);
|
|
336
|
+
|
|
337
|
+
if (analysis.needsMigration || analysis.warnings.length > 0) {
|
|
338
|
+
results.push({
|
|
339
|
+
blockName,
|
|
340
|
+
postId: post.id,
|
|
341
|
+
postTitle: post.title,
|
|
342
|
+
analysis,
|
|
343
|
+
attributes: block.attributes
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
console.log(`✅ Scan complete: ${results.length} blocks need attention`);
|
|
350
|
+
|
|
351
|
+
} catch (error) {
|
|
352
|
+
console.error('❌ Site scan failed:', error);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
return results;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Generate migration report for WordPress admin
|
|
360
|
+
*/
|
|
361
|
+
export function generateMigrationReport(scanResults: BlockScanResult[]): string {
|
|
362
|
+
const needsMigration = scanResults.filter(r => r.analysis.needsMigration);
|
|
363
|
+
const hasWarnings = scanResults.filter(r => r.analysis.warnings.length > 0);
|
|
364
|
+
|
|
365
|
+
let report = `# {{title}} Migration Report\n\n`;
|
|
366
|
+
report += `📊 **Summary:**\n`;
|
|
367
|
+
report += `- Total blocks scanned: ${scanResults.length}\n`;
|
|
368
|
+
report += `- Blocks needing migration: ${needsMigration.length}\n`;
|
|
369
|
+
report += `- Blocks with warnings: ${hasWarnings.length}\n\n`;
|
|
370
|
+
|
|
371
|
+
if (needsMigration.length > 0) {
|
|
372
|
+
report += `## 🔧 Blocks Requiring Migration\n\n`;
|
|
373
|
+
|
|
374
|
+
const byVersion = needsMigration.reduce((acc, result) => {
|
|
375
|
+
const version = result.analysis.currentVersion;
|
|
376
|
+
if (!acc[version]) acc[version] = [];
|
|
377
|
+
acc[version].push(result);
|
|
378
|
+
return acc;
|
|
379
|
+
}, {} as Record<string, BlockScanResult[]>);
|
|
380
|
+
|
|
381
|
+
for (const [version, blocks] of Object.entries(byVersion)) {
|
|
382
|
+
report += `### Version ${version} (${blocks.length} blocks)\n`;
|
|
383
|
+
for (const block of blocks.slice(0, 5)) { // Show first 5
|
|
384
|
+
report += `- Post: "${block.postTitle}" (ID: ${block.postId})\n`;
|
|
385
|
+
report += ` - Confidence: ${(block.analysis.confidence * 100).toFixed(0)}%\n`;
|
|
386
|
+
report += ` - Changes: ${block.analysis.affectedFields.added.join(', ')}\n`;
|
|
387
|
+
}
|
|
388
|
+
if (blocks.length > 5) {
|
|
389
|
+
report += ` - ... and ${blocks.length - 5} more\n`;
|
|
390
|
+
}
|
|
391
|
+
report += `\n`;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
if (hasWarnings.length > 0) {
|
|
396
|
+
report += `## ⚠️ Warnings\n\n`;
|
|
397
|
+
for (const result of hasWarnings.slice(0, 10)) {
|
|
398
|
+
report += `- "${result.postTitle}": ${result.analysis.warnings.join(', ')}\n`;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
report += `\n---\n`;
|
|
403
|
+
report += `Generated: ${new Date().toLocaleString()}\n`;
|
|
404
|
+
|
|
405
|
+
return report;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// Mock functions - would be implemented with WordPress integration
|
|
409
|
+
async function fetchPostsWithBlocks(blockName: string): Promise<any[]> {
|
|
410
|
+
// Implementation would use WordPress REST API or wp.data
|
|
411
|
+
return [];
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
function extractBlocksFromPost(post: any, blockName: string): any[] {
|
|
415
|
+
// Implementation would parse post_content for blocks
|
|
416
|
+
return [];
|
|
417
|
+
}
|