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,25 @@
|
|
|
1
|
+
import { syncBlockMetadata } from "./lib/typia-metadata-core";
|
|
2
|
+
|
|
3
|
+
async function main() {
|
|
4
|
+
const result = await syncBlockMetadata({
|
|
5
|
+
blockJsonFile: "src/block.json",
|
|
6
|
+
manifestFile: "src/typia.manifest.json",
|
|
7
|
+
sourceTypeName: "{{pascalCase}}Attributes",
|
|
8
|
+
typesFile: "src/types.ts",
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
console.log("✅ block.json and typia.manifest.json were generated from TypeScript types!");
|
|
12
|
+
console.log("📝 Generated attributes:", result.attributeNames);
|
|
13
|
+
|
|
14
|
+
if (result.lossyProjectionWarnings.length > 0) {
|
|
15
|
+
console.warn("⚠️ Some Typia constraints were preserved only in typia.manifest.json:");
|
|
16
|
+
for (const warning of result.lossyProjectionWarnings) {
|
|
17
|
+
console.warn(` - ${warning}`);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
main().catch((error) => {
|
|
23
|
+
console.error("❌ Type sync failed:", error);
|
|
24
|
+
process.exit(1);
|
|
25
|
+
});
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://schemas.wp.org/trunk/block.json",
|
|
3
|
+
"apiVersion": 3,
|
|
4
|
+
"name": "{{namespace}}/{{slug}}",
|
|
5
|
+
"version": "0.1.0",
|
|
6
|
+
"title": "{{title}}",
|
|
7
|
+
"category": "text",
|
|
8
|
+
"icon": "smiley",
|
|
9
|
+
"description": "{{description}}",
|
|
10
|
+
"keywords": ["{{keyword}}", "typia", "block"],
|
|
11
|
+
"example": {
|
|
12
|
+
"attributes": {
|
|
13
|
+
"content": "예제 콘텐츠",
|
|
14
|
+
"alignment": "center",
|
|
15
|
+
"isVisible": true
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"supports": {
|
|
19
|
+
"html": false
|
|
20
|
+
},
|
|
21
|
+
"textdomain": "{{textDomain}}",
|
|
22
|
+
"editorScript": "file:./index.js",
|
|
23
|
+
"editorStyle": "file:./index.css",
|
|
24
|
+
"style": "file:./style-index.css",
|
|
25
|
+
"attributes": {
|
|
26
|
+
"content": {
|
|
27
|
+
"type": "string",
|
|
28
|
+
"default": ""
|
|
29
|
+
},
|
|
30
|
+
"alignment": {
|
|
31
|
+
"type": "string",
|
|
32
|
+
"enum": ["left", "center", "right", "justify"],
|
|
33
|
+
"default": "left"
|
|
34
|
+
},
|
|
35
|
+
"className": {
|
|
36
|
+
"type": "string",
|
|
37
|
+
"default": ""
|
|
38
|
+
},
|
|
39
|
+
"isVisible": {
|
|
40
|
+
"type": "boolean",
|
|
41
|
+
"default": true
|
|
42
|
+
},
|
|
43
|
+
"id": {
|
|
44
|
+
"type": "string"
|
|
45
|
+
},
|
|
46
|
+
"version": {
|
|
47
|
+
"type": "number",
|
|
48
|
+
"default": 1
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Editor component for {{title}} Block
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { BlockEditProps } from '@wordpress/blocks';
|
|
6
|
+
import { useBlockProps, InspectorControls } from '@wordpress/block-editor';
|
|
7
|
+
import { PanelBody, TextControl, ToggleControl, SelectControl } from '@wordpress/components';
|
|
8
|
+
import { __ } from '@wordpress/i18n';
|
|
9
|
+
import { {{pascalCase}}Attributes } from './types';
|
|
10
|
+
import { validators, createAttributeUpdater } from './validators';
|
|
11
|
+
import { useValidation } from './hooks';
|
|
12
|
+
|
|
13
|
+
type EditProps = BlockEditProps<{{pascalCase}}Attributes>;
|
|
14
|
+
|
|
15
|
+
function Edit({ attributes, setAttributes }: EditProps) {
|
|
16
|
+
const blockProps = useBlockProps();
|
|
17
|
+
const { errors, isValid } = useValidation(attributes, validators.validate);
|
|
18
|
+
const updateAttribute = createAttributeUpdater(
|
|
19
|
+
attributes,
|
|
20
|
+
setAttributes,
|
|
21
|
+
validators.validate
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<>
|
|
26
|
+
<InspectorControls>
|
|
27
|
+
<PanelBody title={__('설정', '{{textDomain}}')}>
|
|
28
|
+
<TextControl
|
|
29
|
+
label={__('콘텐츠', '{{textDomain}}')}
|
|
30
|
+
value={attributes.content || ''}
|
|
31
|
+
onChange={(value) => updateAttribute('content', value)}
|
|
32
|
+
help={__('블록의 메인 콘텐츠입니다.', '{{textDomain}}')}
|
|
33
|
+
/>
|
|
34
|
+
|
|
35
|
+
<SelectControl
|
|
36
|
+
label={__('정렬', '{{textDomain}}')}
|
|
37
|
+
value={attributes.alignment || 'left'}
|
|
38
|
+
options={[
|
|
39
|
+
{ label: __('왼쪽', '{{textDomain}}'), value: 'left' },
|
|
40
|
+
{ label: __('가운데', '{{textDomain}}'), value: 'center' },
|
|
41
|
+
{ label: __('오른쪽', '{{textDomain}}'), value: 'right' },
|
|
42
|
+
{ label: __('양쪽 맞춤', '{{textDomain}}'), value: 'justify' },
|
|
43
|
+
]}
|
|
44
|
+
onChange={(value) => updateAttribute('alignment', value as any)}
|
|
45
|
+
/>
|
|
46
|
+
|
|
47
|
+
<TextControl
|
|
48
|
+
label={__('CSS 클래스', '{{textDomain}}')}
|
|
49
|
+
value={attributes.className || ''}
|
|
50
|
+
onChange={(value) => updateAttribute('className', value)}
|
|
51
|
+
help={__('추가 CSS 클래스를 입력하세요.', '{{textDomain}}')}
|
|
52
|
+
/>
|
|
53
|
+
|
|
54
|
+
<ToggleControl
|
|
55
|
+
label={__('표시하기', '{{textDomain}}')}
|
|
56
|
+
checked={attributes.isVisible !== false}
|
|
57
|
+
onChange={(value) => updateAttribute('isVisible', value)}
|
|
58
|
+
/>
|
|
59
|
+
</PanelBody>
|
|
60
|
+
|
|
61
|
+
{!isValid && (
|
|
62
|
+
<PanelBody title={__('유효성 검증 오류', '{{textDomain}}')} initialOpen>
|
|
63
|
+
{errors.map((error, index) => (
|
|
64
|
+
<div key={index} style={{ color: '#cc1818', fontSize: '12px' }}>
|
|
65
|
+
• {error}
|
|
66
|
+
</div>
|
|
67
|
+
))}
|
|
68
|
+
</PanelBody>
|
|
69
|
+
)}
|
|
70
|
+
</InspectorControls>
|
|
71
|
+
|
|
72
|
+
<div {...blockProps}>
|
|
73
|
+
<div className="wp-block-{{slug}}__content">
|
|
74
|
+
{attributes.content || (
|
|
75
|
+
<span style={{ color: '#757575' }}>
|
|
76
|
+
{__('콘텐츠를 입력하세요...', '{{textDomain}}')}
|
|
77
|
+
</span>
|
|
78
|
+
)}
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
</>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export default Edit;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { useEffect, useState, useCallback } from '@wordpress/element';
|
|
2
|
+
import { __ } from '@wordpress/i18n';
|
|
3
|
+
import { {{pascalCase}}Attributes } from './types';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 유효성 검증 훅
|
|
7
|
+
*/
|
|
8
|
+
export function useValidation<T>(
|
|
9
|
+
attributes: T,
|
|
10
|
+
validator: (value: T) => { success: boolean; errors?: any[] }
|
|
11
|
+
) {
|
|
12
|
+
const [errors, setErrors] = useState<string[]>([]);
|
|
13
|
+
const [isValid, setIsValid] = useState(true);
|
|
14
|
+
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
const result = validator(attributes);
|
|
17
|
+
if (result.success) {
|
|
18
|
+
setErrors([]);
|
|
19
|
+
setIsValid(true);
|
|
20
|
+
} else {
|
|
21
|
+
setErrors(result.errors?.map(e => e.path || 'Unknown error') || []);
|
|
22
|
+
setIsValid(false);
|
|
23
|
+
}
|
|
24
|
+
}, [attributes, validator]);
|
|
25
|
+
|
|
26
|
+
return { errors, isValid };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* 디바운스 훅
|
|
31
|
+
*/
|
|
32
|
+
export function useDebounce<T>(value: T, delay: number): T {
|
|
33
|
+
const [debouncedValue, setDebouncedValue] = useState<T>(value);
|
|
34
|
+
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
const handler = setTimeout(() => {
|
|
37
|
+
setDebouncedValue(value);
|
|
38
|
+
}, delay);
|
|
39
|
+
|
|
40
|
+
return () => {
|
|
41
|
+
clearTimeout(handler);
|
|
42
|
+
};
|
|
43
|
+
}, [value, delay]);
|
|
44
|
+
|
|
45
|
+
return debouncedValue;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* 로컬 스토리지 훅
|
|
50
|
+
*/
|
|
51
|
+
export function useLocalStorage<T>(
|
|
52
|
+
key: string,
|
|
53
|
+
initialValue: T
|
|
54
|
+
): [T, (value: T) => void] {
|
|
55
|
+
const [storedValue, setStoredValue] = useState<T>(() => {
|
|
56
|
+
try {
|
|
57
|
+
const item = window.localStorage.getItem(key);
|
|
58
|
+
return item ? JSON.parse(item) : initialValue;
|
|
59
|
+
} catch (error) {
|
|
60
|
+
console.error(`Error loading ${key} from localStorage:`, error);
|
|
61
|
+
return initialValue;
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const setValue = useCallback((value: T) => {
|
|
66
|
+
try {
|
|
67
|
+
setStoredValue(value);
|
|
68
|
+
window.localStorage.setItem(key, JSON.stringify(value));
|
|
69
|
+
} catch (error) {
|
|
70
|
+
console.error(`Error saving ${key} to localStorage:`, error);
|
|
71
|
+
}
|
|
72
|
+
}, [key]);
|
|
73
|
+
|
|
74
|
+
return [storedValue, setValue];
|
|
75
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WordPress {{title}} Block
|
|
3
|
+
*
|
|
4
|
+
* Typia 기반 타입 안전 블록
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { registerBlockType } from '@wordpress/blocks';
|
|
8
|
+
import { __ } from '@wordpress/i18n';
|
|
9
|
+
|
|
10
|
+
// Import components
|
|
11
|
+
import Edit from './edit';
|
|
12
|
+
import Save from './save';
|
|
13
|
+
import metadata from './block.json';
|
|
14
|
+
import './style.scss';
|
|
15
|
+
|
|
16
|
+
// Import types
|
|
17
|
+
import { {{pascalCase}}Attributes } from './types';
|
|
18
|
+
import { validators } from './validators';
|
|
19
|
+
|
|
20
|
+
// Register the block
|
|
21
|
+
registerBlockType<{{pascalCase}}Attributes>(metadata.name, {
|
|
22
|
+
title: metadata.title,
|
|
23
|
+
description: metadata.description,
|
|
24
|
+
category: metadata.category as any,
|
|
25
|
+
icon: metadata.icon,
|
|
26
|
+
supports: {
|
|
27
|
+
html: false,
|
|
28
|
+
multiple: true,
|
|
29
|
+
align: ['wide', 'full'],
|
|
30
|
+
},
|
|
31
|
+
attributes: metadata.attributes,
|
|
32
|
+
example: {
|
|
33
|
+
attributes: validators.random(),
|
|
34
|
+
},
|
|
35
|
+
edit: Edit,
|
|
36
|
+
save: Save,
|
|
37
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Save/Frontend component for {{title}} Block
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { useBlockProps } from '@wordpress/block-editor';
|
|
6
|
+
import { {{pascalCase}}Attributes } from './types';
|
|
7
|
+
|
|
8
|
+
interface SaveProps {
|
|
9
|
+
attributes: {{pascalCase}}Attributes;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export default function Save({ attributes }: SaveProps) {
|
|
13
|
+
const blockProps = useBlockProps.save();
|
|
14
|
+
|
|
15
|
+
// 블록이 숨겨져 있으면 아무것도 렌더링하지 않음
|
|
16
|
+
if (attributes.isVisible === false) {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<div {...blockProps}>
|
|
22
|
+
<div className="wp-block-{{slug}}__content">
|
|
23
|
+
{attributes.content}
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* {{title}} Block Styles
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
.wp-block-{{namespace}}-{{slug}} {
|
|
6
|
+
padding: 20px;
|
|
7
|
+
border: 1px solid #ddd;
|
|
8
|
+
border-radius: 4px;
|
|
9
|
+
background-color: #fff;
|
|
10
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
|
|
11
|
+
|
|
12
|
+
&__content {
|
|
13
|
+
font-size: 16px;
|
|
14
|
+
line-height: 1.6;
|
|
15
|
+
color: #333;
|
|
16
|
+
|
|
17
|
+
// 정렬 스타일
|
|
18
|
+
&[data-align="center"] {
|
|
19
|
+
text-align: center;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
&[data-align="right"] {
|
|
23
|
+
text-align: right;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
&[data-align="justify"] {
|
|
27
|
+
text-align: justify;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// 호버 효과
|
|
32
|
+
&:hover {
|
|
33
|
+
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
|
|
34
|
+
transition: box-shadow 0.2s ease;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// 에디터에서의 스타일
|
|
38
|
+
.wp-block-editor & {
|
|
39
|
+
outline: 1px dashed #ddd;
|
|
40
|
+
outline-offset: -1px;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { tags } from "typia";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 블록 속성 인터페이스
|
|
5
|
+
* Typia 태그를 사용하여 유효성 검증 규칙 정의
|
|
6
|
+
*/
|
|
7
|
+
export interface {{pascalCase}}Attributes {
|
|
8
|
+
/**
|
|
9
|
+
* 메인 콘텐츠
|
|
10
|
+
*/
|
|
11
|
+
content: string &
|
|
12
|
+
tags.MinLength<1> &
|
|
13
|
+
tags.MaxLength<1000> &
|
|
14
|
+
tags.Default<"">;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* 정렬 방식
|
|
18
|
+
*/
|
|
19
|
+
alignment?: ('left' | 'center' | 'right' | 'justify') &
|
|
20
|
+
tags.Default<"left">;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 표시 여부
|
|
24
|
+
*/
|
|
25
|
+
isVisible?: boolean &
|
|
26
|
+
tags.Default<true>;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 커스텀 CSS 클래스
|
|
30
|
+
*/
|
|
31
|
+
className?: string &
|
|
32
|
+
tags.MaxLength<100> &
|
|
33
|
+
tags.Default<"">;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* 고유 ID (자동 생성)
|
|
37
|
+
*/
|
|
38
|
+
id?: string &
|
|
39
|
+
tags.Format<"uuid">;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* 블록 버전 (마이그레이션용)
|
|
43
|
+
*/
|
|
44
|
+
version?: number &
|
|
45
|
+
tags.Type<"uint32"> &
|
|
46
|
+
tags.Default<1>;
|
|
47
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import typia from "typia";
|
|
2
|
+
import { {{pascalCase}}Attributes } from "./types";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Typia 유효성 검증기
|
|
6
|
+
*/
|
|
7
|
+
export const validators = {
|
|
8
|
+
validate: typia.createValidate<{{pascalCase}}Attributes>(),
|
|
9
|
+
assert: typia.createAssert<{{pascalCase}}Attributes>(),
|
|
10
|
+
is: typia.createIs<{{pascalCase}}Attributes>(),
|
|
11
|
+
random: typia.createRandom<{{pascalCase}}Attributes>(),
|
|
12
|
+
clone: typia.misc.createClone<{{pascalCase}}Attributes>(),
|
|
13
|
+
prune: typia.misc.createPrune<{{pascalCase}}Attributes>(),
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* 속성 업데이터 생성
|
|
18
|
+
*/
|
|
19
|
+
export function createAttributeUpdater(
|
|
20
|
+
attributes: {{pascalCase}}Attributes,
|
|
21
|
+
setAttributes: (attrs: Partial<{{pascalCase}}Attributes>) => void,
|
|
22
|
+
validator = validators.validate
|
|
23
|
+
) {
|
|
24
|
+
return <K extends keyof {{pascalCase}}Attributes>(
|
|
25
|
+
key: K,
|
|
26
|
+
value: {{pascalCase}}Attributes[K]
|
|
27
|
+
) => {
|
|
28
|
+
const newAttrs = { ...attributes, [key]: value };
|
|
29
|
+
|
|
30
|
+
const validation = validator(newAttrs);
|
|
31
|
+
if (validation.success) {
|
|
32
|
+
setAttributes({ [key]: value } as Partial<{{pascalCase}}Attributes>);
|
|
33
|
+
return true;
|
|
34
|
+
} else {
|
|
35
|
+
console.error(`Validation failed for ${String(key)}:`, validation.errors);
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
|
6
|
+
"jsx": "react-jsx",
|
|
7
|
+
"strict": true,
|
|
8
|
+
"esModuleInterop": true,
|
|
9
|
+
"allowSyntheticDefaultImports": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"forceConsistentCasingInFileNames": true,
|
|
12
|
+
"moduleResolution": "node",
|
|
13
|
+
"resolveJsonModule": true,
|
|
14
|
+
"isolatedModules": true,
|
|
15
|
+
"noEmit": true,
|
|
16
|
+
"baseUrl": "."
|
|
17
|
+
},
|
|
18
|
+
"include": ["src/**/*"],
|
|
19
|
+
"exclude": ["node_modules", "build"]
|
|
20
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const defaultConfig = require("@wordpress/scripts/config/webpack.config");
|
|
4
|
+
|
|
5
|
+
class TypiaManifestAssetPlugin {
|
|
6
|
+
apply(compiler) {
|
|
7
|
+
compiler.hooks.thisCompilation.tap("TypiaManifestAssetPlugin", (compilation) => {
|
|
8
|
+
compilation.hooks.processAssets.tap(
|
|
9
|
+
{
|
|
10
|
+
name: "TypiaManifestAssetPlugin",
|
|
11
|
+
stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONS,
|
|
12
|
+
},
|
|
13
|
+
() => {
|
|
14
|
+
for (const entry of getManifestEntries()) {
|
|
15
|
+
if (compilation.getAsset(entry.outputPath)) {
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
compilation.emitAsset(
|
|
20
|
+
entry.outputPath,
|
|
21
|
+
new compiler.webpack.sources.RawSource(fs.readFileSync(entry.inputPath)),
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
);
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function getManifestEntries() {
|
|
31
|
+
const entries = [];
|
|
32
|
+
const rootManifestPath = path.resolve(process.cwd(), "typia.manifest.json");
|
|
33
|
+
|
|
34
|
+
if (fs.existsSync(rootManifestPath)) {
|
|
35
|
+
entries.push({
|
|
36
|
+
inputPath: rootManifestPath,
|
|
37
|
+
outputPath: "typia.manifest.json",
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const srcDir = path.resolve(process.cwd(), "src");
|
|
42
|
+
if (!fs.existsSync(srcDir)) {
|
|
43
|
+
return entries;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
for (const inputPath of findManifestFiles(srcDir)) {
|
|
47
|
+
entries.push({
|
|
48
|
+
inputPath,
|
|
49
|
+
outputPath: path.relative(srcDir, inputPath),
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return entries;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function findManifestFiles(directory) {
|
|
57
|
+
const manifestFiles = [];
|
|
58
|
+
|
|
59
|
+
for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
|
|
60
|
+
const entryPath = path.join(directory, entry.name);
|
|
61
|
+
|
|
62
|
+
if (entry.isDirectory()) {
|
|
63
|
+
manifestFiles.push(...findManifestFiles(entryPath));
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
if (entry.isFile() && entry.name === "typia.manifest.json") {
|
|
67
|
+
manifestFiles.push(entryPath);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return manifestFiles;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
module.exports = async () => {
|
|
75
|
+
const { default: UnpluginTypia } = await import("@typia/unplugin/webpack");
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
...defaultConfig,
|
|
79
|
+
plugins: [
|
|
80
|
+
UnpluginTypia(),
|
|
81
|
+
...(defaultConfig.plugins || []),
|
|
82
|
+
new TypiaManifestAssetPlugin(),
|
|
83
|
+
],
|
|
84
|
+
};
|
|
85
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{slugSnakeCase}}",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"packageManager": "bun@1.3.10",
|
|
5
|
+
"description": "{{description}}",
|
|
6
|
+
"author": "{{author}}",
|
|
7
|
+
"license": "GPL-2.0-or-later",
|
|
8
|
+
"main": "build/index.js",
|
|
9
|
+
"scripts": {
|
|
10
|
+
"sync-types": "tsx scripts/sync-types-to-block-json.ts",
|
|
11
|
+
"prebuild": "bun run sync-types",
|
|
12
|
+
"build": "wp-scripts build",
|
|
13
|
+
"start": "bun run sync-types && wp-scripts start",
|
|
14
|
+
"dev": "bun run start",
|
|
15
|
+
"lint:js": "wp-scripts lint-js",
|
|
16
|
+
"lint:css": "wp-scripts lint-style",
|
|
17
|
+
"lint": "bun run lint:js && bun run lint:css",
|
|
18
|
+
"format": "wp-scripts format",
|
|
19
|
+
"typecheck": "tsc --noEmit"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"ajv": "^8.18.0",
|
|
23
|
+
"@types/wordpress__block-editor": "^11.5.17",
|
|
24
|
+
"@types/wordpress__blocks": "^12.5.18",
|
|
25
|
+
"@types/wordpress__components": "^23.8.0",
|
|
26
|
+
"@wordpress/browserslist-config": "^6.42.0",
|
|
27
|
+
"@wordpress/scripts": "^30.22.0",
|
|
28
|
+
"@typia/unplugin": "^12.0.1",
|
|
29
|
+
"tsx": "^4.20.5",
|
|
30
|
+
"typescript": "^5.9.2",
|
|
31
|
+
"typia": "^12.0.1"
|
|
32
|
+
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@wordpress/block-editor": "^15.2.0",
|
|
35
|
+
"@wordpress/blocks": "^15.2.0",
|
|
36
|
+
"@wordpress/components": "^30.2.0",
|
|
37
|
+
"@wordpress/element": "^6.29.0",
|
|
38
|
+
"@wordpress/i18n": "^6.2.0"
|
|
39
|
+
}
|
|
40
|
+
}
|