@wp-typia/project-tools 0.19.0 → 0.19.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.
Files changed (39) hide show
  1. package/dist/runtime/block-generator-service-spec.js +206 -2
  2. package/dist/runtime/built-in-block-artifacts.js +3 -1
  3. package/dist/runtime/built-in-block-code-artifacts.js +3 -1
  4. package/dist/runtime/built-in-block-non-ts-artifacts.js +1 -1039
  5. package/dist/runtime/built-in-block-non-ts-family-artifacts.d.ts +30 -0
  6. package/dist/runtime/built-in-block-non-ts-family-artifacts.js +1035 -0
  7. package/dist/runtime/built-in-block-non-ts-render-utils.d.ts +27 -0
  8. package/dist/runtime/built-in-block-non-ts-render-utils.js +51 -0
  9. package/dist/runtime/cli-add-block.js +11 -5
  10. package/dist/runtime/cli-add-workspace-rest-anchors.d.ts +3 -0
  11. package/dist/runtime/cli-add-workspace-rest-anchors.js +188 -0
  12. package/dist/runtime/cli-add-workspace-rest-source-emitters.d.ts +7 -0
  13. package/dist/runtime/cli-add-workspace-rest-source-emitters.js +379 -0
  14. package/dist/runtime/cli-add-workspace-rest.js +5 -564
  15. package/dist/runtime/cli-diagnostics.d.ts +26 -0
  16. package/dist/runtime/cli-diagnostics.js +107 -0
  17. package/dist/runtime/cli-doctor-workspace.js +4 -4
  18. package/dist/runtime/cli-scaffold.js +3 -3
  19. package/dist/runtime/index.d.ts +3 -1
  20. package/dist/runtime/index.js +2 -1
  21. package/dist/runtime/scaffold-bootstrap.js +5 -1
  22. package/dist/runtime/scaffold-documents.js +11 -8
  23. package/dist/runtime/scaffold-template-variable-groups.d.ts +154 -0
  24. package/dist/runtime/scaffold-template-variable-groups.js +13 -0
  25. package/dist/runtime/scaffold-template-variables.js +58 -1
  26. package/dist/runtime/scaffold.d.ts +5 -1
  27. package/dist/runtime/scaffold.js +5 -1
  28. package/dist/runtime/temp-roots.d.ts +44 -0
  29. package/dist/runtime/temp-roots.js +129 -0
  30. package/dist/runtime/template-builtins.js +4 -6
  31. package/dist/runtime/template-registry.d.ts +8 -0
  32. package/dist/runtime/template-registry.js +34 -1
  33. package/dist/runtime/template-source-external.d.ts +1 -0
  34. package/dist/runtime/template-source-external.js +4 -7
  35. package/dist/runtime/template-source-remote.js +44 -23
  36. package/dist/runtime/template-source-seeds.js +3 -9
  37. package/dist/runtime/template-source.d.ts +2 -3
  38. package/dist/runtime/template-source.js +8 -2
  39. package/package.json +6 -1
@@ -1,393 +1,16 @@
1
1
  import { promises as fsp } from "node:fs";
2
2
  import path from "node:path";
3
3
  import { ensureBlockConfigCanAddRestManifests } from "./cli-add-block-legacy-validator.js";
4
- import { assertRestResourceDoesNotExist, assertValidGeneratedSlug, assertValidRestResourceMethods, getWorkspaceBootstrapPath, normalizeBlockSlug, patchFile, quoteTsString, resolveRestResourceNamespace, rollbackWorkspaceMutation, snapshotWorkspaceFiles, } from "./cli-add-shared.js";
5
- import { buildRestResourceEndpointManifest, syncRestResourceArtifacts } from "./rest-resource-artifacts.js";
4
+ import { assertRestResourceDoesNotExist, assertValidGeneratedSlug, assertValidRestResourceMethods, getWorkspaceBootstrapPath, normalizeBlockSlug, resolveRestResourceNamespace, rollbackWorkspaceMutation, snapshotWorkspaceFiles, } from "./cli-add-shared.js";
5
+ import { ensureRestResourceBootstrapAnchors, ensureRestResourceSyncScriptAnchors, } from "./cli-add-workspace-rest-anchors.js";
6
+ import { buildRestResourceApiSource, buildRestResourceConfigEntry, buildRestResourceDataSource, buildRestResourceTypesSource, buildRestResourceValidatorsSource, toPascalCaseFromSlug, } from "./cli-add-workspace-rest-source-emitters.js";
7
+ import { syncRestResourceArtifacts } from "./rest-resource-artifacts.js";
6
8
  import { toTitleCase } from "./string-case.js";
7
9
  import { appendWorkspaceInventoryEntries, readWorkspaceInventory, } from "./workspace-inventory.js";
