@wp-typia/project-tools 0.15.0 → 0.15.2
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/dist/runtime/cli-add.js +26 -70
- package/dist/runtime/cli-doctor.js +25 -9
- package/dist/runtime/cli-help.js +1 -0
- package/dist/runtime/cli-templates.js +10 -0
- package/dist/runtime/json-utils.d.ts +5 -8
- package/dist/runtime/json-utils.js +5 -10
- package/dist/runtime/metadata-analysis.d.ts +7 -11
- package/dist/runtime/metadata-analysis.js +7 -285
- package/dist/runtime/metadata-model.d.ts +7 -84
- package/dist/runtime/metadata-model.js +7 -59
- package/dist/runtime/metadata-parser.d.ts +5 -51
- package/dist/runtime/metadata-parser.js +5 -792
- package/dist/runtime/metadata-php-render.d.ts +5 -27
- package/dist/runtime/metadata-php-render.js +5 -547
- package/dist/runtime/metadata-projection.d.ts +7 -7
- package/dist/runtime/metadata-projection.js +7 -233
- package/dist/runtime/object-utils.d.ts +1 -1
- package/dist/runtime/object-utils.js +3 -6
- package/dist/runtime/persistence-rest-artifacts.d.ts +76 -0
- package/dist/runtime/persistence-rest-artifacts.js +99 -0
- package/dist/runtime/scaffold.d.ts +10 -2
- package/dist/runtime/scaffold.js +95 -1
- package/dist/runtime/template-builtins.js +1 -1
- package/dist/runtime/template-registry.d.ts +2 -1
- package/dist/runtime/template-registry.js +13 -2
- package/package.json +9 -8
- package/templates/_shared/compound/core/scripts/add-compound-child.ts.mustache +103 -7
- package/templates/_shared/persistence/core/src/api-validators.ts.mustache +14 -0
- package/templates/_shared/persistence/core/src/api.ts.mustache +28 -9
- package/templates/_shared/persistence/core/src/interactivity.ts.mustache +17 -11
- package/templates/interactivity/src/block.json.mustache +1 -0
- package/templates/interactivity/src/editor.scss.mustache +8 -0
- package/templates/interactivity/src/index.tsx.mustache +1 -0
- package/templates/persistence/src/edit.tsx.mustache +6 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wp-typia/project-tools",
|
|
3
|
-
"version": "0.15.
|
|
3
|
+
"version": "0.15.2",
|
|
4
4
|
"description": "Project orchestration and programmatic tooling for wp-typia",
|
|
5
5
|
"packageManager": "bun@1.3.11",
|
|
6
6
|
"type": "module",
|
|
@@ -26,9 +26,9 @@
|
|
|
26
26
|
"package.json"
|
|
27
27
|
],
|
|
28
28
|
"scripts": {
|
|
29
|
-
"build": "bun run --filter @wp-typia/block-runtime build && rm -rf dist && tsc -p tsconfig.runtime.json",
|
|
30
|
-
"test": "bun run
|
|
31
|
-
"test:coverage": "bun run
|
|
29
|
+
"build": "bun run --filter @wp-typia/api-client build && bun run --filter @wp-typia/block-runtime build && rm -rf dist && tsc -p tsconfig.runtime.json",
|
|
30
|
+
"test": "bun run build && bun test tests/*.test.ts",
|
|
31
|
+
"test:coverage": "bun run build && bun test tests/*.test.ts --coverage --coverage-reporter=lcov --coverage-dir=coverage",
|
|
32
32
|
"clean": "rm -rf dist",
|
|
33
33
|
"prepack": "bun run build"
|
|
34
34
|
},
|
|
@@ -61,14 +61,15 @@
|
|
|
61
61
|
"bun": ">=1.3.11"
|
|
62
62
|
},
|
|
63
63
|
"dependencies": {
|
|
64
|
-
"@wp-typia/api-client": "^0.4.
|
|
65
|
-
"@wp-typia/block-runtime": "^0.4.
|
|
66
|
-
"@wp-typia/rest": "^0.3.
|
|
67
|
-
"@wp-typia/block-types": "^0.2.
|
|
64
|
+
"@wp-typia/api-client": "^0.4.2",
|
|
65
|
+
"@wp-typia/block-runtime": "^0.4.2",
|
|
66
|
+
"@wp-typia/rest": "^0.3.5",
|
|
67
|
+
"@wp-typia/block-types": "^0.2.1",
|
|
68
68
|
"mustache": "^4.2.0",
|
|
69
69
|
"npm-package-arg": "^13.0.0",
|
|
70
70
|
"semver": "^7.7.3",
|
|
71
71
|
"tar": "^7.4.3",
|
|
72
|
+
"typia": "^12.0.1",
|
|
72
73
|
"typescript": "^5.9.2"
|
|
73
74
|
},
|
|
74
75
|
"devDependencies": {
|
|
@@ -2,14 +2,7 @@
|
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
|
|
5
|
-
const PARENT_BLOCK_NAME = '{{namespace}}/{{slugKebabCase}}';
|
|
6
|
-
const PARENT_BLOCK_NAMESPACE = '{{namespace}}';
|
|
7
|
-
const PARENT_BLOCK_SLUG = '{{slugKebabCase}}';
|
|
8
|
-
const PARENT_BLOCK_TITLE = {{titleJson}};
|
|
9
|
-
const PARENT_TYPE_NAME = '{{pascalCase}}';
|
|
10
|
-
const PARENT_STYLE_IMPORT = '../{{slugKebabCase}}/style.scss';
|
|
11
5
|
const PROJECT_ROOT = process.cwd();
|
|
12
|
-
const TEXT_DOMAIN = '{{textDomain}}';
|
|
13
6
|
|
|
14
7
|
const ALLOWED_CHILD_MARKER = '// add-child: insert new allowed child block names here';
|
|
15
8
|
const BLOCK_CONFIG_MARKER = '// add-child: insert new block config entries here';
|
|
@@ -22,6 +15,16 @@ type StarterManifestDocument = {
|
|
|
22
15
|
sourceType: string;
|
|
23
16
|
};
|
|
24
17
|
|
|
18
|
+
type CompoundParentConfig = {
|
|
19
|
+
blockName: string;
|
|
20
|
+
namespace: string;
|
|
21
|
+
slug: string;
|
|
22
|
+
styleImport: string;
|
|
23
|
+
textDomain: string;
|
|
24
|
+
title: string;
|
|
25
|
+
typeName: string;
|
|
26
|
+
};
|
|
27
|
+
|
|
25
28
|
function parseArgs() {
|
|
26
29
|
const args = process.argv.slice( 2 );
|
|
27
30
|
const parsed: {
|
|
@@ -81,6 +84,26 @@ function toTitleCase( input: string ): string {
|
|
|
81
84
|
.join( ' ' );
|
|
82
85
|
}
|
|
83
86
|
|
|
87
|
+
function readJsonFile( filePath: string ): Record< string, unknown > {
|
|
88
|
+
let parsed: unknown;
|
|
89
|
+
|
|
90
|
+
try {
|
|
91
|
+
parsed = JSON.parse( fs.readFileSync( filePath, 'utf8' ) );
|
|
92
|
+
} catch ( error ) {
|
|
93
|
+
const errorMessage = error instanceof Error ? error.message : String( error );
|
|
94
|
+
throw new Error(
|
|
95
|
+
`Unable to parse JSON from ${ filePath }: ${ errorMessage }`,
|
|
96
|
+
{ cause: error instanceof Error ? error : undefined }
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if ( ! parsed || typeof parsed !== 'object' || Array.isArray( parsed ) ) {
|
|
101
|
+
throw new Error( `${ filePath } must contain a JSON object.` );
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return parsed as Record< string, unknown >;
|
|
105
|
+
}
|
|
106
|
+
|
|
84
107
|
function resolveValidatedNamespace( value: string ): string {
|
|
85
108
|
const normalizedNamespace = toKebabCase( value );
|
|
86
109
|
|
|
@@ -101,6 +124,79 @@ function resolveValidatedBlockSlug( value: string ): string {
|
|
|
101
124
|
return normalizedSlug;
|
|
102
125
|
}
|
|
103
126
|
|
|
127
|
+
function resolveCompoundParentConfig(): CompoundParentConfig {
|
|
128
|
+
const blocksRoot = path.join( PROJECT_ROOT, 'src', 'blocks' );
|
|
129
|
+
|
|
130
|
+
if ( ! fs.existsSync( blocksRoot ) ) {
|
|
131
|
+
throw new Error(
|
|
132
|
+
'This command expects a compound scaffold with src/blocks/<parent>/children.ts and scripts/block-config.ts.'
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const parentCandidates = fs
|
|
137
|
+
.readdirSync( blocksRoot, { withFileTypes: true } )
|
|
138
|
+
.filter( ( entry ) => entry.isDirectory() )
|
|
139
|
+
.map( ( entry ) => path.join( blocksRoot, entry.name ) )
|
|
140
|
+
.filter( ( candidateDir ) =>
|
|
141
|
+
fs.existsSync( path.join( candidateDir, 'children.ts' ) )
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
if ( parentCandidates.length !== 1 ) {
|
|
145
|
+
throw new Error(
|
|
146
|
+
`Unable to resolve the compound parent block. Expected exactly one src/blocks/<parent>/children.ts entry, found ${ parentCandidates.length }.`
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const parentDir = parentCandidates[ 0 ];
|
|
151
|
+
const parentSlug = resolveValidatedBlockSlug( path.basename( parentDir ) );
|
|
152
|
+
const blockJsonPath = path.join( parentDir, 'block.json' );
|
|
153
|
+
|
|
154
|
+
if ( ! fs.existsSync( blockJsonPath ) ) {
|
|
155
|
+
throw new Error( `Unable to resolve ${ blockJsonPath } for the compound parent block.` );
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const blockJson = readJsonFile( blockJsonPath );
|
|
159
|
+
const blockName = typeof blockJson.name === 'string' ? blockJson.name.trim() : '';
|
|
160
|
+
const separatorIndex = blockName.indexOf( '/' );
|
|
161
|
+
|
|
162
|
+
if ( separatorIndex <= 0 || separatorIndex === blockName.length - 1 ) {
|
|
163
|
+
throw new Error(
|
|
164
|
+
`The parent block metadata at ${ blockJsonPath } must declare a valid "name" like "namespace/slug".`
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const namespace = resolveValidatedNamespace( blockName.slice( 0, separatorIndex ) );
|
|
169
|
+
const title =
|
|
170
|
+
typeof blockJson.title === 'string' && blockJson.title.trim().length > 0
|
|
171
|
+
? blockJson.title.trim()
|
|
172
|
+
: toTitleCase( parentSlug );
|
|
173
|
+
const textDomain =
|
|
174
|
+
typeof blockJson.textdomain === 'string' &&
|
|
175
|
+
blockJson.textdomain.trim().length > 0
|
|
176
|
+
? blockJson.textdomain.trim()
|
|
177
|
+
: parentSlug;
|
|
178
|
+
|
|
179
|
+
return {
|
|
180
|
+
blockName,
|
|
181
|
+
namespace,
|
|
182
|
+
slug: parentSlug,
|
|
183
|
+
styleImport: `../${ parentSlug }/style.scss`,
|
|
184
|
+
textDomain,
|
|
185
|
+
title,
|
|
186
|
+
typeName: toPascalCase( parentSlug ),
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const {
|
|
191
|
+
blockName: PARENT_BLOCK_NAME,
|
|
192
|
+
namespace: PARENT_BLOCK_NAMESPACE,
|
|
193
|
+
slug: PARENT_BLOCK_SLUG,
|
|
194
|
+
styleImport: PARENT_STYLE_IMPORT,
|
|
195
|
+
textDomain: TEXT_DOMAIN,
|
|
196
|
+
title: PARENT_BLOCK_TITLE,
|
|
197
|
+
typeName: PARENT_TYPE_NAME,
|
|
198
|
+
} = resolveCompoundParentConfig();
|
|
199
|
+
|
|
104
200
|
function buildBlockCssClassName( namespace: string, slug: string ): string {
|
|
105
201
|
const normalizedSlug = resolveValidatedBlockSlug( slug );
|
|
106
202
|
const normalizedNamespace =
|
|
@@ -5,11 +5,17 @@ import {
|
|
|
5
5
|
type ValidationResult,
|
|
6
6
|
} from '@wp-typia/api-client';
|
|
7
7
|
import type {
|
|
8
|
+
{{pascalCase}}BootstrapQuery,
|
|
9
|
+
{{pascalCase}}BootstrapResponse,
|
|
8
10
|
{{pascalCase}}StateQuery,
|
|
9
11
|
{{pascalCase}}StateResponse,
|
|
10
12
|
{{pascalCase}}WriteStateRequest,
|
|
11
13
|
} from './api-types';
|
|
12
14
|
|
|
15
|
+
const validateBootstrapQuery =
|
|
16
|
+
typia.createValidate< {{pascalCase}}BootstrapQuery >();
|
|
17
|
+
const validateBootstrapResponse =
|
|
18
|
+
typia.createValidate< {{pascalCase}}BootstrapResponse >();
|
|
13
19
|
const validateStateQuery = typia.createValidate< {{pascalCase}}StateQuery >();
|
|
14
20
|
const validateWriteStateRequest =
|
|
15
21
|
typia.createValidate< {{pascalCase}}WriteStateRequest >();
|
|
@@ -17,6 +23,14 @@ const validateStateResponse =
|
|
|
17
23
|
typia.createValidate< {{pascalCase}}StateResponse >();
|
|
18
24
|
|
|
19
25
|
export const apiValidators = {
|
|
26
|
+
bootstrapQuery: (
|
|
27
|
+
input: unknown
|
|
28
|
+
): ValidationResult< {{pascalCase}}BootstrapQuery > =>
|
|
29
|
+
toValidationResult( validateBootstrapQuery( input ) ),
|
|
30
|
+
bootstrapResponse: (
|
|
31
|
+
input: unknown
|
|
32
|
+
): ValidationResult< {{pascalCase}}BootstrapResponse > =>
|
|
33
|
+
toValidationResult( validateBootstrapResponse( input ) ),
|
|
20
34
|
stateQuery: (
|
|
21
35
|
input: unknown
|
|
22
36
|
): ValidationResult< {{pascalCase}}StateQuery > =>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
callEndpoint,
|
|
3
|
+
type ApiEndpoint as RestApiEndpoint,
|
|
3
4
|
} from '@wp-typia/rest';
|
|
4
5
|
|
|
5
6
|
import {
|
|
@@ -17,17 +18,35 @@ import {
|
|
|
17
18
|
type PersistenceTransportOptions,
|
|
18
19
|
} from './transport';
|
|
19
20
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
function createRestEndpoint< Req, Res >(
|
|
22
|
+
endpoint: {
|
|
23
|
+
method: RestApiEndpoint< Req, Res >[ 'method' ];
|
|
24
|
+
path: string;
|
|
25
|
+
validateRequest: RestApiEndpoint< Req, Res >[ 'validateRequest' ];
|
|
26
|
+
validateResponse: RestApiEndpoint< Req, Res >[ 'validateResponse' ];
|
|
27
|
+
}
|
|
28
|
+
): RestApiEndpoint< Req, Res > {
|
|
29
|
+
// Strip generator-only helper fields so the runtime client only sees the
|
|
30
|
+
// canonical RestApiEndpoint surface it expects.
|
|
31
|
+
return {
|
|
32
|
+
method: endpoint.method,
|
|
33
|
+
path: endpoint.path,
|
|
34
|
+
validateRequest: endpoint.validateRequest,
|
|
35
|
+
validateResponse: endpoint.validateResponse,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export const bootstrapEndpoint = createRestEndpoint(
|
|
40
|
+
get{{pascalCase}}BootstrapEndpoint
|
|
41
|
+
);
|
|
23
42
|
|
|
24
|
-
export const stateEndpoint =
|
|
25
|
-
|
|
26
|
-
|
|
43
|
+
export const stateEndpoint = createRestEndpoint(
|
|
44
|
+
get{{pascalCase}}StateEndpoint
|
|
45
|
+
);
|
|
27
46
|
|
|
28
|
-
export const writeStateEndpoint =
|
|
29
|
-
|
|
30
|
-
|
|
47
|
+
export const writeStateEndpoint = createRestEndpoint(
|
|
48
|
+
write{{pascalCase}}StateEndpoint
|
|
49
|
+
);
|
|
31
50
|
|
|
32
51
|
export function fetchState(
|
|
33
52
|
request: {{pascalCase}}StateQuery,
|
|
@@ -7,6 +7,9 @@ import type {
|
|
|
7
7
|
{{pascalCase}}Context,
|
|
8
8
|
{{pascalCase}}State,
|
|
9
9
|
} from './types';
|
|
10
|
+
import type {
|
|
11
|
+
{{pascalCase}}WriteStateRequest,
|
|
12
|
+
} from './api-types';
|
|
10
13
|
|
|
11
14
|
function hasExpiredPublicWriteToken(
|
|
12
15
|
expiresAt?: number
|
|
@@ -123,6 +126,7 @@ const { actions, state } = store( '{{slugKebabCase}}', {
|
|
|
123
126
|
let bootstrapSucceeded = false;
|
|
124
127
|
let lastBootstrapError =
|
|
125
128
|
'Unable to initialize write access';
|
|
129
|
+
const includeRestNonce = {{isAuthenticatedPersistencePolicy}};
|
|
126
130
|
|
|
127
131
|
for ( let attempt = 1; attempt <= BOOTSTRAP_MAX_ATTEMPTS; attempt += 1 ) {
|
|
128
132
|
try {
|
|
@@ -156,6 +160,8 @@ const { actions, state } = store( '{{slugKebabCase}}', {
|
|
|
156
160
|
? result.data.publicWriteToken
|
|
157
161
|
: '';
|
|
158
162
|
clientState.writeNonce =
|
|
163
|
+
includeRestNonce &&
|
|
164
|
+
'restNonce' in result.data &&
|
|
159
165
|
typeof result.data.restNonce === 'string' &&
|
|
160
166
|
result.data.restNonce.length > 0
|
|
161
167
|
? result.data.restNonce
|
|
@@ -231,20 +237,20 @@ const { actions, state } = store( '{{slugKebabCase}}', {
|
|
|
231
237
|
context.error = '';
|
|
232
238
|
|
|
233
239
|
try {
|
|
234
|
-
const
|
|
240
|
+
const request = {
|
|
235
241
|
delta: 1,
|
|
236
242
|
postId: context.postId,
|
|
237
|
-
publicWriteRequestId:
|
|
238
|
-
context.persistencePolicy === 'public'
|
|
239
|
-
? generatePublicWriteRequestId()
|
|
240
|
-
: undefined,
|
|
241
|
-
publicWriteToken:
|
|
242
|
-
context.persistencePolicy === 'public' &&
|
|
243
|
-
clientState.writeToken.length > 0
|
|
244
|
-
? clientState.writeToken
|
|
245
|
-
: undefined,
|
|
246
243
|
resourceKey: context.resourceKey,
|
|
247
|
-
}
|
|
244
|
+
} as {{pascalCase}}WriteStateRequest;
|
|
245
|
+
if ( {{isPublicPersistencePolicy}} ) {
|
|
246
|
+
request.publicWriteRequestId =
|
|
247
|
+
generatePublicWriteRequestId() as {{pascalCase}}WriteStateRequest[ 'publicWriteRequestId' ];
|
|
248
|
+
if ( clientState.writeToken.length > 0 ) {
|
|
249
|
+
request.publicWriteToken =
|
|
250
|
+
clientState.writeToken as {{pascalCase}}WriteStateRequest[ 'publicWriteToken' ];
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
const result = await writeState( request, {
|
|
248
254
|
restNonce:
|
|
249
255
|
clientState.writeNonce.length > 0
|
|
250
256
|
? clientState.writeNonce
|
|
@@ -66,15 +66,15 @@ export default function Edit( {
|
|
|
66
66
|
validateEditorUpdate
|
|
67
67
|
);
|
|
68
68
|
const alignmentValue = editorFields.getStringValue(
|
|
69
|
-
attributes,
|
|
69
|
+
attributes as unknown as Record< string, unknown >,
|
|
70
70
|
'alignment',
|
|
71
71
|
'left'
|
|
72
72
|
);
|
|
73
73
|
const persistencePolicy = '{{persistencePolicy}}';
|
|
74
|
-
const persistencePolicyDescription =
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
74
|
+
const persistencePolicyDescription = __(
|
|
75
|
+
{{persistencePolicyDescriptionJson}},
|
|
76
|
+
'{{textDomain}}'
|
|
77
|
+
);
|
|
78
78
|
|
|
79
79
|
return (
|
|
80
80
|
<>
|
|
@@ -91,7 +91,7 @@ export default function Edit( {
|
|
|
91
91
|
</BlockControls>
|
|
92
92
|
<InspectorControls>
|
|
93
93
|
<InspectorFromManifest
|
|
94
|
-
attributes={ attributes }
|
|
94
|
+
attributes={ attributes as unknown as Record< string, unknown > }
|
|
95
95
|
fieldLookup={ editorFields }
|
|
96
96
|
onChange={ updateField }
|
|
97
97
|
paths={ [ 'alignment', 'isVisible', 'showCount', 'buttonLabel' ] }
|