@wp-typia/project-tools 0.19.1 → 0.19.3

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/built-in-block-non-ts-artifacts.js +1 -1039
  2. package/dist/runtime/built-in-block-non-ts-family-artifacts.d.ts +30 -0
  3. package/dist/runtime/built-in-block-non-ts-family-artifacts.js +1035 -0
  4. package/dist/runtime/built-in-block-non-ts-render-utils.d.ts +27 -0
  5. package/dist/runtime/built-in-block-non-ts-render-utils.js +51 -0
  6. package/dist/runtime/cli-add-workspace-rest-anchors.d.ts +3 -0
  7. package/dist/runtime/cli-add-workspace-rest-anchors.js +188 -0
  8. package/dist/runtime/cli-add-workspace-rest-source-emitters.d.ts +7 -0
  9. package/dist/runtime/cli-add-workspace-rest-source-emitters.js +379 -0
  10. package/dist/runtime/cli-add-workspace-rest.js +5 -564
  11. package/dist/runtime/cli-diagnostics.d.ts +2 -0
  12. package/dist/runtime/cli-diagnostics.js +10 -1
  13. package/dist/runtime/cli-help.js +3 -0
  14. package/dist/runtime/cli-init.d.ts +49 -0
  15. package/dist/runtime/cli-init.js +354 -0
  16. package/dist/runtime/cli-scaffold.d.ts +1 -0
  17. package/dist/runtime/cli-scaffold.js +5 -1
  18. package/dist/runtime/cli-templates.js +36 -6
  19. package/dist/runtime/external-template-guards.d.ts +31 -0
  20. package/dist/runtime/external-template-guards.js +132 -0
  21. package/dist/runtime/package-managers.d.ts +8 -0
  22. package/dist/runtime/package-managers.js +13 -0
  23. package/dist/runtime/package-versions.d.ts +8 -0
  24. package/dist/runtime/package-versions.js +84 -19
  25. package/dist/runtime/scaffold-documents.js +19 -1
  26. package/dist/runtime/scaffold-onboarding.d.ts +4 -0
  27. package/dist/runtime/scaffold-onboarding.js +25 -1
  28. package/dist/runtime/scaffold.js +2 -5
  29. package/dist/runtime/template-registry.d.ts +23 -1
  30. package/dist/runtime/template-registry.js +37 -3
  31. package/dist/runtime/template-source-external.js +9 -3
  32. package/dist/runtime/template-source-remote.js +5 -0
  33. package/dist/runtime/template-source-seeds.js +40 -6
  34. package/package.json +7 -2
  35. package/templates/_shared/base/package.json.mustache +0 -1
  36. package/templates/_shared/compound/core/package.json.mustache +0 -1
  37. package/templates/_shared/compound/persistence/package.json.mustache +0 -1
  38. package/templates/_shared/persistence/core/package.json.mustache +0 -1
  39. package/templates/interactivity/package.json.mustache +0 -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.
@@ -17,6 +17,8 @@ export declare const CLI_DIAGNOSTIC_CODES: {
17
17
  readonly MISSING_ARGUMENT: "missing-argument";
18
18
  readonly MISSING_BUILD_ARTIFACT: "missing-build-artifact";
19
19
  readonly OUTSIDE_PROJECT_ROOT: "outside-project-root";
20
+ readonly TEMPLATE_SOURCE_TIMEOUT: "template-source-timeout";
21
+ readonly TEMPLATE_SOURCE_TOO_LARGE: "template-source-too-large";
20
22
  readonly UNSUPPORTED_COMMAND: "unsupported-command";
21
23
  };
22
24
  export type CliDiagnosticCode = (typeof CLI_DIAGNOSTIC_CODES)[keyof typeof CLI_DIAGNOSTIC_CODES];
@@ -8,12 +8,15 @@ export const CLI_DIAGNOSTIC_CODES = {
8
8
  MISSING_ARGUMENT: "missing-argument",
9
9
  MISSING_BUILD_ARTIFACT: "missing-build-artifact",
10
10
  OUTSIDE_PROJECT_ROOT: "outside-project-root",
11
+ TEMPLATE_SOURCE_TIMEOUT: "template-source-timeout",
12
+ TEMPLATE_SOURCE_TOO_LARGE: "template-source-too-large",
11
13
  UNSUPPORTED_COMMAND: "unsupported-command",
12
14
  };
