@wp-typia/project-tools 0.19.1 → 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.
- package/dist/runtime/built-in-block-non-ts-artifacts.js +1 -1039
- package/dist/runtime/built-in-block-non-ts-family-artifacts.d.ts +30 -0
- package/dist/runtime/built-in-block-non-ts-family-artifacts.js +1035 -0
- package/dist/runtime/built-in-block-non-ts-render-utils.d.ts +27 -0
- package/dist/runtime/built-in-block-non-ts-render-utils.js +51 -0
- package/dist/runtime/cli-add-workspace-rest-anchors.d.ts +3 -0
- package/dist/runtime/cli-add-workspace-rest-anchors.js +188 -0
- package/dist/runtime/cli-add-workspace-rest-source-emitters.d.ts +7 -0
- package/dist/runtime/cli-add-workspace-rest-source-emitters.js +379 -0
- package/dist/runtime/cli-add-workspace-rest.js +5 -564
- package/package.json +1 -1
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { BuiltInCodeArtifact } from "./built-in-block-code-artifacts.js";
|
|
2
|
+
import type { ScaffoldTemplateVariables } from "./scaffold.js";
|
|
3
|
+
/**
|
|
4
|
+
* Renders a non-TypeScript artifact and normalizes the output to a trailing newline.
|
|
5
|
+
*
|
|
6
|
+
* @param relativePath Relative output path for the generated artifact.
|
|
7
|
+
* @param template Mustache template source to render.
|
|
8
|
+
* @param view Template view data passed to the Mustache renderer.
|
|
9
|
+
* @returns A built-in code artifact with normalized source text.
|
|
10
|
+
*/
|
|
11
|
+
export declare function renderArtifact(relativePath: string, template: string, view: Record<string, unknown>): BuiltInCodeArtifact;
|
|
12
|
+
/**
|
|
13
|
+
* Builds a PHP entrypoint for a specific alternate render target.
|
|
14
|
+
*
|
|
15
|
+
* @param relativePath Relative output path for the generated PHP file.
|
|
16
|
+
* @param target Alternate render target identifier to dispatch.
|
|
17
|
+
* @param variables Scaffold template variables used to render the entrypoint.
|
|
18
|
+
* @returns A built-in code artifact for the requested alternate render target.
|
|
19
|
+
*/
|
|
20
|
+
export declare function buildAlternateRenderEntryArtifact(relativePath: string, target: "email" | "mjml" | "plain-text" | "web", variables: ScaffoldTemplateVariables): BuiltInCodeArtifact;
|
|
21
|
+
/**
|
|
22
|
+
* Escapes a string for safe embedding in a PHP single-quoted literal.
|
|
23
|
+
*
|
|
24
|
+
* @param value Source string to escape.
|
|
25
|
+
* @returns A PHP single-quoted string literal.
|
|
26
|
+
*/
|
|
27
|
+
export declare function toPhpSingleQuotedString(value: string): string;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { renderMustacheTemplateString } from "./template-render.js";
|
|
2
|
+
/**
|
|
3
|
+
* Renders a non-TypeScript artifact and normalizes the output to a trailing newline.
|
|
4
|
+
*
|
|
5
|
+
* @param relativePath Relative output path for the generated artifact.
|
|
6
|
+
* @param template Mustache template source to render.
|
|
7
|
+
* @param view Template view data passed to the Mustache renderer.
|
|
8
|
+
* @returns A built-in code artifact with normalized source text.
|
|
9
|
+
*/
|
|
10
|
+
export function renderArtifact(relativePath, template, view) {
|
|
11
|
+
const source = renderMustacheTemplateString(template, view);
|
|
12
|
+
return {
|
|
13
|
+
relativePath,
|
|
14
|
+
source: source.endsWith("\n") ? source : `${source}\n`,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Builds a PHP entrypoint for a specific alternate render target.
|
|
19
|
+
*
|
|
20
|
+
* @param relativePath Relative output path for the generated PHP file.
|
|
21
|
+
* @param target Alternate render target identifier to dispatch.
|
|
22
|
+
* @param variables Scaffold template variables used to render the entrypoint.
|
|
23
|
+
* @returns A built-in code artifact for the requested alternate render target.
|
|
24
|
+
*/
|
|
25
|
+
export function buildAlternateRenderEntryArtifact(relativePath, target, variables) {
|
|
26
|
+
const template = `<?php
|
|
27
|
+
/**
|
|
28
|
+
* Alternate ${target} render entry for {{title}}.
|
|
29
|
+
*
|
|
30
|
+
* @package {{pascalCase}}
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
if ( ! defined( 'ABSPATH' ) ) {
|
|
34
|
+
\texit;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
require_once __DIR__ . '/render-targets.php';
|
|
38
|
+
|
|
39
|
+
return {{phpPrefix}}_{{slugSnakeCase}}_render_target( '${target}', $attributes, $content ?? '', $block ?? null );
|
|
40
|
+
`;
|
|
41
|
+
return renderArtifact(relativePath, template, variables);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Escapes a string for safe embedding in a PHP single-quoted literal.
|
|
45
|
+
*
|
|
46
|
+
* @param value Source string to escape.
|
|
47
|
+
* @returns A PHP single-quoted string literal.
|
|
48
|
+
*/
|
|
49
|
+
export function toPhpSingleQuotedString(value) {
|
|
50
|
+
return `'${value.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
|
|
51
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { WorkspaceProject } from "./workspace-project.js";
|
|
2
|
+
export declare function ensureRestResourceBootstrapAnchors(workspace: WorkspaceProject): Promise<void>;
|
|
3
|
+
export declare function ensureRestResourceSyncScriptAnchors(workspace: WorkspaceProject): Promise<void>;
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { getWorkspaceBootstrapPath, patchFile, } from "./cli-add-shared.js";
|
|
3
|
+
const REST_RESOURCE_SERVER_GLOB = "/inc/rest/*.php";
|
|
4
|
+
function escapeRegex(value) {
|
|
5
|
+
return value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
6
|
+
}
|
|
7
|
+
export async function ensureRestResourceBootstrapAnchors(workspace) {
|
|
8
|
+
const bootstrapPath = getWorkspaceBootstrapPath(workspace);
|
|
9
|
+
await patchFile(bootstrapPath, (source) => {
|
|
10
|
+
let nextSource = source;
|
|
11
|
+
const registerFunctionName = `${workspace.workspace.phpPrefix}_register_rest_resources`;
|
|
12
|
+
const registerHook = `add_action( 'init', '${registerFunctionName}', 20 );`;
|
|
13
|
+
const registerFunction = `
|
|
14
|
+
|
|
15
|
+
function ${registerFunctionName}() {
|
|
16
|
+
\tforeach ( glob( __DIR__ . '${REST_RESOURCE_SERVER_GLOB}' ) ?: array() as $rest_resource_module ) {
|
|
17
|
+
\t\trequire_once $rest_resource_module;
|
|
18
|
+
\t}
|
|
19
|
+
}
|
|
20
|
+
`;
|
|
21
|
+
const insertionAnchors = [
|
|
22
|
+
/add_action\(\s*["']init["']\s*,\s*["'][^"']+_load_textdomain["']\s*\);\s*\n/u,
|
|
23
|
+
/\?>\s*$/u,
|
|
24
|
+
];
|
|
25
|
+
const hasPhpFunctionDefinition = (functionName) => new RegExp(`function\\s+${escapeRegex(functionName)}\\s*\\(`, "u").test(nextSource);
|
|
26
|
+
const insertPhpSnippet = (snippet) => {
|
|
27
|
+
for (const anchor of insertionAnchors) {
|
|
28
|
+
const candidate = nextSource.replace(anchor, (match) => `${snippet}\n${match}`);
|
|
29
|
+
if (candidate !== nextSource) {
|
|
30
|
+
nextSource = candidate;
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
nextSource = `${nextSource.trimEnd()}\n${snippet}\n`;
|
|
35
|
+
};
|
|
36
|
+
const appendPhpSnippet = (snippet) => {
|
|
37
|
+
const closingTagPattern = /\?>\s*$/u;
|
|
38
|
+
if (closingTagPattern.test(nextSource)) {
|
|
39
|
+
nextSource = nextSource.replace(closingTagPattern, `${snippet}\n?>`);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
nextSource = `${nextSource.trimEnd()}\n${snippet}\n`;
|
|
43
|
+
};
|
|
44
|
+
if (!hasPhpFunctionDefinition(registerFunctionName)) {
|
|
45
|
+
insertPhpSnippet(registerFunction);
|
|
46
|
+
}
|
|
47
|
+
else if (!nextSource.includes(REST_RESOURCE_SERVER_GLOB)) {
|
|
48
|
+
throw new Error([
|
|
49
|
+
`Unable to patch ${path.basename(bootstrapPath)} in ensureRestResourceBootstrapAnchors.`,
|
|
50
|
+
`The existing ${registerFunctionName}() definition does not include ${REST_RESOURCE_SERVER_GLOB}.`,
|
|
51
|
+
"Restore the generated bootstrap shape or wire the REST resource loader manually before retrying.",
|
|
52
|
+
].join(" "));
|
|
53
|
+
}
|
|
54
|
+
if (!nextSource.includes(registerHook)) {
|
|
55
|
+
appendPhpSnippet(registerHook);
|
|
56
|
+
}
|
|
57
|
+
return nextSource;
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
function assertSyncRestAnchor(nextSource, target, anchorDescription, hasAnchor, syncRestScriptPath) {
|
|
61
|
+
if (!nextSource.includes(target) && !hasAnchor) {
|
|
62
|
+
throw new Error([
|
|
63
|
+
`ensureRestResourceSyncScriptAnchors could not patch ${path.basename(syncRestScriptPath)}.`,
|
|
64
|
+
`Missing expected ${anchorDescription} anchor in scripts/sync-rest-contracts.ts.`,
|
|
65
|
+
"Restore the generated template or add the REST_RESOURCES wiring manually before retrying.",
|
|
66
|
+
].join(" "));
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function replaceRequiredSyncRestSource(nextSource, target, anchor, replacement, anchorDescription, syncRestScriptPath) {
|
|
70
|
+
if (nextSource.includes(target)) {
|
|
71
|
+
return nextSource;
|
|
72
|
+
}
|
|
73
|
+
const hasAnchor = typeof anchor === "string" ? nextSource.includes(anchor) : anchor.test(nextSource);
|
|
74
|
+
assertSyncRestAnchor(nextSource, target, anchorDescription, hasAnchor, syncRestScriptPath);
|
|
75
|
+
return nextSource.replace(anchor, replacement);
|
|
76
|
+
}
|
|
77
|
+
export async function ensureRestResourceSyncScriptAnchors(workspace) {
|
|
78
|
+
const syncRestScriptPath = path.join(workspace.projectDir, "scripts", "sync-rest-contracts.ts");
|
|
79
|
+
await patchFile(syncRestScriptPath, (source) => {
|
|
80
|
+
let nextSource = source;
|
|
81
|
+
const importAnchor = "import { BLOCKS, type WorkspaceBlockConfig } from './block-config';";
|
|
82
|
+
const helperInsertionAnchor = "async function assertTypeArtifactsCurrent";
|
|
83
|
+
const restBlocksAnchor = "const restBlocks = BLOCKS.filter( isRestEnabledBlock );";
|
|
84
|
+
const noResourcesPattern = /if \( restBlocks.length === 0 \) \{[\s\S]*?\n\t\treturn;\n\t\}/u;
|
|
85
|
+
const consoleLogPattern = /\n\tconsole\.log\(\n\t\toptions\.check/u;
|
|
86
|
+
nextSource = replaceRequiredSyncRestSource(nextSource, "REST_RESOURCES", importAnchor, [
|
|
87
|
+
"import {",
|
|
88
|
+
"\tBLOCKS,",
|
|
89
|
+
"\tREST_RESOURCES,",
|
|
90
|
+
"\ttype WorkspaceBlockConfig,",
|
|
91
|
+
"\ttype WorkspaceRestResourceConfig,",
|
|
92
|
+
"} from './block-config';",
|
|
93
|
+
].join("\n"), "BLOCKS import", syncRestScriptPath);
|
|
94
|
+
nextSource = replaceRequiredSyncRestSource(nextSource, "function isWorkspaceRestResource(", helperInsertionAnchor, [
|
|
95
|
+
"function isWorkspaceRestResource(",
|
|
96
|
+
"\tresource: WorkspaceRestResourceConfig",
|
|
97
|
+
"): resource is WorkspaceRestResourceConfig & {",
|
|
98
|
+
"\tclientFile: string;",
|
|
99
|
+
"\topenApiFile: string;",
|
|
100
|
+
"\trestManifest: NonNullable< WorkspaceRestResourceConfig[ 'restManifest' ] >;",
|
|
101
|
+
"\ttypesFile: string;",
|
|
102
|
+
"\tvalidatorsFile: string;",
|
|
103
|
+
"} {",
|
|
104
|
+
"\treturn (",
|
|
105
|
+
"\t\ttypeof resource.clientFile === 'string' &&",
|
|
106
|
+
"\t\ttypeof resource.openApiFile === 'string' &&",
|
|
107
|
+
"\t\ttypeof resource.typesFile === 'string' &&",
|
|
108
|
+
"\t\ttypeof resource.validatorsFile === 'string' &&",
|
|
109
|
+
"\t\ttypeof resource.restManifest === 'object' &&",
|
|
110
|
+
"\t\tresource.restManifest !== null",
|
|
111
|
+
"\t);",
|
|
112
|
+
"}",
|
|
113
|
+
"",
|
|
114
|
+
"async function assertTypeArtifactsCurrent",
|
|
115
|
+
].join("\n"), "type artifact assertion helper", syncRestScriptPath);
|
|
116
|
+
nextSource = replaceRequiredSyncRestSource(nextSource, "const restResources = REST_RESOURCES.filter( isWorkspaceRestResource );", restBlocksAnchor, [
|
|
117
|
+
"const restBlocks = BLOCKS.filter( isRestEnabledBlock );",
|
|
118
|
+
"const restResources = REST_RESOURCES.filter( isWorkspaceRestResource );",
|
|
119
|
+
].join("\n"), "restBlocks filter", syncRestScriptPath);
|
|
120
|
+
nextSource = replaceRequiredSyncRestSource(nextSource, "restBlocks.length === 0 && restResources.length === 0", noResourcesPattern, [
|
|
121
|
+
"if ( restBlocks.length === 0 && restResources.length === 0 ) {",
|
|
122
|
+
"\t\tconsole.log(",
|
|
123
|
+
"\t\t\toptions.check",
|
|
124
|
+
"\t\t\t\t? 'ℹ️ No REST-enabled workspace blocks or plugin-level REST resources are registered yet. `sync-rest --check` is already clean.'",
|
|
125
|
+
"\t\t\t\t: 'ℹ️ No REST-enabled workspace blocks or plugin-level REST resources are registered yet.'",
|
|
126
|
+
"\t\t);",
|
|
127
|
+
"\t\treturn;",
|
|
128
|
+
"\t}",
|
|
129
|
+
].join("\n"), "no-resources guard", syncRestScriptPath);
|
|
130
|
+
nextSource = replaceRequiredSyncRestSource(nextSource, "for ( const resource of restResources ) {", consoleLogPattern, [
|
|
131
|
+
"",
|
|
132
|
+
"\tfor ( const resource of restResources ) {",
|
|
133
|
+
"\t\tconst contracts = resource.restManifest.contracts;",
|
|
134
|
+
"",
|
|
135
|
+
"\t\tfor ( const [ baseName, contract ] of Object.entries( contracts ) ) {",
|
|
136
|
+
"\t\t\tawait syncTypeSchemas(",
|
|
137
|
+
"\t\t\t\t{",
|
|
138
|
+
"\t\t\t\t\tjsonSchemaFile: path.join(",
|
|
139
|
+
"\t\t\t\t\t\tpath.dirname( resource.typesFile ),",
|
|
140
|
+
"\t\t\t\t\t\t'api-schemas',",
|
|
141
|
+
"\t\t\t\t\t\t`${ baseName }.schema.json`",
|
|
142
|
+
"\t\t\t\t\t),",
|
|
143
|
+
"\t\t\t\t\topenApiFile: path.join(",
|
|
144
|
+
"\t\t\t\t\t\tpath.dirname( resource.typesFile ),",
|
|
145
|
+
"\t\t\t\t\t\t'api-schemas',",
|
|
146
|
+
"\t\t\t\t\t\t`${ baseName }.openapi.json`",
|
|
147
|
+
"\t\t\t\t\t),",
|
|
148
|
+
"\t\t\t\t\tsourceTypeName: contract.sourceTypeName,",
|
|
149
|
+
"\t\t\t\t\ttypesFile: resource.typesFile,",
|
|
150
|
+
"\t\t\t\t},",
|
|
151
|
+
"\t\t\t\t{",
|
|
152
|
+
"\t\t\t\t\tcheck: options.check,",
|
|
153
|
+
"\t\t\t\t}",
|
|
154
|
+
"\t\t\t);",
|
|
155
|
+
"\t\t}",
|
|
156
|
+
"",
|
|
157
|
+
"\t\tawait syncRestOpenApi(",
|
|
158
|
+
"\t\t\t{",
|
|
159
|
+
"\t\t\t\tmanifest: resource.restManifest,",
|
|
160
|
+
"\t\t\t\topenApiFile: resource.openApiFile,",
|
|
161
|
+
"\t\t\t\ttypesFile: resource.typesFile,",
|
|
162
|
+
"\t\t\t},",
|
|
163
|
+
"\t\t\t{",
|
|
164
|
+
"\t\t\t\tcheck: options.check,",
|
|
165
|
+
"\t\t\t}",
|
|
166
|
+
"\t\t);",
|
|
167
|
+
"",
|
|
168
|
+
"\t\tawait syncEndpointClient(",
|
|
169
|
+
"\t\t\t{",
|
|
170
|
+
"\t\t\t\tclientFile: resource.clientFile,",
|
|
171
|
+
"\t\t\t\tmanifest: resource.restManifest,",
|
|
172
|
+
"\t\t\t\ttypesFile: resource.typesFile,",
|
|
173
|
+
"\t\t\t\tvalidatorsFile: resource.validatorsFile,",
|
|
174
|
+
"\t\t\t},",
|
|
175
|
+
"\t\t\t{",
|
|
176
|
+
"\t\t\t\tcheck: options.check,",
|
|
177
|
+
"\t\t\t}",
|
|
178
|
+
"\t\t);",
|
|
179
|
+
"\t}",
|
|
180
|
+
"",
|
|
181
|
+
"\tconsole.log(",
|
|
182
|
+
"\t\toptions.check",
|
|
183
|
+
].join("\n"), "success log insertion point", syncRestScriptPath);
|
|
184
|
+
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!");
|
|
185
|
+
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!");
|
|
186
|
+
return nextSource;
|
|
187
|
+
});
|
|
188
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type RestResourceMethodId } from "./cli-add-shared.js";
|
|
2
|
+
export declare function toPascalCaseFromSlug(slug: string): string;
|
|
3
|
+
export declare function buildRestResourceConfigEntry(restResourceSlug: string, namespace: string, methods: RestResourceMethodId[]): string;
|
|
4
|
+
export declare function buildRestResourceTypesSource(restResourceSlug: string, methods: RestResourceMethodId[]): string;
|
|
5
|
+
export declare function buildRestResourceValidatorsSource(restResourceSlug: string, methods: RestResourceMethodId[]): string;
|
|
6
|
+
export declare function buildRestResourceApiSource(restResourceSlug: string, methods: RestResourceMethodId[]): string;
|
|
7
|
+
export declare function buildRestResourceDataSource(restResourceSlug: string, methods: RestResourceMethodId[]): string;
|
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
import { normalizeBlockSlug, quoteTsString, } from "./cli-add-shared.js";
|
|
2
|
+
import { buildRestResourceEndpointManifest } from "./rest-resource-artifacts.js";
|
|
3
|
+
import { toTitleCase } from "./string-case.js";
|
|
4
|
+
export function toPascalCaseFromSlug(slug) {
|
|
5
|
+
return normalizeBlockSlug(slug)
|
|
6
|
+
.split("-")
|
|
7
|
+
.filter(Boolean)
|
|
8
|
+
.map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1))
|
|
9
|
+
.join("");
|
|
10
|
+
}
|
|
11
|
+
function indentMultiline(source, prefix) {
|
|
12
|
+
return source
|
|
13
|
+
.split("\n")
|
|
14
|
+
.map((line) => `${prefix}${line}`)
|
|
15
|
+
.join("\n");
|
|
16
|
+
}
|
|
17
|
+
export function buildRestResourceConfigEntry(restResourceSlug, namespace, methods) {
|
|
18
|
+
const pascalCase = toPascalCaseFromSlug(restResourceSlug);
|
|
19
|
+
const title = toTitleCase(restResourceSlug);
|
|
20
|
+
const manifest = buildRestResourceEndpointManifest({
|
|
21
|
+
namespace,
|
|
22
|
+
pascalCase,
|
|
23
|
+
slugKebabCase: restResourceSlug,
|
|
24
|
+
title,
|
|
25
|
+
}, methods);
|
|
26
|
+
return [
|
|
27
|
+
"\t{",
|
|
28
|
+
`\t\tapiFile: ${quoteTsString(`src/rest/${restResourceSlug}/api.ts`)},`,
|
|
29
|
+
`\t\tclientFile: ${quoteTsString(`src/rest/${restResourceSlug}/api-client.ts`)},`,
|
|
30
|
+
`\t\tdataFile: ${quoteTsString(`src/rest/${restResourceSlug}/data.ts`)},`,
|
|
31
|
+
`\t\tmethods: [ ${methods.map((method) => quoteTsString(method)).join(", ")} ],`,
|
|
32
|
+
`\t\tnamespace: ${quoteTsString(namespace)},`,
|
|
33
|
+
`\t\topenApiFile: ${quoteTsString(`src/rest/${restResourceSlug}/api.openapi.json`)},`,
|
|
34
|
+
`\t\tphpFile: ${quoteTsString(`inc/rest/${restResourceSlug}.php`)},`,
|
|
35
|
+
"\t\trestManifest: defineEndpointManifest(",
|
|
36
|
+
indentMultiline(JSON.stringify(manifest, null, "\t"), "\t\t\t"),
|
|
37
|
+
"\t\t),",
|
|
38
|
+
`\t\tslug: ${quoteTsString(restResourceSlug)},`,
|
|
39
|
+
`\t\ttypesFile: ${quoteTsString(`src/rest/${restResourceSlug}/api-types.ts`)},`,
|
|
40
|
+
`\t\tvalidatorsFile: ${quoteTsString(`src/rest/${restResourceSlug}/api-validators.ts`)},`,
|
|
41
|
+
"\t},",
|
|
42
|
+
].join("\n");
|
|
43
|
+
}
|
|
44
|
+
export function buildRestResourceTypesSource(restResourceSlug, methods) {
|
|
45
|
+
const pascalCase = toPascalCaseFromSlug(restResourceSlug);
|
|
46
|
+
const lines = [
|
|
47
|
+
"import { tags } from 'typia';",
|
|
48
|
+
"",
|
|
49
|
+
`export type ${pascalCase}Status = 'draft' | 'published';`,
|
|
50
|
+
"",
|
|
51
|
+
`export interface ${pascalCase}Record {`,
|
|
52
|
+
"\tid: number & tags.Type< 'uint32' >;",
|
|
53
|
+
"\ttitle: string & tags.MinLength< 1 > & tags.MaxLength< 120 >;",
|
|
54
|
+
"\tcontent?: string & tags.MaxLength< 2000 >;",
|
|
55
|
+
`\tstatus: ${pascalCase}Status;`,
|
|
56
|
+
"\tupdatedAt: string;",
|
|
57
|
+
"}",
|
|
58
|
+
];
|
|
59
|
+
if (methods.includes("list")) {
|
|
60
|
+
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' >;", "}");
|
|
61
|
+
}
|
|
62
|
+
if (methods.includes("read")) {
|
|
63
|
+
lines.push("", `export interface ${pascalCase}ReadQuery {`, "\tid: number & tags.Type< 'uint32' >;", "}", "", `export type ${pascalCase}ReadResponse = ${pascalCase}Record;`);
|
|
64
|
+
}
|
|
65
|
+
if (methods.includes("create")) {
|
|
66
|
+
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;`);
|
|
67
|
+
}
|
|
68
|
+
if (methods.includes("update")) {
|
|
69
|
+
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;`);
|
|
70
|
+
}
|
|
71
|
+
if (methods.includes("delete")) {
|
|
72
|
+
lines.push("", `export interface ${pascalCase}DeleteQuery {`, "\tid: number & tags.Type< 'uint32' >;", "}", "", `export interface ${pascalCase}DeleteResponse {`, "\tdeleted: true;", "\tid: number & tags.Type< 'uint32' >;", "}");
|
|
73
|
+
}
|
|
74
|
+
return `${lines.join("\n")}\n`;
|
|
75
|
+
}
|
|
76
|
+
export function buildRestResourceValidatorsSource(restResourceSlug, methods) {
|
|
77
|
+
const pascalCase = toPascalCaseFromSlug(restResourceSlug);
|
|
78
|
+
const importedTypes = new Set();
|
|
79
|
+
const validatorDeclarations = [];
|
|
80
|
+
const validatorEntries = [];
|
|
81
|
+
const addValidator = (propertyName, typeName, validateIdentifier) => {
|
|
82
|
+
importedTypes.add(typeName);
|
|
83
|
+
validatorDeclarations.push(`const ${validateIdentifier} = typia.createValidate< ${typeName} >();`);
|
|
84
|
+
validatorEntries.push(`\t${propertyName}: ( input: unknown ) => toValidationResult< ${typeName} >( ${validateIdentifier}( input ) ),`);
|
|
85
|
+
};
|
|
86
|
+
if (methods.includes("list")) {
|
|
87
|
+
addValidator("listQuery", `${pascalCase}ListQuery`, "validateListQuery");
|
|
88
|
+
addValidator("listResponse", `${pascalCase}ListResponse`, "validateListResponse");
|
|
89
|
+
}
|
|
90
|
+
if (methods.includes("read")) {
|
|
91
|
+
addValidator("readQuery", `${pascalCase}ReadQuery`, "validateReadQuery");
|
|
92
|
+
addValidator("readResponse", `${pascalCase}ReadResponse`, "validateReadResponse");
|
|
93
|
+
}
|
|
94
|
+
if (methods.includes("create")) {
|
|
95
|
+
addValidator("createRequest", `${pascalCase}CreateRequest`, "validateCreateRequest");
|
|
96
|
+
addValidator("createResponse", `${pascalCase}CreateResponse`, "validateCreateResponse");
|
|
97
|
+
}
|
|
98
|
+
if (methods.includes("update")) {
|
|
99
|
+
addValidator("updateQuery", `${pascalCase}UpdateQuery`, "validateUpdateQuery");
|
|
100
|
+
addValidator("updateRequest", `${pascalCase}UpdateRequest`, "validateUpdateRequest");
|
|
101
|
+
addValidator("updateResponse", `${pascalCase}UpdateResponse`, "validateUpdateResponse");
|
|
102
|
+
}
|
|
103
|
+
if (methods.includes("delete")) {
|
|
104
|
+
addValidator("deleteQuery", `${pascalCase}DeleteQuery`, "validateDeleteQuery");
|
|
105
|
+
addValidator("deleteResponse", `${pascalCase}DeleteResponse`, "validateDeleteResponse");
|
|
106
|
+
}
|
|
107
|
+
return `import typia from 'typia';
|
|
108
|
+
|
|
109
|
+
import { toValidationResult } from '@wp-typia/rest';
|
|
110
|
+
import type {
|
|
111
|
+
\t${Array.from(importedTypes).sort().join(",\n\t")},
|
|
112
|
+
} from './api-types';
|
|
113
|
+
|
|
114
|
+
${validatorDeclarations.join("\n")}
|
|
115
|
+
|
|
116
|
+
export const apiValidators = {
|
|
117
|
+
${validatorEntries.join("\n")}
|
|
118
|
+
};
|
|
119
|
+
`;
|
|
120
|
+
}
|
|
121
|
+
export function buildRestResourceApiSource(restResourceSlug, methods) {
|
|
122
|
+
const pascalCase = toPascalCaseFromSlug(restResourceSlug);
|
|
123
|
+
const typeImports = new Set();
|
|
124
|
+
const clientEndpointImports = [];
|
|
125
|
+
const exportedBindings = [];
|
|
126
|
+
const writeMethods = methods.filter((method) => ["create", "update", "delete"].includes(method));
|
|
127
|
+
if (methods.includes("list")) {
|
|
128
|
+
typeImports.add(`${pascalCase}ListQuery`);
|
|
129
|
+
clientEndpointImports.push(`list${pascalCase}ResourcesEndpoint`);
|
|
130
|
+
exportedBindings.push(`export const restResourceListEndpoint = {
|
|
131
|
+
\t...list${pascalCase}ResourcesEndpoint,
|
|
132
|
+
\tbuildRequestOptions: () => ( {
|
|
133
|
+
\t\turl: resolveRestRouteUrl( list${pascalCase}ResourcesEndpoint.path ),
|
|
134
|
+
\t} ),
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
export function listResource( request: ${pascalCase}ListQuery ) {
|
|
138
|
+
\treturn callEndpoint( restResourceListEndpoint, request );
|
|
139
|
+
}`);
|
|
140
|
+
}
|
|
141
|
+
if (methods.includes("read")) {
|
|
142
|
+
typeImports.add(`${pascalCase}ReadQuery`);
|
|
143
|
+
clientEndpointImports.push(`read${pascalCase}ResourceEndpoint`);
|
|
144
|
+
exportedBindings.push(`export const restResourceReadEndpoint = {
|
|
145
|
+
\t...read${pascalCase}ResourceEndpoint,
|
|
146
|
+
\tbuildRequestOptions: () => ( {
|
|
147
|
+
\t\turl: resolveRestRouteUrl( read${pascalCase}ResourceEndpoint.path ),
|
|
148
|
+
\t} ),
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
export function readResource( request: ${pascalCase}ReadQuery ) {
|
|
152
|
+
\treturn callEndpoint( restResourceReadEndpoint, request );
|
|
153
|
+
}`);
|
|
154
|
+
}
|
|
155
|
+
if (methods.includes("create")) {
|
|
156
|
+
typeImports.add(`${pascalCase}CreateRequest`);
|
|
157
|
+
clientEndpointImports.push(`create${pascalCase}ResourceEndpoint`);
|
|
158
|
+
exportedBindings.push(`export const restResourceCreateEndpoint = {
|
|
159
|
+
\t...create${pascalCase}ResourceEndpoint,
|
|
160
|
+
\tbuildRequestOptions: () => {
|
|
161
|
+
\t\tconst nonce = resolveRestNonce();
|
|
162
|
+
\t\treturn {
|
|
163
|
+
\t\t\theaders: nonce
|
|
164
|
+
\t\t\t\t? {
|
|
165
|
+
\t\t\t\t\t'X-WP-Nonce': nonce,
|
|
166
|
+
\t\t\t\t}
|
|
167
|
+
\t\t\t\t: undefined,
|
|
168
|
+
\t\t\turl: resolveRestRouteUrl( create${pascalCase}ResourceEndpoint.path ),
|
|
169
|
+
\t\t};
|
|
170
|
+
\t},
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
export function createResource( request: ${pascalCase}CreateRequest ) {
|
|
174
|
+
\treturn callEndpoint( restResourceCreateEndpoint, request );
|
|
175
|
+
}`);
|
|
176
|
+
}
|
|
177
|
+
if (methods.includes("update")) {
|
|
178
|
+
typeImports.add(`${pascalCase}UpdateQuery`);
|
|
179
|
+
typeImports.add(`${pascalCase}UpdateRequest`);
|
|
180
|
+
clientEndpointImports.push(`update${pascalCase}ResourceEndpoint`);
|
|
181
|
+
exportedBindings.push(`export const restResourceUpdateEndpoint = {
|
|
182
|
+
\t...update${pascalCase}ResourceEndpoint,
|
|
183
|
+
\tbuildRequestOptions: () => {
|
|
184
|
+
\t\tconst nonce = resolveRestNonce();
|
|
185
|
+
\t\treturn {
|
|
186
|
+
\t\t\theaders: nonce
|
|
187
|
+
\t\t\t\t? {
|
|
188
|
+
\t\t\t\t\t'X-WP-Nonce': nonce,
|
|
189
|
+
\t\t\t\t}
|
|
190
|
+
\t\t\t\t: undefined,
|
|
191
|
+
\t\t\turl: resolveRestRouteUrl( update${pascalCase}ResourceEndpoint.path ),
|
|
192
|
+
\t\t};
|
|
193
|
+
\t},
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
export function updateResource( request: {
|
|
197
|
+
\tbody: ${pascalCase}UpdateRequest;
|
|
198
|
+
\tquery: ${pascalCase}UpdateQuery;
|
|
199
|
+
} ) {
|
|
200
|
+
\treturn callEndpoint( restResourceUpdateEndpoint, request );
|
|
201
|
+
}`);
|
|
202
|
+
}
|
|
203
|
+
if (methods.includes("delete")) {
|
|
204
|
+
typeImports.add(`${pascalCase}DeleteQuery`);
|
|
205
|
+
clientEndpointImports.push(`delete${pascalCase}ResourceEndpoint`);
|
|
206
|
+
exportedBindings.push(`export const restResourceDeleteEndpoint = {
|
|
207
|
+
\t...delete${pascalCase}ResourceEndpoint,
|
|
208
|
+
\tbuildRequestOptions: () => {
|
|
209
|
+
\t\tconst nonce = resolveRestNonce();
|
|
210
|
+
\t\treturn {
|
|
211
|
+
\t\t\theaders: nonce
|
|
212
|
+
\t\t\t\t? {
|
|
213
|
+
\t\t\t\t\t'X-WP-Nonce': nonce,
|
|
214
|
+
\t\t\t\t}
|
|
215
|
+
\t\t\t\t: undefined,
|
|
216
|
+
\t\t\turl: resolveRestRouteUrl( delete${pascalCase}ResourceEndpoint.path ),
|
|
217
|
+
\t\t};
|
|
218
|
+
\t},
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
export function deleteResource( request: ${pascalCase}DeleteQuery ) {
|
|
222
|
+
\treturn callEndpoint( restResourceDeleteEndpoint, request );
|
|
223
|
+
}`);
|
|
224
|
+
}
|
|
225
|
+
const resolveRestNonceSource = writeMethods.length > 0
|
|
226
|
+
? `function resolveRestNonce( fallback?: string ): string | undefined {
|
|
227
|
+
\tif ( typeof fallback === 'string' && fallback.length > 0 ) {
|
|
228
|
+
\t\treturn fallback;
|
|
229
|
+
\t}
|
|
230
|
+
|
|
231
|
+
\tif ( typeof window === 'undefined' ) {
|
|
232
|
+
\t\treturn undefined;
|
|
233
|
+
\t}
|
|
234
|
+
|
|
235
|
+
\tconst wpApiSettings = (
|
|
236
|
+
\t\twindow as typeof window & {
|
|
237
|
+
\t\t\twpApiSettings?: { nonce?: string };
|
|
238
|
+
\t\t}
|
|
239
|
+
\t).wpApiSettings;
|
|
240
|
+
|
|
241
|
+
\treturn typeof wpApiSettings?.nonce === 'string' &&
|
|
242
|
+
\t\twpApiSettings.nonce.length > 0
|
|
243
|
+
\t\t? wpApiSettings.nonce
|
|
244
|
+
\t\t: undefined;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
`
|
|
248
|
+
: "";
|
|
249
|
+
return `import {
|
|
250
|
+
\tcallEndpoint,
|
|
251
|
+
\tresolveRestRouteUrl,
|
|
252
|
+
} from '@wp-typia/rest';
|
|
253
|
+
|
|
254
|
+
import type {
|
|
255
|
+
\t${Array.from(typeImports).sort().join(",\n\t")},
|
|
256
|
+
} from './api-types';
|
|
257
|
+
import {
|
|
258
|
+
\t${clientEndpointImports.sort().join(",\n\t")},
|
|
259
|
+
} from './api-client';
|
|
260
|
+
${resolveRestNonceSource}
|
|
261
|
+
${exportedBindings.join("\n\n")}
|
|
262
|
+
`;
|
|
263
|
+
}
|
|
264
|
+
export function buildRestResourceDataSource(restResourceSlug, methods) {
|
|
265
|
+
const pascalCase = toPascalCaseFromSlug(restResourceSlug);
|
|
266
|
+
const typeImports = new Set();
|
|
267
|
+
const endpointImports = [];
|
|
268
|
+
const exportedBindings = [];
|
|
269
|
+
if (methods.includes("list")) {
|
|
270
|
+
typeImports.add(`${pascalCase}ListQuery`);
|
|
271
|
+
typeImports.add(`${pascalCase}ListResponse`);
|
|
272
|
+
endpointImports.push("restResourceListEndpoint");
|
|
273
|
+
exportedBindings.push(`export type Use${pascalCase}ListQueryOptions<
|
|
274
|
+
\tSelected = ${pascalCase}ListResponse,
|
|
275
|
+
> = UseEndpointQueryOptions<
|
|
276
|
+
\t${pascalCase}ListQuery,
|
|
277
|
+
\t${pascalCase}ListResponse,
|
|
278
|
+
\tSelected
|
|
279
|
+
>;
|
|
280
|
+
|
|
281
|
+
export function use${pascalCase}ListQuery<
|
|
282
|
+
\tSelected = ${pascalCase}ListResponse,
|
|
283
|
+
>(
|
|
284
|
+
\trequest: ${pascalCase}ListQuery,
|
|
285
|
+
\toptions: Use${pascalCase}ListQueryOptions< Selected > = {}
|
|
286
|
+
) {
|
|
287
|
+
\treturn useEndpointQuery( restResourceListEndpoint, request, options );
|
|
288
|
+
}`);
|
|
289
|
+
}
|
|
290
|
+
if (methods.includes("read")) {
|
|
291
|
+
typeImports.add(`${pascalCase}ReadQuery`);
|
|
292
|
+
typeImports.add(`${pascalCase}ReadResponse`);
|
|
293
|
+
endpointImports.push("restResourceReadEndpoint");
|
|
294
|
+
exportedBindings.push(`export type Use${pascalCase}ReadQueryOptions<
|
|
295
|
+
\tSelected = ${pascalCase}ReadResponse,
|
|
296
|
+
> = UseEndpointQueryOptions<
|
|
297
|
+
\t${pascalCase}ReadQuery,
|
|
298
|
+
\t${pascalCase}ReadResponse,
|
|
299
|
+
\tSelected
|
|
300
|
+
>;
|
|
301
|
+
|
|
302
|
+
export function use${pascalCase}ReadQuery<
|
|
303
|
+
\tSelected = ${pascalCase}ReadResponse,
|
|
304
|
+
>(
|
|
305
|
+
\trequest: ${pascalCase}ReadQuery,
|
|
306
|
+
\toptions: Use${pascalCase}ReadQueryOptions< Selected > = {}
|
|
307
|
+
) {
|
|
308
|
+
\treturn useEndpointQuery( restResourceReadEndpoint, request, options );
|
|
309
|
+
}`);
|
|
310
|
+
}
|
|
311
|
+
if (methods.includes("create")) {
|
|
312
|
+
typeImports.add(`${pascalCase}CreateRequest`);
|
|
313
|
+
typeImports.add(`${pascalCase}CreateResponse`);
|
|
314
|
+
endpointImports.push("restResourceCreateEndpoint");
|
|
315
|
+
exportedBindings.push(`export type UseCreate${pascalCase}ResourceMutationOptions = UseEndpointMutationOptions<
|
|
316
|
+
\t${pascalCase}CreateRequest,
|
|
317
|
+
\t${pascalCase}CreateResponse,
|
|
318
|
+
\tunknown
|
|
319
|
+
>;
|
|
320
|
+
|
|
321
|
+
export function useCreate${pascalCase}ResourceMutation(
|
|
322
|
+
\toptions: UseCreate${pascalCase}ResourceMutationOptions = {}
|
|
323
|
+
) {
|
|
324
|
+
\treturn useEndpointMutation( restResourceCreateEndpoint, options );
|
|
325
|
+
}`);
|
|
326
|
+
}
|
|
327
|
+
if (methods.includes("update")) {
|
|
328
|
+
typeImports.add(`${pascalCase}UpdateQuery`);
|
|
329
|
+
typeImports.add(`${pascalCase}UpdateRequest`);
|
|
330
|
+
typeImports.add(`${pascalCase}UpdateResponse`);
|
|
331
|
+
endpointImports.push("restResourceUpdateEndpoint");
|
|
332
|
+
exportedBindings.push(`export type UseUpdate${pascalCase}ResourceMutationOptions = UseEndpointMutationOptions<
|
|
333
|
+
\t{
|
|
334
|
+
\t\tbody: ${pascalCase}UpdateRequest;
|
|
335
|
+
\t\tquery: ${pascalCase}UpdateQuery;
|
|
336
|
+
\t},
|
|
337
|
+
\t${pascalCase}UpdateResponse,
|
|
338
|
+
\tunknown
|
|
339
|
+
>;
|
|
340
|
+
|
|
341
|
+
export function useUpdate${pascalCase}ResourceMutation(
|
|
342
|
+
\toptions: UseUpdate${pascalCase}ResourceMutationOptions = {}
|
|
343
|
+
) {
|
|
344
|
+
\treturn useEndpointMutation( restResourceUpdateEndpoint, options );
|
|
345
|
+
}`);
|
|
346
|
+
}
|
|
347
|
+
if (methods.includes("delete")) {
|
|
348
|
+
typeImports.add(`${pascalCase}DeleteQuery`);
|
|
349
|
+
typeImports.add(`${pascalCase}DeleteResponse`);
|
|
350
|
+
endpointImports.push("restResourceDeleteEndpoint");
|
|
351
|
+
exportedBindings.push(`export type UseDelete${pascalCase}ResourceMutationOptions = UseEndpointMutationOptions<
|
|
352
|
+
\t${pascalCase}DeleteQuery,
|
|
353
|
+
\t${pascalCase}DeleteResponse,
|
|
354
|
+
\tunknown
|
|
355
|
+
>;
|
|
356
|
+
|
|
357
|
+
export function useDelete${pascalCase}ResourceMutation(
|
|
358
|
+
\toptions: UseDelete${pascalCase}ResourceMutationOptions = {}
|
|
359
|
+
) {
|
|
360
|
+
\treturn useEndpointMutation( restResourceDeleteEndpoint, options );
|
|
361
|
+
}`);
|
|
362
|
+
}
|
|
363
|
+
return `import {
|
|
364
|
+
\tuseEndpointMutation,
|
|
365
|
+
\tuseEndpointQuery,
|
|
366
|
+
\ttype UseEndpointMutationOptions,
|
|
367
|
+
\ttype UseEndpointQueryOptions,
|
|
368
|
+
} from '@wp-typia/rest/react';
|
|
369
|
+
|
|
370
|
+
import type {
|
|
371
|
+
\t${Array.from(typeImports).sort().join(",\n\t")},
|
|
372
|
+
} from './api-types';
|
|
373
|
+
import {
|
|
374
|
+
\t${endpointImports.sort().join(",\n\t")},
|
|
375
|
+
} from './api';
|
|
376
|
+
|
|
377
|
+
${exportedBindings.join("\n\n")}
|
|
378
|
+
`;
|
|
379
|
+
}
|