8
- import { resolveWorkspaceProject, } from "./workspace-project.js";
9
- const REST_RESOURCE_SERVER_GLOB = "/inc/rest/*.php";
10
- function escapeRegex(value) {
11
- return value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
12
- }
10
+ import { resolveWorkspaceProject } from "./workspace-project.js";
13
11
  function quotePhpString(value) {
14
12
  return `'${value.replace(/\\/gu, "\\\\").replace(/'/gu, "\\'")}'`;
15
13
  }
16
- function toPascalCaseFromSlug(slug) {
17
- return normalizeBlockSlug(slug)
18
- .split("-")
19
- .filter(Boolean)
20
- .map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1))
21
- .join("");
22
- }
23
- function indentMultiline(source, prefix) {
24
- return source
25
- .split("\n")
26
- .map((line) => `${prefix}${line}`)
27
- .join("\n");
28
- }
29
- function buildRestResourceConfigEntry(restResourceSlug, namespace, methods) {
30
- const pascalCase = toPascalCaseFromSlug(restResourceSlug);
31
- const title = toTitleCase(restResourceSlug);
32
- const manifest = buildRestResourceEndpointManifest({
33
- namespace,
34
- pascalCase,
35
- slugKebabCase: restResourceSlug,
36
- title,
37
- }, methods);
38
- return [
39
- "\t{",
40
- `\t\tapiFile: ${quoteTsString(`src/rest/${restResourceSlug}/api.ts`)},`,
41
- `\t\tclientFile: ${quoteTsString(`src/rest/${restResourceSlug}/api-client.ts`)},`,
42
- `\t\tdataFile: ${quoteTsString(`src/rest/${restResourceSlug}/data.ts`)},`,
43
- `\t\tmethods: [ ${methods.map((method) => quoteTsString(method)).join(", ")} ],`,
44
- `\t\tnamespace: ${quoteTsString(namespace)},`,
45
- `\t\topenApiFile: ${quoteTsString(`src/rest/${restResourceSlug}/api.openapi.json`)},`,
46
- `\t\tphpFile: ${quoteTsString(`inc/rest/${restResourceSlug}.php`)},`,
47
- "\t\trestManifest: defineEndpointManifest(",
48
- indentMultiline(JSON.stringify(manifest, null, "\t"), "\t\t\t"),
49
- "\t\t),",
50
- `\t\tslug: ${quoteTsString(restResourceSlug)},`,
51
- `\t\ttypesFile: ${quoteTsString(`src/rest/${restResourceSlug}/api-types.ts`)},`,
52
- `\t\tvalidatorsFile: ${quoteTsString(`src/rest/${restResourceSlug}/api-validators.ts`)},`,
53
- "\t},",
54
- ].join("\n");
55
- }
56
- function buildRestResourceTypesSource(restResourceSlug, methods) {
57
- const pascalCase = toPascalCaseFromSlug(restResourceSlug);
58
- const lines = [
59
- "import { tags } from 'typia';",
60
- "",
61
- `export type ${pascalCase}Status = 'draft' | 'published';`,
62
- "",
63
- `export interface ${pascalCase}Record {`,
64
- "\tid: number & tags.Type< 'uint32' >;",
65
- "\ttitle: string & tags.MinLength< 1 > & tags.MaxLength< 120 >;",
66
- "\tcontent?: string & tags.MaxLength< 2000 >;",
67
- `\tstatus: ${pascalCase}Status;`,
68
- "\tupdatedAt: string;",
69
- "}",
70
- ];
71
- if (methods.includes("list")) {
72
- lines.push("", `export interface ${pascalCase}ListQuery {`, "\tpage?: number & tags.Type< 'uint32' > & tags.Minimum< 1 > & tags.Default< 1 >;", "\tperPage?: number & tags.Type< 'uint32' > & tags.Minimum< 1 > & tags.Maximum< 50 > & tags.Default< 10 >;", "\tsearch?: string & tags.MaxLength< 120 >;", "}", "", `export interface ${pascalCase}ListResponse {`, `\titems: ${pascalCase}Record[];`, "\tpage: number & tags.Type< 'uint32' >;", "\tperPage: number & tags.Type< 'uint32' >;", "\ttotal: number & tags.Type< 'uint32' >;", "}");
73
- }
74
- if (methods.includes("read")) {
75
- lines.push("", `export interface ${pascalCase}ReadQuery {`, "\tid: number & tags.Type< 'uint32' >;", "}", "", `export type ${pascalCase}ReadResponse = ${pascalCase}Record;`);
76
- }
77
- if (methods.includes("create")) {
78
- lines.push("", `export interface ${pascalCase}CreateRequest {`, "\ttitle: string & tags.MinLength< 1 > & tags.MaxLength< 120 >;", "\tcontent?: string & tags.MaxLength< 2000 >;", `\tstatus?: ${pascalCase}Status;`, "}", "", `export type ${pascalCase}CreateResponse = ${pascalCase}Record;`);
79
- }
80
- if (methods.includes("update")) {
81
- lines.push("", `export interface ${pascalCase}UpdateQuery {`, "\tid: number & tags.Type< 'uint32' >;", "}", "", `export interface ${pascalCase}UpdateRequest {`, "\ttitle?: string & tags.MinLength< 1 > & tags.MaxLength< 120 >;", "\tcontent?: string & tags.MaxLength< 2000 >;", `\tstatus?: ${pascalCase}Status;`, "}", "", `export type ${pascalCase}UpdateResponse = ${pascalCase}Record;`);
82
- }
83
- if (methods.includes("delete")) {
84
- lines.push("", `export interface ${pascalCase}DeleteQuery {`, "\tid: number & tags.Type< 'uint32' >;", "}", "", `export interface ${pascalCase}DeleteResponse {`, "\tdeleted: true;", "\tid: number & tags.Type< 'uint32' >;", "}");
85
- }
86
- return `${lines.join("\n")}\n`;
87
- }
88
- function buildRestResourceValidatorsSource(restResourceSlug, methods) {
89
- const pascalCase = toPascalCaseFromSlug(restResourceSlug);
90
- const importedTypes = new Set();
91
- const validatorDeclarations = [];
92
- const validatorEntries = [];
93
- const addValidator = (propertyName, typeName, validateIdentifier) => {
94
- importedTypes.add(typeName);
95
- validatorDeclarations.push(`const ${validateIdentifier} = typia.createValidate< ${typeName} >();`);
96
- validatorEntries.push(`\t${propertyName}: ( input: unknown ) => toValidationResult< ${typeName} >( ${validateIdentifier}( input ) ),`);
97
- };
98
- if (methods.includes("list")) {
99
- addValidator("listQuery", `${pascalCase}ListQuery`, "validateListQuery");
100
- addValidator("listResponse", `${pascalCase}ListResponse`, "validateListResponse");
101
- }
102
- if (methods.includes("read")) {
103
- addValidator("readQuery", `${pascalCase}ReadQuery`, "validateReadQuery");
104
- addValidator("readResponse", `${pascalCase}ReadResponse`, "validateReadResponse");
105
- }
106
- if (methods.includes("create")) {
107
- addValidator("createRequest", `${pascalCase}CreateRequest`, "validateCreateRequest");
108
- addValidator("createResponse", `${pascalCase}CreateResponse`, "validateCreateResponse");
109
- }
110
- if (methods.includes("update")) {
111
- addValidator("updateQuery", `${pascalCase}UpdateQuery`, "validateUpdateQuery");
112
- addValidator("updateRequest", `${pascalCase}UpdateRequest`, "validateUpdateRequest");
113
- addValidator("updateResponse", `${pascalCase}UpdateResponse`, "validateUpdateResponse");
114
- }
115
- if (methods.includes("delete")) {
116
- addValidator("deleteQuery", `${pascalCase}DeleteQuery`, "validateDeleteQuery");
117
- addValidator("deleteResponse", `${pascalCase}DeleteResponse`, "validateDeleteResponse");
118
- }
119
- return `import typia from 'typia';
120
-
121
- import { toValidationResult } from '@wp-typia/rest';
122
- import type {
123
- \t${Array.from(importedTypes).sort().join(",\n\t")},
124
- } from './api-types';
125
-
126
- ${validatorDeclarations.join("\n")}
127
-
128
- export const apiValidators = {
129
- ${validatorEntries.join("\n")}
130
- };
131
- `;
132
- }
133
- function buildRestResourceApiSource(restResourceSlug, methods) {
134
- const pascalCase = toPascalCaseFromSlug(restResourceSlug);
135
- const typeImports = new Set();
136
- const clientEndpointImports = [];
137
- const exportedBindings = [];
138
- const writeMethods = methods.filter((method) => ["create", "update", "delete"].includes(method));
139
- if (methods.includes("list")) {
140
- typeImports.add(`${pascalCase}ListQuery`);
141
- clientEndpointImports.push(`list${pascalCase}ResourcesEndpoint`);
142
- exportedBindings.push(`export const restResourceListEndpoint = {
143
- \t...list${pascalCase}ResourcesEndpoint,
144
- \tbuildRequestOptions: () => ( {
145
- \t\turl: resolveRestRouteUrl( list${pascalCase}ResourcesEndpoint.path ),
146
- \t} ),
147
- };
148
-
149
- export function listResource( request: ${pascalCase}ListQuery ) {
150
- \treturn callEndpoint( restResourceListEndpoint, request );
151
- }`);
152
- }
153
- if (methods.includes("read")) {
154
- typeImports.add(`${pascalCase}ReadQuery`);
155
- clientEndpointImports.push(`read${pascalCase}ResourceEndpoint`);
156
- exportedBindings.push(`export const restResourceReadEndpoint = {
157
- \t...read${pascalCase}ResourceEndpoint,
158
- \tbuildRequestOptions: () => ( {
159
- \t\turl: resolveRestRouteUrl( read${pascalCase}ResourceEndpoint.path ),
160
- \t} ),
161
- };
162
-
163
- export function readResource( request: ${pascalCase}ReadQuery ) {
164
- \treturn callEndpoint( restResourceReadEndpoint, request );
165
- }`);
166
- }
167
- if (methods.includes("create")) {
168
- typeImports.add(`${pascalCase}CreateRequest`);
169
- clientEndpointImports.push(`create${pascalCase}ResourceEndpoint`);
170
- exportedBindings.push(`export const restResourceCreateEndpoint = {
171
- \t...create${pascalCase}ResourceEndpoint,
172
- \tbuildRequestOptions: () => {
173
- \t\tconst nonce = resolveRestNonce();
174
- \t\treturn {
175
- \t\t\theaders: nonce
176
- \t\t\t\t? {
177
- \t\t\t\t\t'X-WP-Nonce': nonce,
178
- \t\t\t\t}
179
- \t\t\t\t: undefined,
180
- \t\t\turl: resolveRestRouteUrl( create${pascalCase}ResourceEndpoint.path ),
181
- \t\t};
182
- \t},
183
- };
184
-
185
- export function createResource( request: ${pascalCase}CreateRequest ) {
186
- \treturn callEndpoint( restResourceCreateEndpoint, request );
187
- }`);
188
- }
189
- if (methods.includes("update")) {
190
- typeImports.add(`${pascalCase}UpdateQuery`);
191
- typeImports.add(`${pascalCase}UpdateRequest`);
192
- clientEndpointImports.push(`update${pascalCase}ResourceEndpoint`);
193
- exportedBindings.push(`export const restResourceUpdateEndpoint = {
194
- \t...update${pascalCase}ResourceEndpoint,
195
- \tbuildRequestOptions: () => {
196
- \t\tconst nonce = resolveRestNonce();
197
- \t\treturn {
198
- \t\t\theaders: nonce
199
- \t\t\t\t? {
200
- \t\t\t\t\t'X-WP-Nonce': nonce,
201
- \t\t\t\t}
202
- \t\t\t\t: undefined,
203
- \t\t\turl: resolveRestRouteUrl( update${pascalCase}ResourceEndpoint.path ),
204
- \t\t};
205
- \t},
206
- };
207
-
208
- export function updateResource( request: {
209
- \tbody: ${pascalCase}UpdateRequest;
210
- \tquery: ${pascalCase}UpdateQuery;
211
- } ) {
212
- \treturn callEndpoint( restResourceUpdateEndpoint, request );
213
- }`);
214
- }
215
- if (methods.includes("delete")) {
216
- typeImports.add(`${pascalCase}DeleteQuery`);
217
- clientEndpointImports.push(`delete${pascalCase}ResourceEndpoint`);
218
- exportedBindings.push(`export const restResourceDeleteEndpoint = {
219
- \t...delete${pascalCase}ResourceEndpoint,
220
- \tbuildRequestOptions: () => {
221
- \t\tconst nonce = resolveRestNonce();
222
- \t\treturn {
223
- \t\t\theaders: nonce
224
- \t\t\t\t? {
225
- \t\t\t\t\t'X-WP-Nonce': nonce,
226
- \t\t\t\t}
227
- \t\t\t\t: undefined,
228
- \t\t\turl: resolveRestRouteUrl( delete${pascalCase}ResourceEndpoint.path ),
229
- \t\t};
230
- \t},
231
- };
232
-
233
- export function deleteResource( request: ${pascalCase}DeleteQuery ) {
234
- \treturn callEndpoint( restResourceDeleteEndpoint, request );
235
- }`);
236
- }
237
- return `import {
238
- \tcallEndpoint,
239
- \tresolveRestRouteUrl,
240
- } from '@wp-typia/rest';
241
-
242
- import type {
243
- \t${Array.from(typeImports).sort().join(",\n\t")},
244
- } from './api-types';
245
- import {
246
- \t${clientEndpointImports.sort().join(",\n\t")},
247
- } from './api-client';
248
- ${writeMethods.length > 0
249
- ? `
250
- function resolveRestNonce( fallback?: string ): string | undefined {
251
- \tif ( typeof fallback === 'string' && fallback.length > 0 ) {
252
- \t\treturn fallback;
253
- \t}
254
-
255
- \tif ( typeof window === 'undefined' ) {
256
- \t\treturn undefined;
257
- \t}
258
-
259
- \tconst wpApiSettings = (
260
- \t\twindow as typeof window & {
261
- \t\t\twpApiSettings?: { nonce?: string };
262
- \t\t}
263
- \t).wpApiSettings;
264
-
265
- \treturn typeof wpApiSettings?.nonce === 'string' &&
266
- \t\twpApiSettings.nonce.length > 0
267
- \t\t? wpApiSettings.nonce
268
- \t\t: undefined;
269
- }
270
- `
271
- : ""}
272
- ${exportedBindings.join("\n\n")}
273
- `;
274
- }
275
- function buildRestResourceDataSource(restResourceSlug, methods) {
276
- const pascalCase = toPascalCaseFromSlug(restResourceSlug);
277
- const typeImports = new Set();
278
- const endpointImports = [];
279
- const exportedBindings = [];
280
- if (methods.includes("list")) {
281
- typeImports.add(`${pascalCase}ListQuery`);
282
- typeImports.add(`${pascalCase}ListResponse`);
283
- endpointImports.push("restResourceListEndpoint");
284
- exportedBindings.push(`export type Use${pascalCase}ListQueryOptions<
285
- \tSelected = ${pascalCase}ListResponse,
286
- > = UseEndpointQueryOptions<
287
- \t${pascalCase}ListQuery,
288
- \t${pascalCase}ListResponse,
289
- \tSelected
290
- >;
291
-
292
- export function use${pascalCase}ListQuery<
293
- \tSelected = ${pascalCase}ListResponse,
294
- >(
295
- \trequest: ${pascalCase}ListQuery,
296
- \toptions: Use${pascalCase}ListQueryOptions< Selected > = {}
297
- ) {
298
- \treturn useEndpointQuery( restResourceListEndpoint, request, options );
299
- }`);
300
- }
301
- if (methods.includes("read")) {
302
- typeImports.add(`${pascalCase}ReadQuery`);
303
- typeImports.add(`${pascalCase}ReadResponse`);
304
- endpointImports.push("restResourceReadEndpoint");
305
- exportedBindings.push(`export type Use${pascalCase}ReadQueryOptions<
306
- \tSelected = ${pascalCase}ReadResponse,
307
- > = UseEndpointQueryOptions<
308
- \t${pascalCase}ReadQuery,
309
- \t${pascalCase}ReadResponse,
310
- \tSelected
311
- >;
312
-
313
- export function use${pascalCase}ReadQuery<
314
- \tSelected = ${pascalCase}ReadResponse,
315
- >(
316
- \trequest: ${pascalCase}ReadQuery,
317
- \toptions: Use${pascalCase}ReadQueryOptions< Selected > = {}
318
- ) {
319
- \treturn useEndpointQuery( restResourceReadEndpoint, request, options );
320
- }`);
321
- }
322
- if (methods.includes("create")) {
323
- typeImports.add(`${pascalCase}CreateRequest`);
324
- typeImports.add(`${pascalCase}CreateResponse`);
325
- endpointImports.push("restResourceCreateEndpoint");
326
- exportedBindings.push(`export type UseCreate${pascalCase}ResourceMutationOptions = UseEndpointMutationOptions<
327
- \t${pascalCase}CreateRequest,
328
- \t${pascalCase}CreateResponse,
329
- \tunknown
330
- >;
331
-
332
- export function useCreate${pascalCase}ResourceMutation(
333
- \toptions: UseCreate${pascalCase}ResourceMutationOptions = {}
334
- ) {
335
- \treturn useEndpointMutation( restResourceCreateEndpoint, options );
336
- }`);
337
- }
338
- if (methods.includes("update")) {
339
- typeImports.add(`${pascalCase}UpdateQuery`);
340
- typeImports.add(`${pascalCase}UpdateRequest`);
341
- typeImports.add(`${pascalCase}UpdateResponse`);
342
- endpointImports.push("restResourceUpdateEndpoint");
343
- exportedBindings.push(`export type UseUpdate${pascalCase}ResourceMutationOptions = UseEndpointMutationOptions<
344
- \t{
345
- \t\tbody: ${pascalCase}UpdateRequest;
346
- \t\tquery: ${pascalCase}UpdateQuery;
347
- \t},
348
- \t${pascalCase}UpdateResponse,
349
- \tunknown
350
- >;
351
-
352
- export function useUpdate${pascalCase}ResourceMutation(
353
- \toptions: UseUpdate${pascalCase}ResourceMutationOptions = {}
354
- ) {
355
- \treturn useEndpointMutation( restResourceUpdateEndpoint, options );
356
- }`);
357
- }
358
- if (methods.includes("delete")) {
359
- typeImports.add(`${pascalCase}DeleteQuery`);
360
- typeImports.add(`${pascalCase}DeleteResponse`);
361
- endpointImports.push("restResourceDeleteEndpoint");
362
- exportedBindings.push(`export type UseDelete${pascalCase}ResourceMutationOptions = UseEndpointMutationOptions<
363
- \t${pascalCase}DeleteQuery,
364
- \t${pascalCase}DeleteResponse,
365
- \tunknown
366
- >;
367
-
368
- export function useDelete${pascalCase}ResourceMutation(
369
- \toptions: UseDelete${pascalCase}ResourceMutationOptions = {}
370
- ) {
371
- \treturn useEndpointMutation( restResourceDeleteEndpoint, options );
372
- }`);
373
- }
374
- return `import {
375
- \tuseEndpointMutation,
376
- \tuseEndpointQuery,
377
- \ttype UseEndpointMutationOptions,
378
- \ttype UseEndpointQueryOptions,
379
- } from '@wp-typia/rest/react';
380
-
381
- import type {
382
- \t${Array.from(typeImports).sort().join(",\n\t")},
383
- } from './api-types';
384
- import {
385
- \t${endpointImports.sort().join(",\n\t")},
386
- } from './api';
387
-
388
- ${exportedBindings.join("\n\n")}
389
- `;
390
- }
391
14
  function buildRestResourceRouteRegistrations(restResourceSlug, methods, functions) {
392
15
  const collectionRoutes = [];
393
16
  const itemRoutes = [];
@@ -801,188 +424,6 @@ ${routeRegistrations}
801
424
  add_action( 'rest_api_init', '${registerRoutesFunctionName}' );
802
425
  `;
803
426
  }
804
- async function ensureRestResourceBootstrapAnchors(workspace) {
805
- const bootstrapPath = getWorkspaceBootstrapPath(workspace);
806
- await patchFile(bootstrapPath, (source) => {
807
- let nextSource = source;
808
- const registerFunctionName = `${workspace.workspace.phpPrefix}_register_rest_resources`;
809
- const registerHook = `add_action( 'init', '${registerFunctionName}', 20 );`;
810
- const registerFunction = `
811
-
812
- function ${registerFunctionName}() {
813
- \tforeach ( glob( __DIR__ . '${REST_RESOURCE_SERVER_GLOB}' ) ?: array() as $rest_resource_module ) {
814
- \t\trequire_once $rest_resource_module;
815
- \t}
816
- }
817
- `;
818
- const insertionAnchors = [
819
- /add_action\(\s*["']init["']\s*,\s*["'][^"']+_load_textdomain["']\s*\);\s*\n/u,
820
- /\?>\s*$/u,
821
- ];
822
- const hasPhpFunctionDefinition = (functionName) => new RegExp(`function\\s+${escapeRegex(functionName)}\\s*\\(`, "u").test(nextSource);
823
- const insertPhpSnippet = (snippet) => {
824
- for (const anchor of insertionAnchors) {
825
- const candidate = nextSource.replace(anchor, (match) => `${snippet}\n${match}`);
826
- if (candidate !== nextSource) {
827
- nextSource = candidate;
828
- return;
829
- }
830
- }
831
- nextSource = `${nextSource.trimEnd()}\n${snippet}\n`;
832
- };
833
- const appendPhpSnippet = (snippet) => {
834
- const closingTagPattern = /\?>\s*$/u;
835
- if (closingTagPattern.test(nextSource)) {
836
- nextSource = nextSource.replace(closingTagPattern, `${snippet}\n?>`);
837
- return;
838
- }
839
- nextSource = `${nextSource.trimEnd()}\n${snippet}\n`;
840
- };
841
- if (!hasPhpFunctionDefinition(registerFunctionName)) {
842
- insertPhpSnippet(registerFunction);
843
- }
844
- else if (!nextSource.includes(REST_RESOURCE_SERVER_GLOB)) {
845
- throw new Error([
846
- `Unable to patch ${path.basename(bootstrapPath)} in ensureRestResourceBootstrapAnchors.`,
847
- `The existing ${registerFunctionName}() definition does not include ${REST_RESOURCE_SERVER_GLOB}.`,
848
- "Restore the generated bootstrap shape or wire the REST resource loader manually before retrying.",
849
- ].join(" "));
850
- }
851
- if (!nextSource.includes(registerHook)) {
852
- appendPhpSnippet(registerHook);
853
- }
854
- return nextSource;
855
- });
856
- }
857
- function assertSyncRestAnchor(nextSource, target, anchorDescription, hasAnchor, syncRestScriptPath) {
858
- if (!nextSource.includes(target) && !hasAnchor) {
859
- throw new Error([
860
- `ensureRestResourceSyncScriptAnchors could not patch ${path.basename(syncRestScriptPath)}.`,
861
- `Missing expected ${anchorDescription} anchor in scripts/sync-rest-contracts.ts.`,
862
- "Restore the generated template or add the REST_RESOURCES wiring manually before retrying.",
863
- ].join(" "));
864
- }
865
- }
866
- function replaceRequiredSyncRestSource(nextSource, target, anchor, replacement, anchorDescription, syncRestScriptPath) {
867
- if (nextSource.includes(target)) {
868
- return nextSource;
869
- }
870
- const hasAnchor = typeof anchor === "string" ? nextSource.includes(anchor) : anchor.test(nextSource);
871
- assertSyncRestAnchor(nextSource, target, anchorDescription, hasAnchor, syncRestScriptPath);
872
- return nextSource.replace(anchor, replacement);
873
- }
874
- async function ensureRestResourceSyncScriptAnchors(workspace) {
875
- const syncRestScriptPath = path.join(workspace.projectDir, "scripts", "sync-rest-contracts.ts");
876
- await patchFile(syncRestScriptPath, (source) => {
877
- let nextSource = source;
878
- const importAnchor = "import { BLOCKS, type WorkspaceBlockConfig } from './block-config';";
879
- const helperInsertionAnchor = "async function assertTypeArtifactsCurrent";
880
- const restBlocksAnchor = "const restBlocks = BLOCKS.filter( isRestEnabledBlock );";
881
- const noResourcesPattern = /if \( restBlocks.length === 0 \) \{[\s\S]*?\n\t\treturn;\n\t\}/u;
882
- const consoleLogPattern = /\n\tconsole\.log\(\n\t\toptions\.check/u;
883
- nextSource = replaceRequiredSyncRestSource(nextSource, "REST_RESOURCES", importAnchor, [
884
- "import {",
885
- "\tBLOCKS,",
886
- "\tREST_RESOURCES,",
887
- "\ttype WorkspaceBlockConfig,",
888
- "\ttype WorkspaceRestResourceConfig,",
889
- "} from './block-config';",
890
- ].join("\n"), "BLOCKS import", syncRestScriptPath);
891
- nextSource = replaceRequiredSyncRestSource(nextSource, "function isWorkspaceRestResource(", helperInsertionAnchor, [
892
- "function isWorkspaceRestResource(",
893
- "\tresource: WorkspaceRestResourceConfig",
894
- "): resource is WorkspaceRestResourceConfig & {",
895
- "\tclientFile: string;",
896
- "\topenApiFile: string;",
897
- "\trestManifest: NonNullable< WorkspaceRestResourceConfig[ 'restManifest' ] >;",
898
- "\ttypesFile: string;",
899
- "\tvalidatorsFile: string;",
900
- "} {",
901
- "\treturn (",
902
- "\t\ttypeof resource.clientFile === 'string' &&",
903
- "\t\ttypeof resource.openApiFile === 'string' &&",
904
- "\t\ttypeof resource.typesFile === 'string' &&",
905
- "\t\ttypeof resource.validatorsFile === 'string' &&",
906
- "\t\ttypeof resource.restManifest === 'object' &&",
907
- "\t\tresource.restManifest !== null",
908
- "\t);",
909
- "}",
910
- "",
911
- "async function assertTypeArtifactsCurrent",
912
- ].join("\n"), "type artifact assertion helper", syncRestScriptPath);
913
- nextSource = replaceRequiredSyncRestSource(nextSource, "const restResources = REST_RESOURCES.filter( isWorkspaceRestResource );", restBlocksAnchor, [
914
- "const restBlocks = BLOCKS.filter( isRestEnabledBlock );",
915
- "const restResources = REST_RESOURCES.filter( isWorkspaceRestResource );",
916
- ].join("\n"), "restBlocks filter", syncRestScriptPath);
917
- nextSource = replaceRequiredSyncRestSource(nextSource, "restBlocks.length === 0 && restResources.length === 0", noResourcesPattern, [
918
- "if ( restBlocks.length === 0 && restResources.length === 0 ) {",
919
- "\t\tconsole.log(",
920
- "\t\t\toptions.check",
921
- "\t\t\t\t? 'ℹ️ No REST-enabled workspace blocks or plugin-level REST resources are registered yet. `sync-rest --check` is already clean.'",
922
- "\t\t\t\t: 'ℹ️ No REST-enabled workspace blocks or plugin-level REST resources are registered yet.'",
923
- "\t\t);",
924
- "\t\treturn;",
925
- "\t}",
926
- ].join("\n"), "no-resources guard", syncRestScriptPath);
927
- nextSource = replaceRequiredSyncRestSource(nextSource, "for ( const resource of restResources ) {", consoleLogPattern, [
928
- "",
929
- "\tfor ( const resource of restResources ) {",
930
- "\t\tconst contracts = resource.restManifest.contracts;",
931
- "",
932
- "\t\tfor ( const [ baseName, contract ] of Object.entries( contracts ) ) {",
933
- "\t\t\tawait syncTypeSchemas(",
934
- "\t\t\t\t{",
935
- "\t\t\t\t\tjsonSchemaFile: path.join(",
936
- "\t\t\t\t\t\tpath.dirname( resource.typesFile ),",
937
- "\t\t\t\t\t\t'api-schemas',",
938
- "\t\t\t\t\t\t`${ baseName }.schema.json`",
939
- "\t\t\t\t\t),",
940
- "\t\t\t\t\topenApiFile: path.join(",
941
- "\t\t\t\t\t\tpath.dirname( resource.typesFile ),",
942
- "\t\t\t\t\t\t'api-schemas',",
943
- "\t\t\t\t\t\t`${ baseName }.openapi.json`",
944
- "\t\t\t\t\t),",
945
- "\t\t\t\t\tsourceTypeName: contract.sourceTypeName,",
946
- "\t\t\t\t\ttypesFile: resource.typesFile,",
947
- "\t\t\t\t},",
948
- "\t\t\t\t{",
949
- "\t\t\t\t\tcheck: options.check,",
950
- "\t\t\t\t}",
951
- "\t\t\t);",
952
- "\t\t}",
953
- "",
954
- "\t\tawait syncRestOpenApi(",
955
- "\t\t\t{",
956
- "\t\t\t\tmanifest: resource.restManifest,",
957
- "\t\t\t\topenApiFile: resource.openApiFile,",
958
- "\t\t\t\ttypesFile: resource.typesFile,",
959
- "\t\t\t},",
960
- "\t\t\t{",
961
- "\t\t\t\tcheck: options.check,",
962
- "\t\t\t}",
963
- "\t\t);",
964
- "",
965
- "\t\tawait syncEndpointClient(",
966
- "\t\t\t{",
967
- "\t\t\t\tclientFile: resource.clientFile,",
968
- "\t\t\t\tmanifest: resource.restManifest,",
969
- "\t\t\t\ttypesFile: resource.typesFile,",
970
- "\t\t\t\tvalidatorsFile: resource.validatorsFile,",
971
- "\t\t\t},",
972
- "\t\t\t{",
973
- "\t\t\t\tcheck: options.check,",
974
- "\t\t\t}",
975
- "\t\t);",
976
- "\t}",
977
- "",
978
- "\tconsole.log(",
979
- "\t\toptions.check",
980
- ].join("\n"), "success log insertion point", syncRestScriptPath);
981
- nextSource = nextSource.replace("✅ REST contract schemas, portable API clients, and endpoint-aware OpenAPI documents are already up to date with the TypeScript types!", "✅ REST contract schemas, portable API clients, and endpoint-aware OpenAPI documents are already up to date for workspace blocks and plugin-level resources!");
982
- nextSource = nextSource.replace("✅ REST contract schemas, portable API clients, and endpoint-aware OpenAPI documents generated from TypeScript types!", "✅ REST contract schemas, portable API clients, and endpoint-aware OpenAPI documents generated for workspace blocks and plugin-level resources!");
983
- return nextSource;
984
- });
985
- }
986
427
  /**
987
428
  * Scaffold a workspace-level REST resource and synchronize its generated
988
429
  * TypeScript and PHP artifacts.
@@ -2,10 +2,24 @@
2
2
  * Shared human-readable diagnostics for non-interactive `wp-typia` CLI flows.
3
3
  */
4
4
  export interface CliDiagnosticMessage {
5
+ code: CliDiagnosticCode;
5
6
  command: string;
6
7
  detailLines: string[];
7
8
  summary: string;
8
9
  }
10
+ export declare const CLI_DIAGNOSTIC_CODES: {
11
+ readonly COMMAND_EXECUTION: "command-execution";
12
+ readonly CONFIGURATION_MISSING: "configuration-missing";
13
+ readonly DEPENDENCIES_NOT_INSTALLED: "dependencies-not-installed";
14
+ readonly DOCTOR_CHECK_FAILED: "doctor-check-failed";
15
+ readonly INVALID_ARGUMENT: "invalid-argument";
16
+ readonly INVALID_COMMAND: "invalid-command";
17
+ readonly MISSING_ARGUMENT: "missing-argument";
18
+ readonly MISSING_BUILD_ARTIFACT: "missing-build-artifact";
19
+ readonly OUTSIDE_PROJECT_ROOT: "outside-project-root";
20
+ readonly UNSUPPORTED_COMMAND: "unsupported-command";
21
+ };
22
+ export type CliDiagnosticCode = (typeof CLI_DIAGNOSTIC_CODES)[keyof typeof CLI_DIAGNOSTIC_CODES];
9
23
  type DoctorCheckLike = {
10
24
  detail: string;
11
25
  label: string;
@@ -15,6 +29,7 @@ type DoctorCheckLike = {
15
29
  * Structured CLI failure carrying a stable summary/detail layout.
16
30
  */
17
31
  export declare class CliDiagnosticError extends Error {
32
+ readonly code: CliDiagnosticCode;
18
33
  readonly command: string;
19
34
  readonly detailLines: string[];
20
35
  readonly summary: string;
@@ -29,6 +44,7 @@ export declare function isCliDiagnosticError(error: unknown): error is CliDiagno
29
44
  */
30
45
  export declare function createCliCommandError(options: {
31
46
  command: string;
47
+ code?: CliDiagnosticCode;
32
48
  detailLines?: string[];
33
49
  error?: unknown;
34
50
  summary?: string;
@@ -38,6 +54,16 @@ export declare function createCliCommandError(options: {
38
54
  * plain message so existing non-command failures keep working.
39
55
  */
40
56
  export declare function formatCliDiagnosticError(error: unknown): string;
57
+ export declare function serializeCliDiagnosticError(error: unknown): {
58
+ code: CliDiagnosticCode;
59
+ command?: string;
60
+ detailLines?: string[];
61
+ kind: "command-execution";
62
+ message: string;
63
+ name: string;
64
+ summary?: string;
65
+ tag: "CommandExecutionError";
66
+ };
41
67
  /**
42
68
  * Format one human-readable doctor check row.
43
69
  */