@wp-typia/project-tools 0.17.0 → 0.19.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/dist/runtime/alternate-render-targets.d.ts +5 -0
- package/dist/runtime/alternate-render-targets.js +29 -0
- package/dist/runtime/block-generator-service-core.d.ts +2 -2
- package/dist/runtime/block-generator-service-core.js +13 -8
- package/dist/runtime/block-generator-service-spec.d.ts +10 -2
- package/dist/runtime/block-generator-service-spec.js +43 -1
- package/dist/runtime/built-in-block-artifacts.js +1 -0
- package/dist/runtime/built-in-block-code-templates/compound-child.d.ts +2 -2
- package/dist/runtime/built-in-block-code-templates/compound-child.js +35 -2
- package/dist/runtime/built-in-block-code-templates/compound-parent.d.ts +2 -2
- package/dist/runtime/built-in-block-code-templates/compound-parent.js +204 -27
- package/dist/runtime/built-in-block-code-templates/compound-persistence.d.ts +1 -1
- package/dist/runtime/built-in-block-code-templates/compound-persistence.js +11 -8
- package/dist/runtime/built-in-block-non-ts-artifacts.js +505 -2
- package/dist/runtime/cli-add-block.d.ts +6 -2
- package/dist/runtime/cli-add-block.js +71 -24
- package/dist/runtime/cli-add-shared.d.ts +58 -2
- package/dist/runtime/cli-add-shared.js +111 -12
- package/dist/runtime/cli-add-workspace-assets.d.ts +21 -1
- package/dist/runtime/cli-add-workspace-assets.js +417 -1
- package/dist/runtime/cli-add-workspace-rest.d.ts +14 -0
- package/dist/runtime/cli-add-workspace-rest.js +1060 -0
- package/dist/runtime/cli-add-workspace.d.ts +10 -1
- package/dist/runtime/cli-add-workspace.js +10 -1
- package/dist/runtime/cli-add.d.ts +3 -3
- package/dist/runtime/cli-add.js +2 -2
- package/dist/runtime/cli-core.d.ts +5 -1
- package/dist/runtime/cli-core.js +3 -1
- package/dist/runtime/cli-doctor-workspace.js +135 -1
- package/dist/runtime/cli-help.js +12 -7
- package/dist/runtime/cli-scaffold.d.ts +12 -2
- package/dist/runtime/cli-scaffold.js +222 -46
- package/dist/runtime/cli-templates.d.ts +4 -4
- package/dist/runtime/cli-templates.js +104 -39
- package/dist/runtime/cli-validation.d.ts +66 -0
- package/dist/runtime/cli-validation.js +92 -0
- package/dist/runtime/compound-inner-blocks.d.ts +78 -0
- package/dist/runtime/compound-inner-blocks.js +88 -0
- package/dist/runtime/index.d.ts +6 -3
- package/dist/runtime/index.js +4 -2
- package/dist/runtime/local-dev-presets.js +7 -2
- package/dist/runtime/migration-command-surface.js +2 -0
- package/dist/runtime/package-versions.d.ts +1 -0
- package/dist/runtime/package-versions.js +12 -0
- package/dist/runtime/rest-resource-artifacts.d.ts +35 -0
- package/dist/runtime/rest-resource-artifacts.js +158 -0
- package/dist/runtime/scaffold-answer-resolution.js +78 -8
- package/dist/runtime/scaffold-apply-utils.d.ts +4 -3
- package/dist/runtime/scaffold-apply-utils.js +34 -17
- package/dist/runtime/scaffold-bootstrap.d.ts +15 -0
- package/dist/runtime/scaffold-bootstrap.js +29 -7
- package/dist/runtime/scaffold-documents.js +24 -3
- package/dist/runtime/scaffold-identifiers.d.ts +17 -0
- package/dist/runtime/scaffold-identifiers.js +22 -0
- package/dist/runtime/scaffold-onboarding.js +25 -13
- package/dist/runtime/scaffold-package-manager-files.js +6 -1
- package/dist/runtime/scaffold-template-variables.js +22 -0
- package/dist/runtime/scaffold.d.ts +22 -1
- package/dist/runtime/scaffold.js +56 -11
- package/dist/runtime/template-render.d.ts +5 -2
- package/dist/runtime/template-render.js +9 -3
- package/dist/runtime/template-source-contracts.d.ts +11 -0
- package/dist/runtime/template-source-external.d.ts +1 -1
- package/dist/runtime/template-source-external.js +45 -13
- package/dist/runtime/template-source-normalization.d.ts +1 -1
- package/dist/runtime/template-source-normalization.js +5 -1
- package/dist/runtime/template-source-remote.d.ts +5 -0
- package/dist/runtime/template-source-remote.js +33 -0
- package/dist/runtime/template-source.js +35 -4
- package/dist/runtime/workspace-inventory.d.ts +43 -1
- package/dist/runtime/workspace-inventory.js +132 -1
- package/dist/runtime/workspace-project.d.ts +1 -1
- package/dist/runtime/workspace-project.js +3 -3
- package/package.json +9 -4
- package/templates/_shared/compound/core/scripts/add-compound-child.ts.mustache +728 -49
- package/templates/query-loop/src/validator-toolkit.ts.mustache +0 -1
|
@@ -0,0 +1,1060 @@
|
|
|
1
|
+
import { promises as fsp } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
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";
|
|
6
|
+
import { toTitleCase } from "./string-case.js";
|
|
7
|
+
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
|
+
}
|
|
13
|
+
function quotePhpString(value) {
|
|
14
|
+
return `'${value.replace(/\\/gu, "\\\\").replace(/'/gu, "\\'")}'`;
|
|
15
|
+
}
|
|
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
|
+
function buildRestResourceRouteRegistrations(restResourceSlug, methods, functions) {
|
|
392
|
+
const collectionRoutes = [];
|
|
393
|
+
const itemRoutes = [];
|
|
394
|
+
if (methods.includes("list")) {
|
|
395
|
+
collectionRoutes.push(`\t\tarray(
|
|
396
|
+
\t\t\t'methods' => WP_REST_Server::READABLE,
|
|
397
|
+
\t\t\t'callback' => '${functions.listHandlerName}',
|
|
398
|
+
\t\t\t'permission_callback' => '__return_true',
|
|
399
|
+
\t\t)`);
|
|
400
|
+
}
|
|
401
|
+
if (methods.includes("create")) {
|
|
402
|
+
collectionRoutes.push(`\t\tarray(
|
|
403
|
+
\t\t\t'methods' => WP_REST_Server::CREATABLE,
|
|
404
|
+
\t\t\t'callback' => '${functions.createHandlerName}',
|
|
405
|
+
\t\t\t'permission_callback' => '${functions.canWriteFunctionName}',
|
|
406
|
+
\t\t)`);
|
|
407
|
+
}
|
|
408
|
+
if (methods.includes("read")) {
|
|
409
|
+
itemRoutes.push(`\t\tarray(
|
|
410
|
+
\t\t\t'methods' => WP_REST_Server::READABLE,
|
|
411
|
+
\t\t\t'callback' => '${functions.readHandlerName}',
|
|
412
|
+
\t\t\t'permission_callback' => '__return_true',
|
|
413
|
+
\t\t)`);
|
|
414
|
+
}
|
|
415
|
+
if (methods.includes("update")) {
|
|
416
|
+
itemRoutes.push(`\t\tarray(
|
|
417
|
+
\t\t\t'methods' => WP_REST_Server::EDITABLE,
|
|
418
|
+
\t\t\t'callback' => '${functions.updateHandlerName}',
|
|
419
|
+
\t\t\t'permission_callback' => '${functions.canWriteFunctionName}',
|
|
420
|
+
\t\t)`);
|
|
421
|
+
}
|
|
422
|
+
if (methods.includes("delete")) {
|
|
423
|
+
itemRoutes.push(`\t\tarray(
|
|
424
|
+
\t\t\t'methods' => WP_REST_Server::DELETABLE,
|
|
425
|
+
\t\t\t'callback' => '${functions.deleteHandlerName}',
|
|
426
|
+
\t\t\t'permission_callback' => '${functions.canWriteFunctionName}',
|
|
427
|
+
\t\t)`);
|
|
428
|
+
}
|
|
429
|
+
const registrations = [];
|
|
430
|
+
if (collectionRoutes.length > 0) {
|
|
431
|
+
registrations.push(`\tregister_rest_route(
|
|
432
|
+
\t\t$namespace,
|
|
433
|
+
\t\t'/${restResourceSlug}',
|
|
434
|
+
\t\tarray(
|
|
435
|
+
${collectionRoutes.join(",\n")}
|
|
436
|
+
\t\t)
|
|
437
|
+
\t);`);
|
|
438
|
+
}
|
|
439
|
+
if (itemRoutes.length > 0) {
|
|
440
|
+
registrations.push(`\tregister_rest_route(
|
|
441
|
+
\t\t$namespace,
|
|
442
|
+
\t\t'/${restResourceSlug}/item',
|
|
443
|
+
\t\tarray(
|
|
444
|
+
${itemRoutes.join(",\n")}
|
|
445
|
+
\t\t)
|
|
446
|
+
\t);`);
|
|
447
|
+
}
|
|
448
|
+
return registrations.join("\n\n");
|
|
449
|
+
}
|
|
450
|
+
function buildRestResourcePhpSource(restResourceSlug, namespace, phpPrefix, textDomain, methods) {
|
|
451
|
+
const restResourceTitle = toTitleCase(restResourceSlug);
|
|
452
|
+
const restResourcePhpId = restResourceSlug.replace(/-/g, "_");
|
|
453
|
+
const canWriteFunctionName = `${phpPrefix}_${restResourcePhpId}_can_manage_rest_resource`;
|
|
454
|
+
const getItemsFunctionName = `${phpPrefix}_${restResourcePhpId}_get_rest_resource_items`;
|
|
455
|
+
const loadSchemaFunctionName = `${phpPrefix}_${restResourcePhpId}_load_rest_resource_schema`;
|
|
456
|
+
const normalizeSchemaFunctionName = `${phpPrefix}_${restResourcePhpId}_sanitize_rest_resource_schema`;
|
|
457
|
+
const validatePayloadFunctionName = `${phpPrefix}_${restResourcePhpId}_validate_rest_resource_payload`;
|
|
458
|
+
const normalizeItemFunctionName = `${phpPrefix}_${restResourcePhpId}_normalize_rest_resource_item`;
|
|
459
|
+
const saveItemsFunctionName = `${phpPrefix}_${restResourcePhpId}_save_rest_resource_items`;
|
|
460
|
+
const getOptionNameFunctionName = `${phpPrefix}_${restResourcePhpId}_get_rest_resource_option_name`;
|
|
461
|
+
const listHandlerName = `${phpPrefix}_${restResourcePhpId}_handle_list_rest_resource`;
|
|
462
|
+
const readHandlerName = `${phpPrefix}_${restResourcePhpId}_handle_read_rest_resource`;
|
|
463
|
+
const createHandlerName = `${phpPrefix}_${restResourcePhpId}_handle_create_rest_resource`;
|
|
464
|
+
const updateHandlerName = `${phpPrefix}_${restResourcePhpId}_handle_update_rest_resource`;
|
|
465
|
+
const deleteHandlerName = `${phpPrefix}_${restResourcePhpId}_handle_delete_rest_resource`;
|
|
466
|
+
const registerRoutesFunctionName = `${phpPrefix}_${restResourcePhpId}_register_rest_routes`;
|
|
467
|
+
const routeRegistrations = buildRestResourceRouteRegistrations(restResourceSlug, methods, {
|
|
468
|
+
canWriteFunctionName,
|
|
469
|
+
createHandlerName,
|
|
470
|
+
deleteHandlerName,
|
|
471
|
+
listHandlerName,
|
|
472
|
+
readHandlerName,
|
|
473
|
+
updateHandlerName,
|
|
474
|
+
});
|
|
475
|
+
return `<?php
|
|
476
|
+
if ( ! defined( 'ABSPATH' ) ) {
|
|
477
|
+
\treturn;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
if ( ! function_exists( '${getOptionNameFunctionName}' ) ) {
|
|
481
|
+
\tfunction ${getOptionNameFunctionName}() {
|
|
482
|
+
\t\treturn ${quotePhpString(`${phpPrefix}_${restResourcePhpId}_rest_resource_items`)};
|
|
483
|
+
\t}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
if ( ! function_exists( '${normalizeItemFunctionName}' ) ) {
|
|
487
|
+
\tfunction ${normalizeItemFunctionName}( array $item ) {
|
|
488
|
+
\t\treturn array(
|
|
489
|
+
\t\t\t'id' => isset( $item['id'] ) ? (int) $item['id'] : 0,
|
|
490
|
+
\t\t\t'title' => isset( $item['title'] ) ? (string) $item['title'] : '',
|
|
491
|
+
\t\t\t'content' => isset( $item['content'] ) ? (string) $item['content'] : '',
|
|
492
|
+
\t\t\t'status' => isset( $item['status'] ) && 'published' === $item['status'] ? 'published' : 'draft',
|
|
493
|
+
\t\t\t'updatedAt' => isset( $item['updatedAt'] ) ? (string) $item['updatedAt'] : gmdate( 'c' ),
|
|
494
|
+
\t\t);
|
|
495
|
+
\t}
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
if ( ! function_exists( '${getItemsFunctionName}' ) ) {
|
|
499
|
+
\tfunction ${getItemsFunctionName}() {
|
|
500
|
+
\t\t$seed_items = array(
|
|
501
|
+
\t\t\tarray(
|
|
502
|
+
\t\t\t\t'id' => 1,
|
|
503
|
+
\t\t\t\t'title' => ${quotePhpString(`${restResourceTitle} Starter`)},
|
|
504
|
+
\t\t\t\t'content' => ${quotePhpString(`Replace this seeded ${restResourceTitle.toLowerCase()} content with your plugin data source.`)},
|
|
505
|
+
\t\t\t\t'status' => 'draft',
|
|
506
|
+
\t\t\t\t'updatedAt' => '2026-01-01T00:00:00Z',
|
|
507
|
+
\t\t\t),
|
|
508
|
+
\t\t);
|
|
509
|
+
\t\t$items = get_option( ${getOptionNameFunctionName}(), $seed_items );
|
|
510
|
+
|
|
511
|
+
\t\tif ( ! is_array( $items ) ) {
|
|
512
|
+
\t\t\t$items = $seed_items;
|
|
513
|
+
\t\t}
|
|
514
|
+
|
|
515
|
+
\t\treturn array_values(
|
|
516
|
+
\t\t\tarray_map(
|
|
517
|
+
\t\t\t\t'${normalizeItemFunctionName}',
|
|
518
|
+
\t\t\t\tarray_filter(
|
|
519
|
+
\t\t\t\t\t$items,
|
|
520
|
+
\t\t\t\t\t'is_array'
|
|
521
|
+
\t\t\t\t)
|
|
522
|
+
\t\t\t)
|
|
523
|
+
\t\t);
|
|
524
|
+
\t}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
if ( ! function_exists( '${saveItemsFunctionName}' ) ) {
|
|
528
|
+
\tfunction ${saveItemsFunctionName}( array $items ) {
|
|
529
|
+
\t\tupdate_option(
|
|
530
|
+
\t\t\t${getOptionNameFunctionName}(),
|
|
531
|
+
\t\t\tarray_values(
|
|
532
|
+
\t\t\t\tarray_map(
|
|
533
|
+
\t\t\t\t\t'${normalizeItemFunctionName}',
|
|
534
|
+
\t\t\t\t\t$items
|
|
535
|
+
\t\t\t\t)
|
|
536
|
+
\t\t\t),
|
|
537
|
+
\t\t\tfalse
|
|
538
|
+
\t\t);
|
|
539
|
+
\t}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
if ( ! function_exists( '${loadSchemaFunctionName}' ) ) {
|
|
543
|
+
\tfunction ${loadSchemaFunctionName}( $schema_name ) {
|
|
544
|
+
\t\t$project_root = dirname( __DIR__, 2 );
|
|
545
|
+
\t\t$schema_path = $project_root . '/src/rest/${restResourceSlug}/api-schemas/' . $schema_name . '.schema.json';
|
|
546
|
+
\t\tif ( ! file_exists( $schema_path ) ) {
|
|
547
|
+
\t\t\treturn null;
|
|
548
|
+
\t\t}
|
|
549
|
+
|
|
550
|
+
\t\t$decoded = json_decode( file_get_contents( $schema_path ), true );
|
|
551
|
+
\t\treturn is_array( $decoded ) ? $decoded : null;
|
|
552
|
+
\t}
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
if ( ! function_exists( '${normalizeSchemaFunctionName}' ) ) {
|
|
556
|
+
\tfunction ${normalizeSchemaFunctionName}( $schema ) {
|
|
557
|
+
\t\tif ( ! is_array( $schema ) ) {
|
|
558
|
+
\t\t\treturn $schema;
|
|
559
|
+
\t\t}
|
|
560
|
+
|
|
561
|
+
\t\tunset( $schema['$schema'], $schema['title'] );
|
|
562
|
+
|
|
563
|
+
\t\tif ( isset( $schema['properties'] ) && is_array( $schema['properties'] ) ) {
|
|
564
|
+
\t\t\tforeach ( $schema['properties'] as $key => $property_schema ) {
|
|
565
|
+
\t\t\t\t$schema['properties'][ $key ] = ${normalizeSchemaFunctionName}( $property_schema );
|
|
566
|
+
\t\t\t}
|
|
567
|
+
\t\t}
|
|
568
|
+
|
|
569
|
+
\t\tif ( isset( $schema['items'] ) && is_array( $schema['items'] ) ) {
|
|
570
|
+
\t\t\t$schema['items'] = ${normalizeSchemaFunctionName}( $schema['items'] );
|
|
571
|
+
\t\t}
|
|
572
|
+
|
|
573
|
+
\t\treturn $schema;
|
|
574
|
+
\t}
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
if ( ! function_exists( '${validatePayloadFunctionName}' ) ) {
|
|
578
|
+
\tfunction ${validatePayloadFunctionName}( $value, $schema_name, $param_name ) {
|
|
579
|
+
\t\t$schema = ${loadSchemaFunctionName}( $schema_name );
|
|
580
|
+
\t\tif ( ! is_array( $schema ) ) {
|
|
581
|
+
\t\t\treturn new WP_Error( 'missing_schema', 'Missing REST schema.', array( 'status' => 500 ) );
|
|
582
|
+
\t\t}
|
|
583
|
+
|
|
584
|
+
\t\t$rest_schema = ${normalizeSchemaFunctionName}( $schema );
|
|
585
|
+
\t\t$validation = rest_validate_value_from_schema( $value, $rest_schema, $param_name );
|
|
586
|
+
\t\tif ( is_wp_error( $validation ) ) {
|
|
587
|
+
\t\t\treturn $validation;
|
|
588
|
+
\t\t}
|
|
589
|
+
|
|
590
|
+
\t\treturn rest_sanitize_value_from_schema( $value, $rest_schema, $param_name );
|
|
591
|
+
\t}
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
if ( ! function_exists( '${canWriteFunctionName}' ) ) {
|
|
595
|
+
\tfunction ${canWriteFunctionName}() {
|
|
596
|
+
\t\treturn current_user_can( 'edit_posts' );
|
|
597
|
+
\t}
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
if ( ! function_exists( '${listHandlerName}' ) ) {
|
|
601
|
+
\tfunction ${listHandlerName}( WP_REST_Request $request ) {
|
|
602
|
+
\t\t$payload_input = array();
|
|
603
|
+
\t\t$page = $request->get_param( 'page' );
|
|
604
|
+
\t\t$per_page = $request->get_param( 'perPage' );
|
|
605
|
+
\t\t$search = $request->get_param( 'search' );
|
|
606
|
+
|
|
607
|
+
\t\tif ( null !== $page ) {
|
|
608
|
+
\t\t\t$payload_input['page'] = $page;
|
|
609
|
+
\t\t}
|
|
610
|
+
\t\tif ( null !== $per_page ) {
|
|
611
|
+
\t\t\t$payload_input['perPage'] = $per_page;
|
|
612
|
+
\t\t}
|
|
613
|
+
\t\tif ( null !== $search ) {
|
|
614
|
+
\t\t\t$payload_input['search'] = $search;
|
|
615
|
+
\t\t}
|
|
616
|
+
|
|
617
|
+
\t\t$payload = ${validatePayloadFunctionName}(
|
|
618
|
+
\t\t\t$payload_input,
|
|
619
|
+
\t\t\t'list-query',
|
|
620
|
+
\t\t\t'query'
|
|
621
|
+
\t\t);
|
|
622
|
+
|
|
623
|
+
\t\tif ( is_wp_error( $payload ) ) {
|
|
624
|
+
\t\t\treturn $payload;
|
|
625
|
+
\t\t}
|
|
626
|
+
|
|
627
|
+
\t\t$page = isset( $payload['page'] ) ? max( 1, (int) $payload['page'] ) : 1;
|
|
628
|
+
\t\t$per_page = isset( $payload['perPage'] ) ? min( 50, max( 1, (int) $payload['perPage'] ) ) : 10;
|
|
629
|
+
\t\t$search = isset( $payload['search'] ) ? strtolower( (string) $payload['search'] ) : '';
|
|
630
|
+
\t\t$items = ${getItemsFunctionName}();
|
|
631
|
+
|
|
632
|
+
\t\tif ( '' !== $search ) {
|
|
633
|
+
\t\t\t$items = array_values(
|
|
634
|
+
\t\t\t\tarray_filter(
|
|
635
|
+
\t\t\t\t\t$items,
|
|
636
|
+
\t\t\t\t\tstatic function ( $item ) use ( $search ) {
|
|
637
|
+
\t\t\t\t\t\treturn false !== strpos( strtolower( (string) ( $item['title'] ?? '' ) ), $search ) ||
|
|
638
|
+
\t\t\t\t\t\t\tfalse !== strpos( strtolower( (string) ( $item['content'] ?? '' ) ), $search );
|
|
639
|
+
\t\t\t\t\t}
|
|
640
|
+
\t\t\t\t)
|
|
641
|
+
\t\t\t);
|
|
642
|
+
\t\t}
|
|
643
|
+
|
|
644
|
+
\t\t$total = count( $items );
|
|
645
|
+
\t\t$items = array_slice( $items, ( $page - 1 ) * $per_page, $per_page );
|
|
646
|
+
|
|
647
|
+
\t\treturn rest_ensure_response(
|
|
648
|
+
\t\t\tarray(
|
|
649
|
+
\t\t\t\t'items' => $items,
|
|
650
|
+
\t\t\t\t'page' => $page,
|
|
651
|
+
\t\t\t\t'perPage' => $per_page,
|
|
652
|
+
\t\t\t\t'total' => $total,
|
|
653
|
+
\t\t\t)
|
|
654
|
+
\t\t);
|
|
655
|
+
\t}
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
if ( ! function_exists( '${readHandlerName}' ) ) {
|
|
659
|
+
\tfunction ${readHandlerName}( WP_REST_Request $request ) {
|
|
660
|
+
\t\t$payload = ${validatePayloadFunctionName}(
|
|
661
|
+
\t\t\tarray(
|
|
662
|
+
\t\t\t\t'id' => $request->get_param( 'id' ),
|
|
663
|
+
\t\t\t),
|
|
664
|
+
\t\t\t'read-query',
|
|
665
|
+
\t\t\t'query'
|
|
666
|
+
\t\t);
|
|
667
|
+
|
|
668
|
+
\t\tif ( is_wp_error( $payload ) ) {
|
|
669
|
+
\t\t\treturn $payload;
|
|
670
|
+
\t\t}
|
|
671
|
+
|
|
672
|
+
\t\tforeach ( ${getItemsFunctionName}() as $item ) {
|
|
673
|
+
\t\t\tif ( (int) $item['id'] === (int) $payload['id'] ) {
|
|
674
|
+
\t\t\t\treturn rest_ensure_response( $item );
|
|
675
|
+
\t\t\t}
|
|
676
|
+
\t\t}
|
|
677
|
+
|
|
678
|
+
\t\treturn new WP_Error( 'rest_not_found', 'Resource not found.', array( 'status' => 404 ) );
|
|
679
|
+
\t}
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
if ( ! function_exists( '${createHandlerName}' ) ) {
|
|
683
|
+
\tfunction ${createHandlerName}( WP_REST_Request $request ) {
|
|
684
|
+
\t\t$payload = ${validatePayloadFunctionName}( $request->get_json_params(), 'create-request', 'body' );
|
|
685
|
+
\t\tif ( is_wp_error( $payload ) ) {
|
|
686
|
+
\t\t\treturn $payload;
|
|
687
|
+
\t\t}
|
|
688
|
+
|
|
689
|
+
\t\t$items = ${getItemsFunctionName}();
|
|
690
|
+
\t\t$next_id = 1;
|
|
691
|
+
\t\tforeach ( $items as $item ) {
|
|
692
|
+
\t\t\t$next_id = max( $next_id, (int) $item['id'] + 1 );
|
|
693
|
+
\t\t}
|
|
694
|
+
|
|
695
|
+
\t\t$record = ${normalizeItemFunctionName}(
|
|
696
|
+
\t\t\tarray(
|
|
697
|
+
\t\t\t\t'id' => $next_id,
|
|
698
|
+
\t\t\t\t'title' => (string) $payload['title'],
|
|
699
|
+
\t\t\t\t'content' => isset( $payload['content'] ) ? (string) $payload['content'] : '',
|
|
700
|
+
\t\t\t\t'status' => isset( $payload['status'] ) ? (string) $payload['status'] : 'draft',
|
|
701
|
+
\t\t\t\t'updatedAt' => gmdate( 'c' ),
|
|
702
|
+
\t\t\t)
|
|
703
|
+
\t\t);
|
|
704
|
+
|
|
705
|
+
\t\t$items[] = $record;
|
|
706
|
+
\t\t${saveItemsFunctionName}( $items );
|
|
707
|
+
|
|
708
|
+
\t\treturn rest_ensure_response( $record );
|
|
709
|
+
\t}
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
if ( ! function_exists( '${updateHandlerName}' ) ) {
|
|
713
|
+
\tfunction ${updateHandlerName}( WP_REST_Request $request ) {
|
|
714
|
+
\t\t$query = ${validatePayloadFunctionName}(
|
|
715
|
+
\t\t\tarray(
|
|
716
|
+
\t\t\t\t'id' => $request->get_param( 'id' ),
|
|
717
|
+
\t\t\t),
|
|
718
|
+
\t\t\t'update-query',
|
|
719
|
+
\t\t\t'query'
|
|
720
|
+
\t\t);
|
|
721
|
+
\t\tif ( is_wp_error( $query ) ) {
|
|
722
|
+
\t\t\treturn $query;
|
|
723
|
+
\t\t}
|
|
724
|
+
|
|
725
|
+
\t\t$payload = ${validatePayloadFunctionName}( $request->get_json_params(), 'update-request', 'body' );
|
|
726
|
+
\t\tif ( is_wp_error( $payload ) ) {
|
|
727
|
+
\t\t\treturn $payload;
|
|
728
|
+
\t\t}
|
|
729
|
+
|
|
730
|
+
\t\t$items = ${getItemsFunctionName}();
|
|
731
|
+
\t\tforeach ( $items as $index => $item ) {
|
|
732
|
+
\t\t\tif ( (int) $item['id'] !== (int) $query['id'] ) {
|
|
733
|
+
\t\t\t\tcontinue;
|
|
734
|
+
\t\t\t}
|
|
735
|
+
|
|
736
|
+
\t\t\t$items[ $index ] = ${normalizeItemFunctionName}(
|
|
737
|
+
\t\t\t\tarray(
|
|
738
|
+
\t\t\t\t\t'id' => $item['id'],
|
|
739
|
+
\t\t\t\t\t'title' => isset( $payload['title'] ) ? (string) $payload['title'] : (string) $item['title'],
|
|
740
|
+
\t\t\t\t\t'content' => array_key_exists( 'content', $payload ) ? (string) $payload['content'] : (string) $item['content'],
|
|
741
|
+
\t\t\t\t\t'status' => isset( $payload['status'] ) ? (string) $payload['status'] : (string) $item['status'],
|
|
742
|
+
\t\t\t\t\t'updatedAt' => gmdate( 'c' ),
|
|
743
|
+
\t\t\t\t)
|
|
744
|
+
\t\t\t);
|
|
745
|
+
|
|
746
|
+
\t\t\t${saveItemsFunctionName}( $items );
|
|
747
|
+
\t\t\treturn rest_ensure_response( $items[ $index ] );
|
|
748
|
+
\t\t}
|
|
749
|
+
|
|
750
|
+
\t\treturn new WP_Error( 'rest_not_found', 'Resource not found.', array( 'status' => 404 ) );
|
|
751
|
+
\t}
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
if ( ! function_exists( '${deleteHandlerName}' ) ) {
|
|
755
|
+
\tfunction ${deleteHandlerName}( WP_REST_Request $request ) {
|
|
756
|
+
\t\t$query = ${validatePayloadFunctionName}(
|
|
757
|
+
\t\t\tarray(
|
|
758
|
+
\t\t\t\t'id' => $request->get_param( 'id' ),
|
|
759
|
+
\t\t\t),
|
|
760
|
+
\t\t\t'delete-query',
|
|
761
|
+
\t\t\t'query'
|
|
762
|
+
\t\t);
|
|
763
|
+
\t\tif ( is_wp_error( $query ) ) {
|
|
764
|
+
\t\t\treturn $query;
|
|
765
|
+
\t\t}
|
|
766
|
+
|
|
767
|
+
\t\t$items = ${getItemsFunctionName}();
|
|
768
|
+
\t\t$filtered = array_values(
|
|
769
|
+
\t\t\tarray_filter(
|
|
770
|
+
\t\t\t\t$items,
|
|
771
|
+
\t\t\t\tstatic function ( $item ) use ( $query ) {
|
|
772
|
+
\t\t\t\t\treturn (int) $item['id'] !== (int) $query['id'];
|
|
773
|
+
\t\t\t\t}
|
|
774
|
+
\t\t\t)
|
|
775
|
+
\t\t);
|
|
776
|
+
\t\t$was_deleted = count( $filtered ) !== count( $items );
|
|
777
|
+
|
|
778
|
+
\t\tif ( ! $was_deleted ) {
|
|
779
|
+
\t\t\treturn new WP_Error( 'rest_not_found', 'Resource not found.', array( 'status' => 404 ) );
|
|
780
|
+
\t\t}
|
|
781
|
+
|
|
782
|
+
\t\t${saveItemsFunctionName}( $filtered );
|
|
783
|
+
|
|
784
|
+
\t\treturn rest_ensure_response(
|
|
785
|
+
\t\t\tarray(
|
|
786
|
+
\t\t\t\t'deleted' => true,
|
|
787
|
+
\t\t\t\t'id' => (int) $query['id'],
|
|
788
|
+
\t\t\t)
|
|
789
|
+
\t\t);
|
|
790
|
+
\t}
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
if ( ! function_exists( '${registerRoutesFunctionName}' ) ) {
|
|
794
|
+
\tfunction ${registerRoutesFunctionName}() {
|
|
795
|
+
\t\t$namespace = ${quotePhpString(namespace)};
|
|
796
|
+
|
|
797
|
+
${routeRegistrations}
|
|
798
|
+
\t}
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
add_action( 'rest_api_init', '${registerRoutesFunctionName}' );
|
|
802
|
+
`;
|
|
803
|
+
}
|
|
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
|
+
/**
|
|
987
|
+
* Scaffold a workspace-level REST resource and synchronize its generated
|
|
988
|
+
* TypeScript and PHP artifacts.
|
|
989
|
+
*
|
|
990
|
+
* @param options Command options for the REST resource scaffold workflow.
|
|
991
|
+
* @returns Resolved scaffold metadata for the created REST resource.
|
|
992
|
+
*/
|
|
993
|
+
export async function runAddRestResourceCommand({ cwd = process.cwd(), methods, namespace, restResourceName, }) {
|
|
994
|
+
const workspace = resolveWorkspaceProject(cwd);
|
|
995
|
+
const restResourceSlug = assertValidGeneratedSlug("REST resource name", normalizeBlockSlug(restResourceName), "wp-typia add rest-resource <name> [--namespace <vendor/v1>] [--methods <list,read,create>]");
|
|
996
|
+
const resolvedMethods = assertValidRestResourceMethods(methods);
|
|
997
|
+
const resolvedNamespace = resolveRestResourceNamespace(workspace.workspace.namespace, namespace);
|
|
998
|
+
const inventory = readWorkspaceInventory(workspace.projectDir);
|
|
999
|
+
assertRestResourceDoesNotExist(workspace.projectDir, restResourceSlug, inventory);
|
|
1000
|
+
const blockConfigPath = path.join(workspace.projectDir, "scripts", "block-config.ts");
|
|
1001
|
+
const bootstrapPath = getWorkspaceBootstrapPath(workspace);
|
|
1002
|
+
const syncRestScriptPath = path.join(workspace.projectDir, "scripts", "sync-rest-contracts.ts");
|
|
1003
|
+
const restResourceDir = path.join(workspace.projectDir, "src", "rest", restResourceSlug);
|
|
1004
|
+
const typesFilePath = path.join(restResourceDir, "api-types.ts");
|
|
1005
|
+
const validatorsFilePath = path.join(restResourceDir, "api-validators.ts");
|
|
1006
|
+
const apiFilePath = path.join(restResourceDir, "api.ts");
|
|
1007
|
+
const dataFilePath = path.join(restResourceDir, "data.ts");
|
|
1008
|
+
const clientFilePath = path.join(restResourceDir, "api-client.ts");
|
|
1009
|
+
const phpFilePath = path.join(workspace.projectDir, "inc", "rest", `${restResourceSlug}.php`);
|
|
1010
|
+
const mutationSnapshot = {
|
|
1011
|
+
fileSources: await snapshotWorkspaceFiles([
|
|
1012
|
+
blockConfigPath,
|
|
1013
|
+
bootstrapPath,
|
|
1014
|
+
syncRestScriptPath,
|
|
1015
|
+
]),
|
|
1016
|
+
snapshotDirs: [],
|
|
1017
|
+
targetPaths: [restResourceDir, phpFilePath],
|
|
1018
|
+
};
|
|
1019
|
+
try {
|
|
1020
|
+
await fsp.mkdir(restResourceDir, { recursive: true });
|
|
1021
|
+
await fsp.mkdir(path.dirname(phpFilePath), { recursive: true });
|
|
1022
|
+
await ensureRestResourceBootstrapAnchors(workspace);
|
|
1023
|
+
await ensureRestResourceSyncScriptAnchors(workspace);
|
|
1024
|
+
await fsp.writeFile(typesFilePath, buildRestResourceTypesSource(restResourceSlug, resolvedMethods), "utf8");
|
|
1025
|
+
await fsp.writeFile(validatorsFilePath, buildRestResourceValidatorsSource(restResourceSlug, resolvedMethods), "utf8");
|
|
1026
|
+
await fsp.writeFile(apiFilePath, buildRestResourceApiSource(restResourceSlug, resolvedMethods), "utf8");
|
|
1027
|
+
await fsp.writeFile(dataFilePath, buildRestResourceDataSource(restResourceSlug, resolvedMethods), "utf8");
|
|
1028
|
+
await fsp.writeFile(phpFilePath, buildRestResourcePhpSource(restResourceSlug, resolvedNamespace, workspace.workspace.phpPrefix, workspace.workspace.textDomain, resolvedMethods), "utf8");
|
|
1029
|
+
await syncRestResourceArtifacts({
|
|
1030
|
+
clientFile: `src/rest/${restResourceSlug}/api-client.ts`,
|
|
1031
|
+
methods: resolvedMethods,
|
|
1032
|
+
outputDir: restResourceDir,
|
|
1033
|
+
projectDir: workspace.projectDir,
|
|
1034
|
+
typesFile: `src/rest/${restResourceSlug}/api-types.ts`,
|
|
1035
|
+
validatorsFile: `src/rest/${restResourceSlug}/api-validators.ts`,
|
|
1036
|
+
variables: {
|
|
1037
|
+
namespace: resolvedNamespace,
|
|
1038
|
+
pascalCase: toPascalCaseFromSlug(restResourceSlug),
|
|
1039
|
+
slugKebabCase: restResourceSlug,
|
|
1040
|
+
title: toTitleCase(restResourceSlug),
|
|
1041
|
+
},
|
|
1042
|
+
});
|
|
1043
|
+
await appendWorkspaceInventoryEntries(workspace.projectDir, {
|
|
1044
|
+
restResourceEntries: [
|
|
1045
|
+
buildRestResourceConfigEntry(restResourceSlug, resolvedNamespace, resolvedMethods),
|
|
1046
|
+
],
|
|
1047
|
+
transformSource: ensureBlockConfigCanAddRestManifests,
|
|
1048
|
+
});
|
|
1049
|
+
return {
|
|
1050
|
+
methods: resolvedMethods,
|
|
1051
|
+
namespace: resolvedNamespace,
|
|
1052
|
+
projectDir: workspace.projectDir,
|
|
1053
|
+
restResourceSlug,
|
|
1054
|
+
};
|
|
1055
|
+
}
|
|
1056
|
+
catch (error) {
|
|
1057
|
+
await rollbackWorkspaceMutation(mutationSnapshot);
|
|
1058
|
+
throw error;
|
|
1059
|
+
}
|
|
1060
|
+
}
|