@wp-typia/project-tools 0.16.1 → 0.16.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/README.md +6 -0
- package/dist/runtime/package-versions.js +7 -2
- package/dist/runtime/schema-core.d.ts +5 -266
- package/dist/runtime/schema-core.js +5 -775
- package/package.json +56 -3
package/README.md
CHANGED
|
@@ -14,6 +14,12 @@ Supported public imports:
|
|
|
14
14
|
- `@wp-typia/project-tools`
|
|
15
15
|
- `@wp-typia/project-tools/schema-core`
|
|
16
16
|
|
|
17
|
+
Implementation note:
|
|
18
|
+
|
|
19
|
+
- `@wp-typia/project-tools/schema-core` remains the preferred project-tooling
|
|
20
|
+
import path.
|
|
21
|
+
- The shared implementation now lives in `@wp-typia/block-runtime/schema-core`.
|
|
22
|
+
|
|
17
23
|
Example:
|
|
18
24
|
|
|
19
25
|
```ts
|
|
@@ -15,6 +15,9 @@ function normalizeVersionRange(value) {
|
|
|
15
15
|
if (!trimmed) {
|
|
16
16
|
return DEFAULT_VERSION_RANGE;
|
|
17
17
|
}
|
|
18
|
+
if (trimmed.startsWith("workspace:")) {
|
|
19
|
+
return DEFAULT_VERSION_RANGE;
|
|
20
|
+
}
|
|
18
21
|
return /^[~^<>=]/.test(trimmed) ? trimmed : `^${trimmed}`;
|
|
19
22
|
}
|
|
20
23
|
function readPackageManifest(packageJsonPath) {
|
|
@@ -52,11 +55,13 @@ export function getPackageVersions() {
|
|
|
52
55
|
const wpTypiaManifest = readPackageManifest(path.join(PROJECT_TOOLS_PACKAGE_ROOT, "..", "wp-typia", "package.json")) ??
|
|
53
56
|
resolveInstalledPackageManifest("wp-typia") ??
|
|
54
57
|
{};
|
|
58
|
+
const blockRuntimeDependencyVersion = normalizeVersionRange(createManifest.dependencies?.["@wp-typia/block-runtime"]);
|
|
55
59
|
cachedPackageVersions = {
|
|
56
60
|
apiClientPackageVersion: normalizeVersionRange(createManifest.dependencies?.["@wp-typia/api-client"] ??
|
|
57
61
|
resolveInstalledPackageManifest("@wp-typia/api-client")?.version),
|
|
58
|
-
blockRuntimePackageVersion:
|
|
59
|
-
|
|
62
|
+
blockRuntimePackageVersion: blockRuntimeDependencyVersion !== DEFAULT_VERSION_RANGE
|
|
63
|
+
? blockRuntimeDependencyVersion
|
|
64
|
+
: normalizeVersionRange(blockRuntimeManifest.version),
|
|
60
65
|
blockTypesPackageVersion: normalizeVersionRange(createManifest.dependencies?.["@wp-typia/block-types"] ??
|
|
61
66
|
resolveInstalledPackageManifest("@wp-typia/block-types")?.version),
|
|
62
67
|
projectToolsPackageVersion: normalizeVersionRange(createManifest.version),
|
|
@@ -1,268 +1,7 @@
|
|
|
1
|
-
import type { JsonValue, ManifestAttribute, ManifestDocument } from "./migration-types.js";
|
|
2
|
-
export interface JsonSchemaObject {
|
|
3
|
-
[key: string]: JsonValue | JsonSchemaObject | JsonSchemaObject[] | undefined;
|
|
4
|
-
}
|
|
5
1
|
/**
|
|
6
|
-
*
|
|
2
|
+
* Re-exports schema and OpenAPI helpers from `@wp-typia/block-runtime`.
|
|
3
|
+
* This adapter preserves the public `@wp-typia/project-tools/schema-core`
|
|
4
|
+
* path while sharing the canonical implementation with block-runtime.
|
|
5
|
+
* @module
|
|
7
6
|
*/
|
|
8
|
-
export
|
|
9
|
-
$schema: string;
|
|
10
|
-
additionalProperties: boolean;
|
|
11
|
-
properties: Record<string, JsonSchemaObject>;
|
|
12
|
-
required: string[];
|
|
13
|
-
title: string;
|
|
14
|
-
type: "object";
|
|
15
|
-
}
|
|
16
|
-
/**
|
|
17
|
-
* Document-level metadata applied to generated OpenAPI files.
|
|
18
|
-
*/
|
|
19
|
-
export interface OpenApiInfo {
|
|
20
|
-
description?: string;
|
|
21
|
-
title?: string;
|
|
22
|
-
version?: string;
|
|
23
|
-
}
|
|
24
|
-
/**
|
|
25
|
-
* JSON Schema reference used inside generated OpenAPI documents.
|
|
26
|
-
*/
|
|
27
|
-
export interface OpenApiSchemaReference extends JsonSchemaObject {
|
|
28
|
-
$ref: string;
|
|
29
|
-
}
|
|
30
|
-
/**
|
|
31
|
-
* OpenAPI query parameter emitted from a manifest attribute.
|
|
32
|
-
*/
|
|
33
|
-
export interface OpenApiParameter extends JsonSchemaObject {
|
|
34
|
-
in: "query";
|
|
35
|
-
name: string;
|
|
36
|
-
required: boolean;
|
|
37
|
-
schema: JsonSchemaObject;
|
|
38
|
-
}
|
|
39
|
-
/**
|
|
40
|
-
* OpenAPI media type wrapper for JSON responses and request bodies.
|
|
41
|
-
*/
|
|
42
|
-
export interface OpenApiMediaType extends JsonSchemaObject {
|
|
43
|
-
schema: OpenApiSchemaReference;
|
|
44
|
-
}
|
|
45
|
-
/**
|
|
46
|
-
* OpenAPI request body definition for generated JSON endpoints.
|
|
47
|
-
*/
|
|
48
|
-
export interface OpenApiRequestBody extends JsonSchemaObject {
|
|
49
|
-
content: {
|
|
50
|
-
"application/json": OpenApiMediaType;
|
|
51
|
-
};
|
|
52
|
-
required: true;
|
|
53
|
-
}
|
|
54
|
-
/**
|
|
55
|
-
* Successful JSON response entry in the generated OpenAPI document.
|
|
56
|
-
*/
|
|
57
|
-
export interface OpenApiResponse extends JsonSchemaObject {
|
|
58
|
-
content: {
|
|
59
|
-
"application/json": OpenApiMediaType;
|
|
60
|
-
};
|
|
61
|
-
description: string;
|
|
62
|
-
headers?: Record<string, JsonSchemaObject>;
|
|
63
|
-
}
|
|
64
|
-
/**
|
|
65
|
-
* Header-based security scheme used by authenticated WordPress REST routes.
|
|
66
|
-
*/
|
|
67
|
-
export interface OpenApiSecurityScheme extends JsonSchemaObject {
|
|
68
|
-
description?: string;
|
|
69
|
-
in: "header";
|
|
70
|
-
name: string;
|
|
71
|
-
type: "apiKey";
|
|
72
|
-
}
|
|
73
|
-
/**
|
|
74
|
-
* One generated OpenAPI operation for a scaffolded REST endpoint.
|
|
75
|
-
*/
|
|
76
|
-
export interface OpenApiOperation extends JsonSchemaObject {
|
|
77
|
-
operationId: string;
|
|
78
|
-
parameters?: OpenApiParameter[];
|
|
79
|
-
requestBody?: OpenApiRequestBody;
|
|
80
|
-
responses: Record<string, OpenApiResponse>;
|
|
81
|
-
security?: Array<Record<string, string[]>>;
|
|
82
|
-
summary?: string;
|
|
83
|
-
tags: string[];
|
|
84
|
-
"x-typia-authIntent": EndpointAuthIntent;
|
|
85
|
-
"x-wp-typia-authPolicy"?: EndpointOpenApiAuthMode;
|
|
86
|
-
"x-wp-typia-publicTokenField"?: string;
|
|
87
|
-
}
|
|
88
|
-
/**
|
|
89
|
-
* Path item containing one or more generated REST operations.
|
|
90
|
-
*/
|
|
91
|
-
export type OpenApiPathItem = JsonSchemaObject & Partial<Record<Lowercase<EndpointOpenApiMethod>, OpenApiOperation>>;
|
|
92
|
-
/**
|
|
93
|
-
* Named tag entry surfaced at the top level of generated OpenAPI docs.
|
|
94
|
-
*/
|
|
95
|
-
export interface OpenApiTag extends JsonSchemaObject {
|
|
96
|
-
name: string;
|
|
97
|
-
}
|
|
98
|
-
/**
|
|
99
|
-
* OpenAPI component registry for generated schemas and security schemes.
|
|
100
|
-
*/
|
|
101
|
-
export interface OpenApiComponents extends JsonSchemaObject {
|
|
102
|
-
schemas: Record<string, JsonSchemaDocument>;
|
|
103
|
-
securitySchemes?: Record<string, OpenApiSecurityScheme>;
|
|
104
|
-
}
|
|
105
|
-
/**
|
|
106
|
-
* Complete OpenAPI 3.1 document emitted for endpoint-aware REST contracts.
|
|
107
|
-
*/
|
|
108
|
-
export interface OpenApiDocument extends JsonSchemaObject {
|
|
109
|
-
components: OpenApiComponents;
|
|
110
|
-
info: {
|
|
111
|
-
description?: string;
|
|
112
|
-
title: string;
|
|
113
|
-
version: string;
|
|
114
|
-
};
|
|
115
|
-
openapi: "3.1.0";
|
|
116
|
-
paths: Record<string, OpenApiPathItem>;
|
|
117
|
-
tags?: OpenApiTag[];
|
|
118
|
-
}
|
|
119
|
-
/**
|
|
120
|
-
* Backend-neutral auth intent for one manifest-defined endpoint.
|
|
121
|
-
*/
|
|
122
|
-
export type EndpointAuthIntent = "authenticated" | "public" | "public-write-protected";
|
|
123
|
-
/**
|
|
124
|
-
* WordPress-specific authentication mechanisms that can implement neutral auth intent.
|
|
125
|
-
*/
|
|
126
|
-
export type EndpointWordPressAuthMechanism = "public-signed-token" | "rest-nonce";
|
|
127
|
-
/**
|
|
128
|
-
* Optional WordPress adapter metadata that explains how the default runtime satisfies auth intent.
|
|
129
|
-
*/
|
|
130
|
-
export interface EndpointWordPressAuthDefinition {
|
|
131
|
-
mechanism: EndpointWordPressAuthMechanism;
|
|
132
|
-
publicTokenField?: string;
|
|
133
|
-
}
|
|
134
|
-
/**
|
|
135
|
-
* Legacy WordPress auth-mode literals kept for backward compatibility.
|
|
136
|
-
*/
|
|
137
|
-
export type EndpointOpenApiAuthMode = "authenticated-rest-nonce" | "public-read" | "public-signed-token";
|
|
138
|
-
/**
|
|
139
|
-
* Supported HTTP methods for generated REST OpenAPI endpoints.
|
|
140
|
-
*/
|
|
141
|
-
export type EndpointOpenApiMethod = "DELETE" | "GET" | "PATCH" | "POST" | "PUT";
|
|
142
|
-
/**
|
|
143
|
-
* Contract document used when composing an endpoint-aware OpenAPI file.
|
|
144
|
-
*/
|
|
145
|
-
export interface EndpointOpenApiContractDocument {
|
|
146
|
-
/** Manifest-derived contract document for this schema component. */
|
|
147
|
-
document: ManifestDocument;
|
|
148
|
-
/** Optional component name override for the generated schema reference. */
|
|
149
|
-
schemaName?: string;
|
|
150
|
-
}
|
|
151
|
-
interface EndpointOpenApiEndpointBaseDefinition {
|
|
152
|
-
/** Authentication policy surfaced in OpenAPI metadata. */
|
|
153
|
-
auth?: EndpointAuthIntent;
|
|
154
|
-
/** @deprecated Prefer `auth` plus `wordpressAuth` for new manifests. */
|
|
155
|
-
authMode?: EndpointOpenApiAuthMode;
|
|
156
|
-
/** Contract key for a JSON request body, when the endpoint accepts one. */
|
|
157
|
-
bodyContract?: string;
|
|
158
|
-
/** HTTP method exposed by the route. */
|
|
159
|
-
method: EndpointOpenApiMethod;
|
|
160
|
-
/** Stable OpenAPI operation id for this route. */
|
|
161
|
-
operationId: string;
|
|
162
|
-
/** Absolute REST path including namespace and version. */
|
|
163
|
-
path: string;
|
|
164
|
-
/** Contract key for query parameters, when the endpoint reads from the query string. */
|
|
165
|
-
queryContract?: string;
|
|
166
|
-
/** Contract key for the successful JSON response body. */
|
|
167
|
-
responseContract: string;
|
|
168
|
-
/** Optional short endpoint summary shown in generated docs. */
|
|
169
|
-
summary?: string;
|
|
170
|
-
/** OpenAPI tag names applied to this endpoint. */
|
|
171
|
-
tags: readonly string[];
|
|
172
|
-
/** Optional WordPress adapter metadata for the default runtime. */
|
|
173
|
-
wordpressAuth?: EndpointWordPressAuthDefinition;
|
|
174
|
-
}
|
|
175
|
-
/**
|
|
176
|
-
* Route metadata for one REST endpoint in the aggregate OpenAPI document.
|
|
177
|
-
*/
|
|
178
|
-
export type EndpointOpenApiEndpointDefinition = (EndpointOpenApiEndpointBaseDefinition & {
|
|
179
|
-
auth: EndpointAuthIntent;
|
|
180
|
-
}) | (EndpointOpenApiEndpointBaseDefinition & {
|
|
181
|
-
/** @deprecated Prefer `auth` plus `wordpressAuth` for new manifests. */
|
|
182
|
-
authMode: EndpointOpenApiAuthMode;
|
|
183
|
-
});
|
|
184
|
-
export interface NormalizedEndpointAuthDefinition {
|
|
185
|
-
auth: EndpointAuthIntent;
|
|
186
|
-
authMode?: EndpointOpenApiAuthMode;
|
|
187
|
-
wordpressAuth?: EndpointWordPressAuthDefinition;
|
|
188
|
-
}
|
|
189
|
-
/**
|
|
190
|
-
* Options for building an aggregate endpoint-aware OpenAPI document.
|
|
191
|
-
*/
|
|
192
|
-
export interface EndpointOpenApiDocumentOptions {
|
|
193
|
-
/** Named contract documents keyed by the endpoint registry identifiers. */
|
|
194
|
-
contracts: Readonly<Record<string, EndpointOpenApiContractDocument>>;
|
|
195
|
-
/** Route definitions that should appear in the generated OpenAPI file. */
|
|
196
|
-
endpoints: readonly EndpointOpenApiEndpointDefinition[];
|
|
197
|
-
/** Optional document-level OpenAPI info metadata. */
|
|
198
|
-
info?: OpenApiInfo;
|
|
199
|
-
}
|
|
200
|
-
/**
|
|
201
|
-
* Supported schema projection profiles derived from one canonical wp-typia JSON Schema document.
|
|
202
|
-
*/
|
|
203
|
-
export type JsonSchemaProjectionProfile = "ai-structured-output" | "rest";
|
|
204
|
-
/**
|
|
205
|
-
* Options for projecting one generated JSON Schema document into another consumer-facing profile.
|
|
206
|
-
*/
|
|
207
|
-
export interface JsonSchemaProjectionOptions {
|
|
208
|
-
/** Projection profile that controls schema transformation rules. */
|
|
209
|
-
profile: JsonSchemaProjectionProfile;
|
|
210
|
-
}
|
|
211
|
-
/**
|
|
212
|
-
* Converts one manifest attribute definition into a JSON Schema fragment.
|
|
213
|
-
*
|
|
214
|
-
* @param attribute Manifest-derived attribute metadata.
|
|
215
|
-
* @returns A JSON-compatible schema fragment for the attribute.
|
|
216
|
-
*/
|
|
217
|
-
export declare function manifestAttributeToJsonSchema(attribute: ManifestAttribute): JsonSchemaObject;
|
|
218
|
-
/**
|
|
219
|
-
* Builds a full JSON Schema document from a Typia manifest document.
|
|
220
|
-
*
|
|
221
|
-
* @param doc Manifest-derived attribute document.
|
|
222
|
-
* @returns A draft 2020-12 JSON Schema document for the manifest root object.
|
|
223
|
-
*/
|
|
224
|
-
export declare function manifestToJsonSchema(doc: ManifestDocument): JsonSchemaDocument;
|
|
225
|
-
/**
|
|
226
|
-
* Projects one generated wp-typia JSON Schema document into a consumer-facing profile.
|
|
227
|
-
*
|
|
228
|
-
* @param schema Existing generated JSON Schema document.
|
|
229
|
-
* @param options Projection profile options.
|
|
230
|
-
* @returns A cloned schema document adjusted for the requested profile.
|
|
231
|
-
*/
|
|
232
|
-
export declare function projectJsonSchemaDocument<Schema extends JsonSchemaDocument | JsonSchemaObject>(schema: Schema, options: JsonSchemaProjectionOptions): Schema;
|
|
233
|
-
/**
|
|
234
|
-
* Wraps a manifest-derived JSON Schema document in a minimal OpenAPI 3.1 shell.
|
|
235
|
-
*
|
|
236
|
-
* @param doc Manifest-derived attribute document.
|
|
237
|
-
* @param info Optional OpenAPI document metadata.
|
|
238
|
-
* @returns An OpenAPI document containing the schema as a single component.
|
|
239
|
-
*/
|
|
240
|
-
export declare function manifestToOpenApi(doc: ManifestDocument, info?: OpenApiInfo): OpenApiDocument;
|
|
241
|
-
/**
|
|
242
|
-
* Normalizes endpoint auth metadata into backend-neutral intent plus optional
|
|
243
|
-
* WordPress adapter details.
|
|
244
|
-
*
|
|
245
|
-
* This public runtime helper accepts either the authored `auth` and optional
|
|
246
|
-
* `wordpressAuth` shape or the deprecated `authMode` field. It validates that
|
|
247
|
-
* the provided fields are compatible, resolves the legacy compatibility mode,
|
|
248
|
-
* and returns a normalized definition without mutating the input endpoint.
|
|
249
|
-
*
|
|
250
|
-
* @param endpoint - Endpoint auth fields to normalize, including `auth`,
|
|
251
|
-
* `authMode`, `wordpressAuth`, and optional identity fields used in error
|
|
252
|
-
* messages (`operationId`, `path`, and `method`).
|
|
253
|
-
* @returns The normalized auth definition for the endpoint.
|
|
254
|
-
* @throws When `auth` and deprecated `authMode` conflict.
|
|
255
|
-
* @throws When `wordpressAuth` is attached to the `public` auth intent.
|
|
256
|
-
* @throws When the selected `wordpressAuth` mechanism is incompatible with the
|
|
257
|
-
* chosen auth intent.
|
|
258
|
-
* @throws When neither `auth` nor deprecated `authMode` is defined.
|
|
259
|
-
*/
|
|
260
|
-
export declare function normalizeEndpointAuthDefinition(endpoint: Pick<EndpointOpenApiEndpointDefinition, "auth" | "authMode" | "operationId" | "path" | "wordpressAuth" | "method">): NormalizedEndpointAuthDefinition;
|
|
261
|
-
/**
|
|
262
|
-
* Build a complete OpenAPI 3.1 document from contract manifests and route metadata.
|
|
263
|
-
*
|
|
264
|
-
* @param options Aggregate contract and endpoint definitions for the REST surface.
|
|
265
|
-
* @returns A JSON-compatible OpenAPI document with paths, components, and auth metadata.
|
|
266
|
-
*/
|
|
267
|
-
export declare function buildEndpointOpenApiDocument(options: EndpointOpenApiDocumentOptions): OpenApiDocument;
|
|
268
|
-
export {};
|
|
7
|
+
export * from "@wp-typia/block-runtime/schema-core";
|
|
@@ -1,777 +1,7 @@
|
|
|
1
|
-
const WP_TYPIA_OPENAPI_EXTENSION_KEYS = {
|
|
2
|
-
AUTH_INTENT: "x-typia-authIntent",
|
|
3
|
-
AUTH_POLICY: "x-wp-typia-authPolicy",
|
|
4
|
-
PUBLIC_TOKEN_FIELD: "x-wp-typia-publicTokenField",
|
|
5
|
-
TYPE_TAG: "x-typeTag",
|
|
6
|
-
};
|
|
7
|
-
const WP_TYPIA_OPENAPI_LITERALS = {
|
|
8
|
-
JSON_CONTENT_TYPE: "application/json",
|
|
9
|
-
PUBLIC_WRITE_TOKEN_FIELD: "publicWriteToken",
|
|
10
|
-
QUERY_LOCATION: "query",
|
|
11
|
-
SUCCESS_RESPONSE_DESCRIPTION: "Successful response",
|
|
12
|
-
WORDPRESS_PUBLIC_TOKEN_MECHANISM: "public-signed-token",
|
|
13
|
-
WORDPRESS_REST_NONCE_MECHANISM: "rest-nonce",
|
|
14
|
-
WP_REST_NONCE_HEADER: "X-WP-Nonce",
|
|
15
|
-
WP_REST_NONCE_SCHEME: "wpRestNonce",
|
|
16
|
-
};
|
|
17
|
-
const WP_TYPIA_SCHEMA_UINT32_MAX = 4294967295;
|
|
18
|
-
const WP_TYPIA_SCHEMA_INT32_MAX = 2147483647;
|
|
19
|
-
const WP_TYPIA_SCHEMA_INT32_MIN = -2147483648;
|
|
20
|
-
function applyConstraintIfNumber(schema, key, value) {
|
|
21
|
-
if (typeof value === "number" && Number.isFinite(value)) {
|
|
22
|
-
schema[key] = value;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
function applyConstraintIfString(schema, key, value) {
|
|
26
|
-
if (typeof value === "string" && value.length > 0) {
|
|
27
|
-
schema[key] = value;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
function applyCommonConstraints(schema, constraints) {
|
|
31
|
-
applyConstraintIfString(schema, "format", constraints.format);
|
|
32
|
-
applyConstraintIfString(schema, "pattern", constraints.pattern);
|
|
33
|
-
applyConstraintIfString(schema, WP_TYPIA_OPENAPI_EXTENSION_KEYS.TYPE_TAG, constraints.typeTag);
|
|
34
|
-
applyConstraintIfNumber(schema, "minLength", constraints.minLength);
|
|
35
|
-
applyConstraintIfNumber(schema, "maxLength", constraints.maxLength);
|
|
36
|
-
applyConstraintIfNumber(schema, "minimum", constraints.minimum);
|
|
37
|
-
applyConstraintIfNumber(schema, "maximum", constraints.maximum);
|
|
38
|
-
applyConstraintIfNumber(schema, "exclusiveMinimum", constraints.exclusiveMinimum);
|
|
39
|
-
applyConstraintIfNumber(schema, "exclusiveMaximum", constraints.exclusiveMaximum);
|
|
40
|
-
applyConstraintIfNumber(schema, "multipleOf", constraints.multipleOf);
|
|
41
|
-
applyConstraintIfNumber(schema, "minItems", constraints.minItems);
|
|
42
|
-
applyConstraintIfNumber(schema, "maxItems", constraints.maxItems);
|
|
43
|
-
}
|
|
44
|
-
function createUnionDiscriminatorProperty(branchKey) {
|
|
45
|
-
return {
|
|
46
|
-
const: branchKey,
|
|
47
|
-
enum: [branchKey],
|
|
48
|
-
type: "string",
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
function addDiscriminatorToObjectBranch(schema, discriminator, branchKey) {
|
|
52
|
-
if (typeof schema.properties !== "object" ||
|
|
53
|
-
schema.properties === null ||
|
|
54
|
-
Array.isArray(schema.properties)) {
|
|
55
|
-
return null;
|
|
56
|
-
}
|
|
57
|
-
const properties = schema.properties;
|
|
58
|
-
properties[discriminator] = createUnionDiscriminatorProperty(branchKey);
|
|
59
|
-
const required = Array.isArray(schema.required)
|
|
60
|
-
? [...new Set([...schema.required, discriminator])]
|
|
61
|
-
: [discriminator];
|
|
62
|
-
schema.required = required;
|
|
63
|
-
return schema;
|
|
64
|
-
}
|
|
65
|
-
function isJsonSchemaObject(value) {
|
|
66
|
-
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
67
|
-
}
|
|
68
|
-
function cloneJsonSchemaNode(value) {
|
|
69
|
-
if (Array.isArray(value)) {
|
|
70
|
-
return value.map((item) => isJsonSchemaObject(item) || Array.isArray(item)
|
|
71
|
-
? cloneJsonSchemaNode(item)
|
|
72
|
-
: item);
|
|
73
|
-
}
|
|
74
|
-
if (!isJsonSchemaObject(value)) {
|
|
75
|
-
return value;
|
|
76
|
-
}
|
|
77
|
-
return Object.fromEntries(Object.entries(value).map(([key, child]) => [
|
|
78
|
-
key,
|
|
79
|
-
child === undefined
|
|
80
|
-
? undefined
|
|
81
|
-
: isJsonSchemaObject(child) || Array.isArray(child)
|
|
82
|
-
? cloneJsonSchemaNode(child)
|
|
83
|
-
: child,
|
|
84
|
-
]));
|
|
85
|
-
}
|
|
86
|
-
function isWpTypiaSchemaExtensionKey(key) {
|
|
87
|
-
return key.startsWith("x-wp-typia-");
|
|
88
|
-
}
|
|
89
|
-
function getProjectedNumericMultipleOf(schema, path, typeTag) {
|
|
90
|
-
const currentMultipleOf = schema.multipleOf;
|
|
91
|
-
if (currentMultipleOf !== undefined &&
|
|
92
|
-
(typeof currentMultipleOf !== "number" ||
|
|
93
|
-
!Number.isFinite(currentMultipleOf) ||
|
|
94
|
-
!Number.isInteger(currentMultipleOf) ||
|
|
95
|
-
currentMultipleOf <= 0)) {
|
|
96
|
-
throw new Error(`Unable to project unsupported ${typeTag} multipleOf at "${path}".`);
|
|
97
|
-
}
|
|
98
|
-
return typeof currentMultipleOf === "number" ? currentMultipleOf : 1;
|
|
99
|
-
}
|
|
100
|
-
function applyProjectedUint32Constraints(schema, path) {
|
|
101
|
-
const currentMinimum = schema.minimum;
|
|
102
|
-
if (typeof currentMinimum === "number" && Number.isFinite(currentMinimum)) {
|
|
103
|
-
schema.minimum = Math.max(currentMinimum, 0);
|
|
104
|
-
}
|
|
105
|
-
else {
|
|
106
|
-
schema.minimum = 0;
|
|
107
|
-
}
|
|
108
|
-
const currentMaximum = schema.maximum;
|
|
109
|
-
if (typeof currentMaximum === "number" && Number.isFinite(currentMaximum)) {
|
|
110
|
-
schema.maximum = Math.min(currentMaximum, WP_TYPIA_SCHEMA_UINT32_MAX);
|
|
111
|
-
}
|
|
112
|
-
else {
|
|
113
|
-
schema.maximum = WP_TYPIA_SCHEMA_UINT32_MAX;
|
|
114
|
-
}
|
|
115
|
-
schema.multipleOf = getProjectedNumericMultipleOf(schema, path, "uint32");
|
|
116
|
-
schema.type = "integer";
|
|
117
|
-
}
|
|
118
|
-
function applyProjectedInt32Constraints(schema, path) {
|
|
119
|
-
const currentMinimum = schema.minimum;
|
|
120
|
-
if (typeof currentMinimum === "number" && Number.isFinite(currentMinimum)) {
|
|
121
|
-
schema.minimum = Math.max(currentMinimum, WP_TYPIA_SCHEMA_INT32_MIN);
|
|
122
|
-
}
|
|
123
|
-
else {
|
|
124
|
-
schema.minimum = WP_TYPIA_SCHEMA_INT32_MIN;
|
|
125
|
-
}
|
|
126
|
-
const currentMaximum = schema.maximum;
|
|
127
|
-
if (typeof currentMaximum === "number" && Number.isFinite(currentMaximum)) {
|
|
128
|
-
schema.maximum = Math.min(currentMaximum, WP_TYPIA_SCHEMA_INT32_MAX);
|
|
129
|
-
}
|
|
130
|
-
else {
|
|
131
|
-
schema.maximum = WP_TYPIA_SCHEMA_INT32_MAX;
|
|
132
|
-
}
|
|
133
|
-
schema.multipleOf = getProjectedNumericMultipleOf(schema, path, "int32");
|
|
134
|
-
schema.type = "integer";
|
|
135
|
-
}
|
|
136
|
-
function applyProjectedTypeTag(schema, typeTag, path) {
|
|
137
|
-
switch (typeTag) {
|
|
138
|
-
case "uint32":
|
|
139
|
-
applyProjectedUint32Constraints(schema, path);
|
|
140
|
-
break;
|
|
141
|
-
case "int32":
|
|
142
|
-
applyProjectedInt32Constraints(schema, path);
|
|
143
|
-
break;
|
|
144
|
-
case "float":
|
|
145
|
-
case "double":
|
|
146
|
-
schema.type = "number";
|
|
147
|
-
break;
|
|
148
|
-
default:
|
|
149
|
-
throw new Error(`Unsupported wp-typia schema type tag "${typeTag}" at "${path}".`);
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
function canProjectTypeTag(typeTag) {
|
|
153
|
-
switch (typeTag) {
|
|
154
|
-
case "uint32":
|
|
155
|
-
case "int32":
|
|
156
|
-
case "float":
|
|
157
|
-
case "double":
|
|
158
|
-
return true;
|
|
159
|
-
default:
|
|
160
|
-
return false;
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
function projectSchemaArrayItemsForAiStructuredOutput(items, path) {
|
|
164
|
-
return items.map((item, index) => projectSchemaObjectForAiStructuredOutput(item, `${path}/${index}`));
|
|
165
|
-
}
|
|
166
|
-
function projectSchemaArrayItemsForRest(items, path) {
|
|
167
|
-
return items.map((item, index) => projectSchemaObjectForRest(item, `${path}/${index}`));
|
|
168
|
-
}
|
|
169
|
-
function projectSchemaPropertyMapForAiStructuredOutput(properties, path) {
|
|
170
|
-
return Object.fromEntries(Object.entries(properties).map(([key, value]) => [
|
|
171
|
-
key,
|
|
172
|
-
projectSchemaObjectForAiStructuredOutput(value, `${path}/${key}`),
|
|
173
|
-
]));
|
|
174
|
-
}
|
|
175
|
-
function projectSchemaPropertyMapForRest(properties, path) {
|
|
176
|
-
return Object.fromEntries(Object.entries(properties).map(([key, value]) => [
|
|
177
|
-
key,
|
|
178
|
-
projectSchemaObjectForRest(value, `${path}/${key}`),
|
|
179
|
-
]));
|
|
180
|
-
}
|
|
181
|
-
function projectSchemaObjectForAiStructuredOutput(node, path) {
|
|
182
|
-
const projectedNode = cloneJsonSchemaNode(node);
|
|
183
|
-
const rawTypeTag = projectedNode[WP_TYPIA_OPENAPI_EXTENSION_KEYS.TYPE_TAG];
|
|
184
|
-
if (typeof rawTypeTag === "string") {
|
|
185
|
-
applyProjectedTypeTag(projectedNode, rawTypeTag, path);
|
|
186
|
-
}
|
|
187
|
-
delete projectedNode[WP_TYPIA_OPENAPI_EXTENSION_KEYS.TYPE_TAG];
|
|
188
|
-
for (const key of Object.keys(projectedNode)) {
|
|
189
|
-
if (isWpTypiaSchemaExtensionKey(key)) {
|
|
190
|
-
delete projectedNode[key];
|
|
191
|
-
continue;
|
|
192
|
-
}
|
|
193
|
-
const child = projectedNode[key];
|
|
194
|
-
if (Array.isArray(child)) {
|
|
195
|
-
projectedNode[key] = child.every(isJsonSchemaObject)
|
|
196
|
-
? projectSchemaArrayItemsForAiStructuredOutput(child, `${path}/${key}`)
|
|
197
|
-
: child;
|
|
198
|
-
continue;
|
|
199
|
-
}
|
|
200
|
-
if (!isJsonSchemaObject(child)) {
|
|
201
|
-
continue;
|
|
202
|
-
}
|
|
203
|
-
if (key === "properties") {
|
|
204
|
-
projectedNode[key] = projectSchemaPropertyMapForAiStructuredOutput(child, `${path}/${key}`);
|
|
205
|
-
continue;
|
|
206
|
-
}
|
|
207
|
-
projectedNode[key] = projectSchemaObjectForAiStructuredOutput(child, `${path}/${key}`);
|
|
208
|
-
}
|
|
209
|
-
return projectedNode;
|
|
210
|
-
}
|
|
211
|
-
function projectSchemaObjectForRest(node, path) {
|
|
212
|
-
const projectedNode = cloneJsonSchemaNode(node);
|
|
213
|
-
const rawTypeTag = projectedNode[WP_TYPIA_OPENAPI_EXTENSION_KEYS.TYPE_TAG];
|
|
214
|
-
if (typeof rawTypeTag === "string" && canProjectTypeTag(rawTypeTag)) {
|
|
215
|
-
applyProjectedTypeTag(projectedNode, rawTypeTag, path);
|
|
216
|
-
}
|
|
217
|
-
for (const key of Object.keys(projectedNode)) {
|
|
218
|
-
const child = projectedNode[key];
|
|
219
|
-
if (Array.isArray(child)) {
|
|
220
|
-
projectedNode[key] = child.every(isJsonSchemaObject)
|
|
221
|
-
? projectSchemaArrayItemsForRest(child, `${path}/${key}`)
|
|
222
|
-
: child;
|
|
223
|
-
continue;
|
|
224
|
-
}
|
|
225
|
-
if (!isJsonSchemaObject(child)) {
|
|
226
|
-
continue;
|
|
227
|
-
}
|
|
228
|
-
if (key === "properties") {
|
|
229
|
-
projectedNode[key] = projectSchemaPropertyMapForRest(child, `${path}/${key}`);
|
|
230
|
-
continue;
|
|
231
|
-
}
|
|
232
|
-
projectedNode[key] = projectSchemaObjectForRest(child, `${path}/${key}`);
|
|
233
|
-
}
|
|
234
|
-
applyProjectedBootstrapContract(projectedNode);
|
|
235
|
-
return projectedNode;
|
|
236
|
-
}
|
|
237
|
-
function applyProjectedBootstrapContract(schema) {
|
|
238
|
-
if (schema.type !== "object" || !isJsonSchemaObject(schema.properties)) {
|
|
239
|
-
return;
|
|
240
|
-
}
|
|
241
|
-
const properties = schema.properties;
|
|
242
|
-
const buildRequiredPropertyObject = (requiredKeys) => Object.fromEntries(requiredKeys.map((requiredKey) => [requiredKey, properties[requiredKey] ?? {}]));
|
|
243
|
-
if (properties.canWrite?.type !== "boolean") {
|
|
244
|
-
return;
|
|
245
|
-
}
|
|
246
|
-
const allOf = Array.isArray(schema.allOf)
|
|
247
|
-
? [...schema.allOf]
|
|
248
|
-
: [];
|
|
249
|
-
const canWriteIsTrue = {
|
|
250
|
-
properties: {
|
|
251
|
-
canWrite: {
|
|
252
|
-
const: true,
|
|
253
|
-
},
|
|
254
|
-
},
|
|
255
|
-
required: ["canWrite"],
|
|
256
|
-
};
|
|
257
|
-
const hasRestNonce = properties.restNonce?.type === "string";
|
|
258
|
-
const hasPublicWriteCredential = properties.publicWriteToken?.type === "string" &&
|
|
259
|
-
isJsonSchemaObject(properties.publicWriteExpiresAt);
|
|
260
|
-
if (hasRestNonce && hasPublicWriteCredential) {
|
|
261
|
-
allOf.push({
|
|
262
|
-
if: canWriteIsTrue,
|
|
263
|
-
then: {
|
|
264
|
-
anyOf: [
|
|
265
|
-
{
|
|
266
|
-
properties: buildRequiredPropertyObject(["restNonce"]),
|
|
267
|
-
required: ["restNonce"],
|
|
268
|
-
},
|
|
269
|
-
{
|
|
270
|
-
properties: buildRequiredPropertyObject([
|
|
271
|
-
"publicWriteExpiresAt",
|
|
272
|
-
"publicWriteToken",
|
|
273
|
-
]),
|
|
274
|
-
required: ["publicWriteExpiresAt", "publicWriteToken"],
|
|
275
|
-
},
|
|
276
|
-
],
|
|
277
|
-
},
|
|
278
|
-
else: {
|
|
279
|
-
not: {
|
|
280
|
-
anyOf: [
|
|
281
|
-
{
|
|
282
|
-
properties: buildRequiredPropertyObject(["restNonce"]),
|
|
283
|
-
required: ["restNonce"],
|
|
284
|
-
},
|
|
285
|
-
{
|
|
286
|
-
properties: buildRequiredPropertyObject(["publicWriteToken"]),
|
|
287
|
-
required: ["publicWriteToken"],
|
|
288
|
-
},
|
|
289
|
-
],
|
|
290
|
-
},
|
|
291
|
-
},
|
|
292
|
-
});
|
|
293
|
-
}
|
|
294
|
-
if (hasRestNonce && !hasPublicWriteCredential) {
|
|
295
|
-
allOf.push({
|
|
296
|
-
if: canWriteIsTrue,
|
|
297
|
-
then: {
|
|
298
|
-
properties: buildRequiredPropertyObject(["restNonce"]),
|
|
299
|
-
required: ["restNonce"],
|
|
300
|
-
},
|
|
301
|
-
else: {
|
|
302
|
-
not: {
|
|
303
|
-
properties: buildRequiredPropertyObject(["restNonce"]),
|
|
304
|
-
required: ["restNonce"],
|
|
305
|
-
},
|
|
306
|
-
},
|
|
307
|
-
});
|
|
308
|
-
}
|
|
309
|
-
if (hasPublicWriteCredential && !hasRestNonce) {
|
|
310
|
-
allOf.push({
|
|
311
|
-
if: canWriteIsTrue,
|
|
312
|
-
then: {
|
|
313
|
-
properties: buildRequiredPropertyObject([
|
|
314
|
-
"publicWriteExpiresAt",
|
|
315
|
-
"publicWriteToken",
|
|
316
|
-
]),
|
|
317
|
-
required: ["publicWriteExpiresAt", "publicWriteToken"],
|
|
318
|
-
},
|
|
319
|
-
else: {
|
|
320
|
-
not: {
|
|
321
|
-
properties: buildRequiredPropertyObject(["publicWriteToken"]),
|
|
322
|
-
required: ["publicWriteToken"],
|
|
323
|
-
},
|
|
324
|
-
},
|
|
325
|
-
});
|
|
326
|
-
}
|
|
327
|
-
if (allOf.length > 0) {
|
|
328
|
-
schema.allOf = allOf;
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
function manifestUnionToJsonSchema(union) {
|
|
332
|
-
const oneOf = Object.entries(union.branches).map(([branchKey, branch]) => {
|
|
333
|
-
if (branch.ts.kind !== "object") {
|
|
334
|
-
throw new Error(`Discriminated union branch "${branchKey}" must be an object to carry "${union.discriminator}".`);
|
|
335
|
-
}
|
|
336
|
-
const schema = manifestAttributeToJsonSchema(branch);
|
|
337
|
-
const objectSchema = addDiscriminatorToObjectBranch(schema, union.discriminator, branchKey);
|
|
338
|
-
if (!objectSchema) {
|
|
339
|
-
throw new Error(`Discriminated union branch "${branchKey}" is missing an object schema for "${union.discriminator}".`);
|
|
340
|
-
}
|
|
341
|
-
return objectSchema;
|
|
342
|
-
});
|
|
343
|
-
return {
|
|
344
|
-
discriminator: {
|
|
345
|
-
propertyName: union.discriminator,
|
|
346
|
-
},
|
|
347
|
-
oneOf,
|
|
348
|
-
};
|
|
349
|
-
}
|
|
350
1
|
/**
|
|
351
|
-
*
|
|
352
|
-
*
|
|
353
|
-
*
|
|
354
|
-
* @
|
|
2
|
+
* Re-exports schema and OpenAPI helpers from `@wp-typia/block-runtime`.
|
|
3
|
+
* This adapter preserves the public `@wp-typia/project-tools/schema-core`
|
|
4
|
+
* path while sharing the canonical implementation with block-runtime.
|
|
5
|
+
* @module
|
|
355
6
|
*/
|
|
356
|
-
export
|
|
357
|
-
if (attribute.ts.union) {
|
|
358
|
-
const schema = manifestUnionToJsonSchema(attribute.ts.union);
|
|
359
|
-
if (attribute.typia.hasDefault) {
|
|
360
|
-
schema.default = attribute.typia.defaultValue ?? null;
|
|
361
|
-
}
|
|
362
|
-
return schema;
|
|
363
|
-
}
|
|
364
|
-
const schema = {};
|
|
365
|
-
const enumValues = Array.isArray(attribute.wp.enum) ? attribute.wp.enum : null;
|
|
366
|
-
if (enumValues && enumValues.length > 0) {
|
|
367
|
-
schema.enum = enumValues;
|
|
368
|
-
}
|
|
369
|
-
if (attribute.typia.hasDefault) {
|
|
370
|
-
schema.default = attribute.typia.defaultValue ?? null;
|
|
371
|
-
}
|
|
372
|
-
switch (attribute.ts.kind) {
|
|
373
|
-
case "string":
|
|
374
|
-
schema.type = "string";
|
|
375
|
-
break;
|
|
376
|
-
case "number":
|
|
377
|
-
schema.type = "number";
|
|
378
|
-
break;
|
|
379
|
-
case "boolean":
|
|
380
|
-
schema.type = "boolean";
|
|
381
|
-
break;
|
|
382
|
-
case "array":
|
|
383
|
-
schema.type = "array";
|
|
384
|
-
if (attribute.ts.items) {
|
|
385
|
-
schema.items = manifestAttributeToJsonSchema(attribute.ts.items);
|
|
386
|
-
}
|
|
387
|
-
break;
|
|
388
|
-
case "object": {
|
|
389
|
-
schema.type = "object";
|
|
390
|
-
schema.additionalProperties = false;
|
|
391
|
-
const properties = attribute.ts.properties ?? {};
|
|
392
|
-
schema.properties = Object.fromEntries(Object.entries(properties).map(([key, value]) => [
|
|
393
|
-
key,
|
|
394
|
-
manifestAttributeToJsonSchema(value),
|
|
395
|
-
]));
|
|
396
|
-
const required = Object.entries(properties)
|
|
397
|
-
.filter(([, value]) => value.ts.required !== false)
|
|
398
|
-
.map(([key]) => key);
|
|
399
|
-
if (required.length > 0) {
|
|
400
|
-
schema.required = required;
|
|
401
|
-
}
|
|
402
|
-
break;
|
|
403
|
-
}
|
|
404
|
-
case "union":
|
|
405
|
-
if (attribute.ts.union) {
|
|
406
|
-
return manifestUnionToJsonSchema(attribute.ts.union);
|
|
407
|
-
}
|
|
408
|
-
schema.oneOf = [];
|
|
409
|
-
break;
|
|
410
|
-
default:
|
|
411
|
-
schema.type = attribute.wp.type ?? "string";
|
|
412
|
-
break;
|
|
413
|
-
}
|
|
414
|
-
applyCommonConstraints(schema, attribute.typia.constraints);
|
|
415
|
-
return schema;
|
|
416
|
-
}
|
|
417
|
-
/**
|
|
418
|
-
* Builds a full JSON Schema document from a Typia manifest document.
|
|
419
|
-
*
|
|
420
|
-
* @param doc Manifest-derived attribute document.
|
|
421
|
-
* @returns A draft 2020-12 JSON Schema document for the manifest root object.
|
|
422
|
-
*/
|
|
423
|
-
export function manifestToJsonSchema(doc) {
|
|
424
|
-
const attributes = doc.attributes ?? {};
|
|
425
|
-
return {
|
|
426
|
-
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
427
|
-
additionalProperties: false,
|
|
428
|
-
properties: Object.fromEntries(Object.entries(attributes).map(([key, value]) => [
|
|
429
|
-
key,
|
|
430
|
-
manifestAttributeToJsonSchema(value),
|
|
431
|
-
])),
|
|
432
|
-
required: Object.entries(attributes)
|
|
433
|
-
.filter(([, value]) => value.ts.required !== false)
|
|
434
|
-
.map(([key]) => key),
|
|
435
|
-
title: doc.sourceType ?? "TypiaDocument",
|
|
436
|
-
type: "object",
|
|
437
|
-
};
|
|
438
|
-
}
|
|
439
|
-
/**
|
|
440
|
-
* Projects one generated wp-typia JSON Schema document into a consumer-facing profile.
|
|
441
|
-
*
|
|
442
|
-
* @param schema Existing generated JSON Schema document.
|
|
443
|
-
* @param options Projection profile options.
|
|
444
|
-
* @returns A cloned schema document adjusted for the requested profile.
|
|
445
|
-
*/
|
|
446
|
-
export function projectJsonSchemaDocument(schema, options) {
|
|
447
|
-
if (options.profile === "rest") {
|
|
448
|
-
return projectSchemaObjectForRest(schema, "#");
|
|
449
|
-
}
|
|
450
|
-
if (options.profile === "ai-structured-output") {
|
|
451
|
-
return projectSchemaObjectForAiStructuredOutput(schema, "#");
|
|
452
|
-
}
|
|
453
|
-
throw new Error(`Unsupported JSON Schema projection profile "${String(options.profile)}".`);
|
|
454
|
-
}
|
|
455
|
-
/**
|
|
456
|
-
* Wraps a manifest-derived JSON Schema document in a minimal OpenAPI 3.1 shell.
|
|
457
|
-
*
|
|
458
|
-
* @param doc Manifest-derived attribute document.
|
|
459
|
-
* @param info Optional OpenAPI document metadata.
|
|
460
|
-
* @returns An OpenAPI document containing the schema as a single component.
|
|
461
|
-
*/
|
|
462
|
-
export function manifestToOpenApi(doc, info = {}) {
|
|
463
|
-
const schemaName = doc.sourceType ?? "TypiaDocument";
|
|
464
|
-
const projectedSchema = projectJsonSchemaDocument(manifestToJsonSchema(doc), {
|
|
465
|
-
profile: "rest",
|
|
466
|
-
});
|
|
467
|
-
delete projectedSchema.$schema;
|
|
468
|
-
return {
|
|
469
|
-
components: {
|
|
470
|
-
schemas: {
|
|
471
|
-
[schemaName]: projectedSchema,
|
|
472
|
-
},
|
|
473
|
-
},
|
|
474
|
-
info: {
|
|
475
|
-
title: info.title ?? schemaName,
|
|
476
|
-
version: info.version ?? "1.0.0",
|
|
477
|
-
...(info.description ? { description: info.description } : {}),
|
|
478
|
-
},
|
|
479
|
-
openapi: "3.1.0",
|
|
480
|
-
paths: {},
|
|
481
|
-
};
|
|
482
|
-
}
|
|
483
|
-
function createOpenApiSchemaRef(schemaName) {
|
|
484
|
-
return {
|
|
485
|
-
$ref: `#/components/schemas/${schemaName}`,
|
|
486
|
-
};
|
|
487
|
-
}
|
|
488
|
-
function formatEndpointDescription(endpoint) {
|
|
489
|
-
return `${endpoint.operationId} (${endpoint.method} ${endpoint.path})`;
|
|
490
|
-
}
|
|
491
|
-
function normalizeWordPressAuthDefinition(wordpressAuth) {
|
|
492
|
-
if (!wordpressAuth) {
|
|
493
|
-
return undefined;
|
|
494
|
-
}
|
|
495
|
-
if (wordpressAuth.mechanism !==
|
|
496
|
-
WP_TYPIA_OPENAPI_LITERALS.WORDPRESS_PUBLIC_TOKEN_MECHANISM) {
|
|
497
|
-
return {
|
|
498
|
-
mechanism: wordpressAuth.mechanism,
|
|
499
|
-
};
|
|
500
|
-
}
|
|
501
|
-
return {
|
|
502
|
-
mechanism: wordpressAuth.mechanism,
|
|
503
|
-
publicTokenField: wordpressAuth.publicTokenField ??
|
|
504
|
-
WP_TYPIA_OPENAPI_LITERALS.PUBLIC_WRITE_TOKEN_FIELD,
|
|
505
|
-
};
|
|
506
|
-
}
|
|
507
|
-
function deriveLegacyAuthModeFromNormalizedAuth(auth, wordpressAuth) {
|
|
508
|
-
if (auth === "public") {
|
|
509
|
-
return "public-read";
|
|
510
|
-
}
|
|
511
|
-
if (auth === "authenticated" &&
|
|
512
|
-
wordpressAuth?.mechanism ===
|
|
513
|
-
WP_TYPIA_OPENAPI_LITERALS.WORDPRESS_REST_NONCE_MECHANISM) {
|
|
514
|
-
return "authenticated-rest-nonce";
|
|
515
|
-
}
|
|
516
|
-
if (auth === "public-write-protected" &&
|
|
517
|
-
wordpressAuth?.mechanism ===
|
|
518
|
-
WP_TYPIA_OPENAPI_LITERALS.WORDPRESS_PUBLIC_TOKEN_MECHANISM) {
|
|
519
|
-
return "public-signed-token";
|
|
520
|
-
}
|
|
521
|
-
return undefined;
|
|
522
|
-
}
|
|
523
|
-
function compareNormalizedEndpointAuth(left, right) {
|
|
524
|
-
return (left.auth === right.auth &&
|
|
525
|
-
left.authMode === right.authMode &&
|
|
526
|
-
left.wordpressAuth?.mechanism === right.wordpressAuth?.mechanism &&
|
|
527
|
-
left.wordpressAuth?.publicTokenField === right.wordpressAuth?.publicTokenField);
|
|
528
|
-
}
|
|
529
|
-
/**
|
|
530
|
-
* Normalizes endpoint auth metadata into backend-neutral intent plus optional
|
|
531
|
-
* WordPress adapter details.
|
|
532
|
-
*
|
|
533
|
-
* This public runtime helper accepts either the authored `auth` and optional
|
|
534
|
-
* `wordpressAuth` shape or the deprecated `authMode` field. It validates that
|
|
535
|
-
* the provided fields are compatible, resolves the legacy compatibility mode,
|
|
536
|
-
* and returns a normalized definition without mutating the input endpoint.
|
|
537
|
-
*
|
|
538
|
-
* @param endpoint - Endpoint auth fields to normalize, including `auth`,
|
|
539
|
-
* `authMode`, `wordpressAuth`, and optional identity fields used in error
|
|
540
|
-
* messages (`operationId`, `path`, and `method`).
|
|
541
|
-
* @returns The normalized auth definition for the endpoint.
|
|
542
|
-
* @throws When `auth` and deprecated `authMode` conflict.
|
|
543
|
-
* @throws When `wordpressAuth` is attached to the `public` auth intent.
|
|
544
|
-
* @throws When the selected `wordpressAuth` mechanism is incompatible with the
|
|
545
|
-
* chosen auth intent.
|
|
546
|
-
* @throws When neither `auth` nor deprecated `authMode` is defined.
|
|
547
|
-
*/
|
|
548
|
-
export function normalizeEndpointAuthDefinition(endpoint) {
|
|
549
|
-
const endpointDescription = typeof endpoint.operationId === "string" &&
|
|
550
|
-
typeof endpoint.path === "string" &&
|
|
551
|
-
typeof endpoint.method === "string"
|
|
552
|
-
? formatEndpointDescription(endpoint)
|
|
553
|
-
: "the current endpoint";
|
|
554
|
-
const nextWordPressAuth = normalizeWordPressAuthDefinition(endpoint.wordpressAuth);
|
|
555
|
-
let normalized = null;
|
|
556
|
-
if (endpoint.auth) {
|
|
557
|
-
normalized = {
|
|
558
|
-
auth: endpoint.auth,
|
|
559
|
-
authMode: deriveLegacyAuthModeFromNormalizedAuth(endpoint.auth, nextWordPressAuth),
|
|
560
|
-
...(nextWordPressAuth ? { wordpressAuth: nextWordPressAuth } : {}),
|
|
561
|
-
};
|
|
562
|
-
if (endpoint.auth === "public" && nextWordPressAuth) {
|
|
563
|
-
throw new Error(`Endpoint "${endpointDescription}" cannot attach wordpressAuth when auth intent is "public".`);
|
|
564
|
-
}
|
|
565
|
-
if (endpoint.auth === "authenticated" &&
|
|
566
|
-
nextWordPressAuth?.mechanism ===
|
|
567
|
-
WP_TYPIA_OPENAPI_LITERALS.WORDPRESS_PUBLIC_TOKEN_MECHANISM) {
|
|
568
|
-
throw new Error(`Endpoint "${endpointDescription}" uses auth intent "authenticated" but wordpressAuth mechanism "${nextWordPressAuth.mechanism}" only supports "public-write-protected".`);
|
|
569
|
-
}
|
|
570
|
-
if (endpoint.auth === "public-write-protected" &&
|
|
571
|
-
nextWordPressAuth?.mechanism ===
|
|
572
|
-
WP_TYPIA_OPENAPI_LITERALS.WORDPRESS_REST_NONCE_MECHANISM) {
|
|
573
|
-
throw new Error(`Endpoint "${endpointDescription}" uses auth intent "public-write-protected" but wordpressAuth mechanism "${nextWordPressAuth.mechanism}" only supports "authenticated".`);
|
|
574
|
-
}
|
|
575
|
-
}
|
|
576
|
-
let legacyNormalized = null;
|
|
577
|
-
if (endpoint.authMode) {
|
|
578
|
-
legacyNormalized =
|
|
579
|
-
endpoint.authMode === "public-read"
|
|
580
|
-
? {
|
|
581
|
-
auth: "public",
|
|
582
|
-
authMode: "public-read",
|
|
583
|
-
}
|
|
584
|
-
: endpoint.authMode === "authenticated-rest-nonce"
|
|
585
|
-
? {
|
|
586
|
-
auth: "authenticated",
|
|
587
|
-
authMode: "authenticated-rest-nonce",
|
|
588
|
-
wordpressAuth: {
|
|
589
|
-
mechanism: WP_TYPIA_OPENAPI_LITERALS.WORDPRESS_REST_NONCE_MECHANISM,
|
|
590
|
-
},
|
|
591
|
-
}
|
|
592
|
-
: {
|
|
593
|
-
auth: "public-write-protected",
|
|
594
|
-
authMode: "public-signed-token",
|
|
595
|
-
wordpressAuth: {
|
|
596
|
-
mechanism: WP_TYPIA_OPENAPI_LITERALS.WORDPRESS_PUBLIC_TOKEN_MECHANISM,
|
|
597
|
-
publicTokenField: WP_TYPIA_OPENAPI_LITERALS.PUBLIC_WRITE_TOKEN_FIELD,
|
|
598
|
-
},
|
|
599
|
-
};
|
|
600
|
-
}
|
|
601
|
-
if (normalized && legacyNormalized) {
|
|
602
|
-
if (!compareNormalizedEndpointAuth(normalized, legacyNormalized)) {
|
|
603
|
-
throw new Error(`Endpoint "${endpointDescription}" defines conflicting auth metadata between auth/wordpressAuth and deprecated authMode.`);
|
|
604
|
-
}
|
|
605
|
-
return normalized;
|
|
606
|
-
}
|
|
607
|
-
if (normalized) {
|
|
608
|
-
return normalized;
|
|
609
|
-
}
|
|
610
|
-
if (legacyNormalized) {
|
|
611
|
-
return legacyNormalized;
|
|
612
|
-
}
|
|
613
|
-
throw new Error(`Endpoint "${endpointDescription}" must define either auth or deprecated authMode.`);
|
|
614
|
-
}
|
|
615
|
-
function getContractSchemaName(contractKey, contract, endpoint, role = "contract") {
|
|
616
|
-
if (!contract) {
|
|
617
|
-
if (endpoint) {
|
|
618
|
-
throw new Error(`Missing ${role} contract "${contractKey}" while building endpoint "${formatEndpointDescription(endpoint)}"`);
|
|
619
|
-
}
|
|
620
|
-
throw new Error(`Missing OpenAPI contract definition for "${contractKey}"`);
|
|
621
|
-
}
|
|
622
|
-
return contract.schemaName ?? contract.document.sourceType ?? contractKey;
|
|
623
|
-
}
|
|
624
|
-
function buildQueryParameters(contract) {
|
|
625
|
-
const attributes = contract.document.attributes ?? {};
|
|
626
|
-
return Object.entries(attributes).map(([name, attribute]) => ({
|
|
627
|
-
in: WP_TYPIA_OPENAPI_LITERALS.QUERY_LOCATION,
|
|
628
|
-
name,
|
|
629
|
-
required: attribute.ts.required !== false,
|
|
630
|
-
schema: manifestAttributeToJsonSchema(attribute),
|
|
631
|
-
}));
|
|
632
|
-
}
|
|
633
|
-
function createBootstrapResponseHeaders(normalizedAuth) {
|
|
634
|
-
const headers = {
|
|
635
|
-
"Cache-Control": {
|
|
636
|
-
description: "Must be non-cacheable for fresh bootstrap write/session state.",
|
|
637
|
-
schema: {
|
|
638
|
-
type: "string",
|
|
639
|
-
example: "private, no-store, no-cache, must-revalidate",
|
|
640
|
-
},
|
|
641
|
-
},
|
|
642
|
-
Pragma: {
|
|
643
|
-
description: "Legacy non-cacheable bootstrap response directive.",
|
|
644
|
-
schema: {
|
|
645
|
-
type: "string",
|
|
646
|
-
example: "no-cache",
|
|
647
|
-
},
|
|
648
|
-
},
|
|
649
|
-
};
|
|
650
|
-
if (normalizedAuth.wordpressAuth?.mechanism ===
|
|
651
|
-
WP_TYPIA_OPENAPI_LITERALS.WORDPRESS_REST_NONCE_MECHANISM) {
|
|
652
|
-
headers.Vary = {
|
|
653
|
-
description: "Viewer-aware bootstrap responses should vary on cookie-backed auth state.",
|
|
654
|
-
schema: {
|
|
655
|
-
type: "string",
|
|
656
|
-
example: "Cookie",
|
|
657
|
-
},
|
|
658
|
-
};
|
|
659
|
-
}
|
|
660
|
-
return headers;
|
|
661
|
-
}
|
|
662
|
-
function createSuccessResponse(schemaName, headers) {
|
|
663
|
-
return {
|
|
664
|
-
content: {
|
|
665
|
-
[WP_TYPIA_OPENAPI_LITERALS.JSON_CONTENT_TYPE]: {
|
|
666
|
-
schema: createOpenApiSchemaRef(schemaName),
|
|
667
|
-
},
|
|
668
|
-
},
|
|
669
|
-
description: WP_TYPIA_OPENAPI_LITERALS.SUCCESS_RESPONSE_DESCRIPTION,
|
|
670
|
-
...(headers ? { headers } : {}),
|
|
671
|
-
};
|
|
672
|
-
}
|
|
673
|
-
function buildEndpointOpenApiOperation(endpoint, contracts) {
|
|
674
|
-
const normalizedAuth = normalizeEndpointAuthDefinition(endpoint);
|
|
675
|
-
const isBootstrapEndpoint = endpoint.path.endsWith("/bootstrap");
|
|
676
|
-
const operation = {
|
|
677
|
-
operationId: endpoint.operationId,
|
|
678
|
-
responses: {
|
|
679
|
-
"200": createSuccessResponse(getContractSchemaName(endpoint.responseContract, contracts[endpoint.responseContract], endpoint, "response"), isBootstrapEndpoint
|
|
680
|
-
? createBootstrapResponseHeaders(normalizedAuth)
|
|
681
|
-
: undefined),
|
|
682
|
-
},
|
|
683
|
-
tags: [...endpoint.tags],
|
|
684
|
-
[WP_TYPIA_OPENAPI_EXTENSION_KEYS.AUTH_INTENT]: normalizedAuth.auth,
|
|
685
|
-
...(normalizedAuth.authMode
|
|
686
|
-
? {
|
|
687
|
-
[WP_TYPIA_OPENAPI_EXTENSION_KEYS.AUTH_POLICY]: normalizedAuth.authMode,
|
|
688
|
-
}
|
|
689
|
-
: {}),
|
|
690
|
-
};
|
|
691
|
-
if (typeof endpoint.summary === "string" && endpoint.summary.length > 0) {
|
|
692
|
-
operation.summary = endpoint.summary;
|
|
693
|
-
}
|
|
694
|
-
if (typeof endpoint.queryContract === "string") {
|
|
695
|
-
operation.parameters = buildQueryParameters(contracts[endpoint.queryContract] ??
|
|
696
|
-
(() => {
|
|
697
|
-
throw new Error(`Missing query contract "${endpoint.queryContract}" while building endpoint "${formatEndpointDescription(endpoint)}"`);
|
|
698
|
-
})());
|
|
699
|
-
}
|
|
700
|
-
if (typeof endpoint.bodyContract === "string") {
|
|
701
|
-
operation.requestBody = {
|
|
702
|
-
content: {
|
|
703
|
-
[WP_TYPIA_OPENAPI_LITERALS.JSON_CONTENT_TYPE]: {
|
|
704
|
-
schema: createOpenApiSchemaRef(getContractSchemaName(endpoint.bodyContract, contracts[endpoint.bodyContract], endpoint, "request body")),
|
|
705
|
-
},
|
|
706
|
-
},
|
|
707
|
-
required: true,
|
|
708
|
-
};
|
|
709
|
-
}
|
|
710
|
-
if (normalizedAuth.wordpressAuth?.mechanism ===
|
|
711
|
-
WP_TYPIA_OPENAPI_LITERALS.WORDPRESS_REST_NONCE_MECHANISM) {
|
|
712
|
-
operation.security = [
|
|
713
|
-
{
|
|
714
|
-
[WP_TYPIA_OPENAPI_LITERALS.WP_REST_NONCE_SCHEME]: [],
|
|
715
|
-
},
|
|
716
|
-
];
|
|
717
|
-
}
|
|
718
|
-
else if (normalizedAuth.wordpressAuth?.mechanism ===
|
|
719
|
-
WP_TYPIA_OPENAPI_LITERALS.WORDPRESS_PUBLIC_TOKEN_MECHANISM) {
|
|
720
|
-
operation[WP_TYPIA_OPENAPI_EXTENSION_KEYS.PUBLIC_TOKEN_FIELD] =
|
|
721
|
-
normalizedAuth.wordpressAuth.publicTokenField ??
|
|
722
|
-
WP_TYPIA_OPENAPI_LITERALS.PUBLIC_WRITE_TOKEN_FIELD;
|
|
723
|
-
}
|
|
724
|
-
return operation;
|
|
725
|
-
}
|
|
726
|
-
/**
|
|
727
|
-
* Build a complete OpenAPI 3.1 document from contract manifests and route metadata.
|
|
728
|
-
*
|
|
729
|
-
* @param options Aggregate contract and endpoint definitions for the REST surface.
|
|
730
|
-
* @returns A JSON-compatible OpenAPI document with paths, components, and auth metadata.
|
|
731
|
-
*/
|
|
732
|
-
export function buildEndpointOpenApiDocument(options) {
|
|
733
|
-
const contractEntries = Object.entries(options.contracts);
|
|
734
|
-
const schemas = Object.fromEntries(contractEntries.map(([contractKey, contract]) => {
|
|
735
|
-
const projectedSchema = projectJsonSchemaDocument(manifestToJsonSchema(contract.document), {
|
|
736
|
-
profile: "rest",
|
|
737
|
-
});
|
|
738
|
-
delete projectedSchema.$schema;
|
|
739
|
-
return [getContractSchemaName(contractKey, contract), projectedSchema];
|
|
740
|
-
}));
|
|
741
|
-
const paths = {};
|
|
742
|
-
const topLevelTags = [...new Set(options.endpoints.flatMap((endpoint) => endpoint.tags))]
|
|
743
|
-
.filter((tag) => typeof tag === "string" && tag.length > 0)
|
|
744
|
-
.map((name) => ({ name }));
|
|
745
|
-
const usesWpRestNonce = options.endpoints.some((endpoint) => normalizeEndpointAuthDefinition(endpoint).wordpressAuth?.mechanism ===
|
|
746
|
-
WP_TYPIA_OPENAPI_LITERALS.WORDPRESS_REST_NONCE_MECHANISM);
|
|
747
|
-
for (const endpoint of options.endpoints) {
|
|
748
|
-
const pathItem = paths[endpoint.path] ?? {};
|
|
749
|
-
pathItem[endpoint.method.toLowerCase()] = buildEndpointOpenApiOperation(endpoint, options.contracts);
|
|
750
|
-
paths[endpoint.path] = pathItem;
|
|
751
|
-
}
|
|
752
|
-
return {
|
|
753
|
-
components: {
|
|
754
|
-
schemas,
|
|
755
|
-
...(usesWpRestNonce
|
|
756
|
-
? {
|
|
757
|
-
securitySchemes: {
|
|
758
|
-
[WP_TYPIA_OPENAPI_LITERALS.WP_REST_NONCE_SCHEME]: {
|
|
759
|
-
description: "WordPress REST nonce sent in the X-WP-Nonce header.",
|
|
760
|
-
in: "header",
|
|
761
|
-
name: WP_TYPIA_OPENAPI_LITERALS.WP_REST_NONCE_HEADER,
|
|
762
|
-
type: "apiKey",
|
|
763
|
-
},
|
|
764
|
-
},
|
|
765
|
-
}
|
|
766
|
-
: {}),
|
|
767
|
-
},
|
|
768
|
-
info: {
|
|
769
|
-
title: options.info?.title ?? "Typia REST API",
|
|
770
|
-
version: options.info?.version ?? "1.0.0",
|
|
771
|
-
...(options.info?.description ? { description: options.info.description } : {}),
|
|
772
|
-
},
|
|
773
|
-
openapi: "3.1.0",
|
|
774
|
-
paths,
|
|
775
|
-
...(topLevelTags.length > 0 ? { tags: topLevelTags } : {}),
|
|
776
|
-
};
|
|
777
|
-
}
|
|
7
|
+
export * from "@wp-typia/block-runtime/schema-core";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wp-typia/project-tools",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.2",
|
|
4
4
|
"description": "Project orchestration and programmatic tooling for wp-typia",
|
|
5
5
|
"packageManager": "bun@1.3.11",
|
|
6
6
|
"type": "module",
|
|
@@ -12,11 +12,56 @@
|
|
|
12
12
|
"import": "./dist/runtime/index.js",
|
|
13
13
|
"default": "./dist/runtime/index.js"
|
|
14
14
|
},
|
|
15
|
+
"./cli-add": {
|
|
16
|
+
"types": "./dist/runtime/cli-add.d.ts",
|
|
17
|
+
"import": "./dist/runtime/cli-add.js",
|
|
18
|
+
"default": "./dist/runtime/cli-add.js"
|
|
19
|
+
},
|
|
20
|
+
"./cli-doctor": {
|
|
21
|
+
"types": "./dist/runtime/cli-doctor.d.ts",
|
|
22
|
+
"import": "./dist/runtime/cli-doctor.js",
|
|
23
|
+
"default": "./dist/runtime/cli-doctor.js"
|
|
24
|
+
},
|
|
25
|
+
"./cli-prompt": {
|
|
26
|
+
"types": "./dist/runtime/cli-prompt.d.ts",
|
|
27
|
+
"import": "./dist/runtime/cli-prompt.js",
|
|
28
|
+
"default": "./dist/runtime/cli-prompt.js"
|
|
29
|
+
},
|
|
30
|
+
"./cli-scaffold": {
|
|
31
|
+
"types": "./dist/runtime/cli-scaffold.d.ts",
|
|
32
|
+
"import": "./dist/runtime/cli-scaffold.js",
|
|
33
|
+
"default": "./dist/runtime/cli-scaffold.js"
|
|
34
|
+
},
|
|
35
|
+
"./cli-templates": {
|
|
36
|
+
"types": "./dist/runtime/cli-templates.d.ts",
|
|
37
|
+
"import": "./dist/runtime/cli-templates.js",
|
|
38
|
+
"default": "./dist/runtime/cli-templates.js"
|
|
39
|
+
},
|
|
40
|
+
"./hooked-blocks": {
|
|
41
|
+
"types": "./dist/runtime/hooked-blocks.d.ts",
|
|
42
|
+
"import": "./dist/runtime/hooked-blocks.js",
|
|
43
|
+
"default": "./dist/runtime/hooked-blocks.js"
|
|
44
|
+
},
|
|
45
|
+
"./migrations": {
|
|
46
|
+
"types": "./dist/runtime/migrations.d.ts",
|
|
47
|
+
"import": "./dist/runtime/migrations.js",
|
|
48
|
+
"default": "./dist/runtime/migrations.js"
|
|
49
|
+
},
|
|
50
|
+
"./package-managers": {
|
|
51
|
+
"types": "./dist/runtime/package-managers.d.ts",
|
|
52
|
+
"import": "./dist/runtime/package-managers.js",
|
|
53
|
+
"default": "./dist/runtime/package-managers.js"
|
|
54
|
+
},
|
|
15
55
|
"./schema-core": {
|
|
16
56
|
"types": "./dist/runtime/schema-core.d.ts",
|
|
17
57
|
"import": "./dist/runtime/schema-core.js",
|
|
18
58
|
"default": "./dist/runtime/schema-core.js"
|
|
19
59
|
},
|
|
60
|
+
"./workspace-project": {
|
|
61
|
+
"types": "./dist/runtime/workspace-project.d.ts",
|
|
62
|
+
"import": "./dist/runtime/workspace-project.js",
|
|
63
|
+
"default": "./dist/runtime/workspace-project.js"
|
|
64
|
+
},
|
|
20
65
|
"./package.json": "./package.json"
|
|
21
66
|
},
|
|
22
67
|
"files": [
|
|
@@ -27,10 +72,18 @@
|
|
|
27
72
|
],
|
|
28
73
|
"scripts": {
|
|
29
74
|
"build": "bun run --filter @wp-typia/api-client build && bun run --filter @wp-typia/block-runtime build && rm -rf dist && tsc -p tsconfig.runtime.json",
|
|
75
|
+
"prepack": "bun run build && node ./scripts/publish-manifest.mjs prepare",
|
|
76
|
+
"postpack": "node ./scripts/publish-manifest.mjs restore",
|
|
30
77
|
"test": "bun run build && bun test tests/*.test.ts",
|
|
78
|
+
"test:scaffold-core": "bun run build && bun test tests/scaffold-basic.test.ts tests/scaffold-persistence.test.ts tests/template-source.test.ts tests/cli-entry.test.ts tests/import-policy.test.ts",
|
|
79
|
+
"test:workspace": "bun run build && bun test tests/workspace-add.test.ts tests/workspace-doctor.test.ts",
|
|
80
|
+
"test:compound": "bun run build && bun test tests/scaffold-compound.test.ts",
|
|
81
|
+
"test:migration-planning": "bun run build && bun test tests/migration-init.test.ts tests/migration-config.test.ts tests/migration-plan-wizard.test.ts",
|
|
82
|
+
"test:migration-execution": "bun run build && bun test tests/migration-scaffold-diff.test.ts tests/migration-doctor.test.ts tests/migration-fixtures-fuzz.test.ts",
|
|
83
|
+
"test:project-tools": "bun run test:scaffold-core && bun run test:workspace && bun run test:compound && bun run test:migration-planning && bun run test:migration-execution",
|
|
31
84
|
"test:coverage": "bun run build && bun test tests/*.test.ts --coverage --coverage-reporter=lcov --coverage-dir=coverage",
|
|
32
85
|
"clean": "rm -rf dist",
|
|
33
|
-
"
|
|
86
|
+
"prepublishOnly": "bun run build"
|
|
34
87
|
},
|
|
35
88
|
"keywords": [
|
|
36
89
|
"wordpress",
|
|
@@ -62,7 +115,7 @@
|
|
|
62
115
|
},
|
|
63
116
|
"dependencies": {
|
|
64
117
|
"@wp-typia/api-client": "^0.4.2",
|
|
65
|
-
"@wp-typia/block-runtime": "^0.4.
|
|
118
|
+
"@wp-typia/block-runtime": "^0.4.5",
|
|
66
119
|
"@wp-typia/rest": "^0.3.5",
|
|
67
120
|
"@wp-typia/block-types": "^0.2.1",
|
|
68
121
|
"mustache": "^4.2.0",
|