13
15
  const DEFAULT_CLI_FAILURE_SUMMARIES = {
14
16
  add: "Unable to complete the requested add workflow.",
15
17
  create: "Unable to complete the requested create workflow.",
16
18
  doctor: "One or more doctor checks failed.",
19
+ init: "Unable to preview the requested retrofit init plan.",
17
20
  mcp: "Unable to inspect or sync MCP metadata.",
18
21
  migrate: "Unable to complete the requested migration command.",
19
22
  sync: "Unable to complete the requested sync workflow.",
@@ -168,10 +171,16 @@ function inferCliDiagnosticCode(options) {
168
171
  if (/dependencies have not been installed yet/u.test(haystack)) {
169
172
  return CLI_DIAGNOSTIC_CODES.DEPENDENCIES_NOT_INSTALLED;
170
173
  }
174
+ if (/Timed out while .*external template|Timed out while .*npm template|Timed out while .*GitHub template/u.test(haystack)) {
175
+ return CLI_DIAGNOSTIC_CODES.TEMPLATE_SOURCE_TIMEOUT;
176
+ }
177
+ if (/external template size limit/u.test(haystack)) {
178
+ return CLI_DIAGNOSTIC_CODES.TEMPLATE_SOURCE_TOO_LARGE;
179
+ }
171
180
  if (options.command === "doctor") {
172
181
  return CLI_DIAGNOSTIC_CODES.DOCTOR_CHECK_FAILED;
173
182
  }
174
- if (/requires <|requires --/u.test(haystack)) {
183
+ if (/requires <|requires --|requires a value/u.test(haystack)) {
175
184
  return CLI_DIAGNOSTIC_CODES.MISSING_ARGUMENT;
176
185
  }
177
186
  if (/Unknown .*subcommand|Unknown add kind|Unknown template|removed in favor|does not support|The Bun-free fallback runtime does not support|The positional alias only accepts/u.test(haystack)) {
@@ -17,6 +17,7 @@ export function formatHelpText() {
17
17
  wp-typia create <project-dir> [--template persistence] [--external-layer-source <./path|github:owner/repo/path[#ref]|npm-package>] [--external-layer-id <layer-id>] [--alternate-render-targets <email,mjml,plain-text>] [--data-storage <post-meta|custom-table>] [--persistence-policy <authenticated|public>] [--namespace <value>] [--text-domain <value>] [--php-prefix <value>] [--with-migration-ui] [--with-wp-env] [--with-test-preset] [--yes] [--dry-run] [--no-install] [--package-manager <id>]
18
18
  wp-typia create <project-dir> [--template compound] [--external-layer-source <./path|github:owner/repo/path[#ref]|npm-package>] [--external-layer-id <layer-id>] [--inner-blocks-preset <freeform|ordered|horizontal|locked-structure>] [--alternate-render-targets <email,mjml,plain-text>] [--data-storage <post-meta|custom-table>] [--persistence-policy <authenticated|public>] [--namespace <value>] [--text-domain <value>] [--php-prefix <value>] [--with-migration-ui] [--with-wp-env] [--with-test-preset] [--yes] [--dry-run] [--no-install] [--package-manager <id>]
19
19
  wp-typia <project-dir> [create flags...]
20
+ wp-typia init [project-dir]
20
21
  wp-typia add block <name> --template <basic|interactivity|persistence|compound> [--external-layer-source <./path|github:owner/repo/path[#ref]|npm-package>] [--external-layer-id <layer-id>] [--inner-blocks-preset <freeform|ordered|horizontal|locked-structure>] [--alternate-render-targets <email,mjml,plain-text>] [--data-storage <post-meta|custom-table>] [--persistence-policy <authenticated|public>]
21
22
  wp-typia add variation <name> --block <block-slug>
22
23
  wp-typia add pattern <name>
@@ -37,6 +38,7 @@ Package managers: ${PACKAGE_MANAGER_IDS.join(", ")}
37
38
  Notes:
38
39
  \`wp-typia create\` is the canonical scaffold command.
39
40
  \`wp-typia <project-dir>\` remains a backward-compatible alias to \`create\` when \`<project-dir>\` is the only positional argument.
41
+ \`wp-typia init\` is a preview-only retrofit planner for existing projects. It does not write files yet.
40
42
  Use \`--template workspace\` as shorthand for \`@wp-typia/create-workspace-template\`, the official empty workspace scaffold behind \`wp-typia add ...\`.
41
43
  \`query-loop\` is create-only. Use \`wp-typia create <project-dir> --template query-loop\`; \`wp-typia add block\` accepts only basic, interactivity, persistence, and compound families.
42
44
  \`add variation\` uses an existing workspace block from \`scripts/block-config.ts\`.
@@ -46,6 +48,7 @@ Notes:
46
48
  \`add editor-plugin\` scaffolds a document-level editor extension under \`src/editor-plugins/\`.
47
49
  \`add hooked-block\` patches an existing workspace block's \`block.json\` \`blockHooks\` metadata.
48
50
  \`wp-typia doctor\` always checks environment readiness and reports when it only ran environment-level diagnostics; official workspace roots also get inventory and source-tree drift checks.
51
+ \`wp-typia init\` previews the minimum sync/doctor/migration adoption layer for supported existing layouts before a future write mode exists.
49
52
  \`wp-typia migrate doctor --all\` checks migration target alignment, snapshots, fixtures, and generated migration artifacts.
50
53
  \`migrate\` is the canonical migration command; \`migrations\` is no longer supported.`;
51
54
  }