@wp-typia/project-tools 0.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (187) hide show
  1. package/README.md +32 -0
  2. package/dist/runtime/cli-add.d.ts +38 -0
  3. package/dist/runtime/cli-add.js +561 -0
  4. package/dist/runtime/cli-core.d.ts +25 -0
  5. package/dist/runtime/cli-core.js +25 -0
  6. package/dist/runtime/cli-doctor.d.ts +34 -0
  7. package/dist/runtime/cli-doctor.js +131 -0
  8. package/dist/runtime/cli-help.d.ts +9 -0
  9. package/dist/runtime/cli-help.js +37 -0
  10. package/dist/runtime/cli-prompt.d.ts +21 -0
  11. package/dist/runtime/cli-prompt.js +53 -0
  12. package/dist/runtime/cli-scaffold.d.ts +79 -0
  13. package/dist/runtime/cli-scaffold.js +206 -0
  14. package/dist/runtime/cli-templates.d.ts +30 -0
  15. package/dist/runtime/cli-templates.js +61 -0
  16. package/dist/runtime/index.d.ts +9 -0
  17. package/dist/runtime/index.js +7 -0
  18. package/dist/runtime/json-utils.d.ts +10 -0
  19. package/dist/runtime/json-utils.js +12 -0
  20. package/dist/runtime/local-dev-presets.d.ts +26 -0
  21. package/dist/runtime/local-dev-presets.js +132 -0
  22. package/dist/runtime/metadata-analysis.d.ts +11 -0
  23. package/dist/runtime/metadata-analysis.js +285 -0
  24. package/dist/runtime/metadata-model.d.ts +84 -0
  25. package/dist/runtime/metadata-model.js +59 -0
  26. package/dist/runtime/metadata-parser.d.ts +53 -0
  27. package/dist/runtime/metadata-parser.js +794 -0
  28. package/dist/runtime/metadata-php-render.d.ts +29 -0
  29. package/dist/runtime/metadata-php-render.js +549 -0
  30. package/dist/runtime/metadata-projection.d.ts +7 -0
  31. package/dist/runtime/metadata-projection.js +233 -0
  32. package/dist/runtime/migration-constants.d.ts +15 -0
  33. package/dist/runtime/migration-constants.js +16 -0
  34. package/dist/runtime/migration-diff.d.ts +2 -0
  35. package/dist/runtime/migration-diff.js +537 -0
  36. package/dist/runtime/migration-fixtures.d.ts +8 -0
  37. package/dist/runtime/migration-fixtures.js +94 -0
  38. package/dist/runtime/migration-fuzz-plan.d.ts +2 -0
  39. package/dist/runtime/migration-fuzz-plan.js +50 -0
  40. package/dist/runtime/migration-manifest.d.ts +19 -0
  41. package/dist/runtime/migration-manifest.js +129 -0
  42. package/dist/runtime/migration-project.d.ts +94 -0
  43. package/dist/runtime/migration-project.js +1101 -0
  44. package/dist/runtime/migration-render.d.ts +11 -0
  45. package/dist/runtime/migration-render.js +741 -0
  46. package/dist/runtime/migration-risk.d.ts +4 -0
  47. package/dist/runtime/migration-risk.js +52 -0
  48. package/dist/runtime/migration-types.d.ts +249 -0
  49. package/dist/runtime/migration-types.js +1 -0
  50. package/dist/runtime/migration-ui-capability.d.ts +17 -0
  51. package/dist/runtime/migration-ui-capability.js +190 -0
  52. package/dist/runtime/migration-utils.d.ts +69 -0
  53. package/dist/runtime/migration-utils.js +246 -0
  54. package/dist/runtime/migrations.d.ts +249 -0
  55. package/dist/runtime/migrations.js +1061 -0
  56. package/dist/runtime/object-utils.d.ts +12 -0
  57. package/dist/runtime/object-utils.js +14 -0
  58. package/dist/runtime/package-managers.d.ts +28 -0
  59. package/dist/runtime/package-managers.js +156 -0
  60. package/dist/runtime/package-versions.d.ts +10 -0
  61. package/dist/runtime/package-versions.js +68 -0
  62. package/dist/runtime/scaffold-onboarding.d.ts +32 -0
  63. package/dist/runtime/scaffold-onboarding.js +99 -0
  64. package/dist/runtime/scaffold.d.ts +146 -0
  65. package/dist/runtime/scaffold.js +612 -0
  66. package/dist/runtime/schema-core.d.ts +267 -0
  67. package/dist/runtime/schema-core.js +597 -0
  68. package/dist/runtime/starter-manifests.d.ts +25 -0
  69. package/dist/runtime/starter-manifests.js +383 -0
  70. package/dist/runtime/string-case.d.ts +36 -0
  71. package/dist/runtime/string-case.js +69 -0
  72. package/dist/runtime/template-builtins.d.ts +38 -0
  73. package/dist/runtime/template-builtins.js +72 -0
  74. package/dist/runtime/template-defaults.d.ts +75 -0
  75. package/dist/runtime/template-defaults.js +65 -0
  76. package/dist/runtime/template-registry.d.ts +36 -0
  77. package/dist/runtime/template-registry.js +94 -0
  78. package/dist/runtime/template-render.d.ts +24 -0
  79. package/dist/runtime/template-render.js +113 -0
  80. package/dist/runtime/template-source.d.ts +71 -0
  81. package/dist/runtime/template-source.js +821 -0
  82. package/dist/runtime/typia-tags.d.ts +1 -0
  83. package/dist/runtime/typia-tags.js +1 -0
  84. package/package.json +79 -0
  85. package/templates/_shared/base/languages/.gitkeep +1 -0
  86. package/templates/_shared/base/package.json.mustache +41 -0
  87. package/templates/_shared/base/scripts/sync-types-to-block-json.ts.mustache +118 -0
  88. package/templates/_shared/base/src/hooks.ts.mustache +19 -0
  89. package/templates/_shared/base/src/validator-toolkit.ts.mustache +31 -0
  90. package/templates/_shared/base/tsconfig.json.mustache +21 -0
  91. package/templates/_shared/base/webpack.config.js.mustache +99 -0
  92. package/templates/_shared/base/{{slugKebabCase}}.php.mustache +53 -0
  93. package/templates/_shared/compound/core/package.json.mustache +45 -0
  94. package/templates/_shared/compound/core/scripts/add-compound-child.ts.mustache +559 -0
  95. package/templates/_shared/compound/core/scripts/block-config.ts.mustache +13 -0
  96. package/templates/_shared/compound/core/scripts/sync-types-to-block-json.ts.mustache +53 -0
  97. package/templates/_shared/compound/core/webpack.config.js.mustache +141 -0
  98. package/templates/_shared/compound/core/{{slugKebabCase}}.php.mustache +51 -0
  99. package/templates/_shared/compound/persistence/package.json.mustache +50 -0
  100. package/templates/_shared/compound/persistence/scripts/block-config.ts.mustache +59 -0
  101. package/templates/_shared/compound/persistence/scripts/sync-rest-contracts.ts.mustache +101 -0
  102. package/templates/_shared/compound/persistence/src/blocks/{{slugKebabCase}}/api-types.ts.mustache +21 -0
  103. package/templates/_shared/compound/persistence/src/blocks/{{slugKebabCase}}/api-validators.ts.mustache +32 -0
  104. package/templates/_shared/compound/persistence/src/blocks/{{slugKebabCase}}/api.ts.mustache +68 -0
  105. package/templates/_shared/compound/persistence/src/blocks/{{slugKebabCase}}/block.json.mustache +52 -0
  106. package/templates/_shared/compound/persistence/src/blocks/{{slugKebabCase}}/data.ts.mustache +192 -0
  107. package/templates/_shared/compound/persistence/src/blocks/{{slugKebabCase}}/edit.tsx.mustache +123 -0
  108. package/templates/_shared/compound/persistence/src/blocks/{{slugKebabCase}}/hooks.ts.mustache +11 -0
  109. package/templates/_shared/compound/persistence/src/blocks/{{slugKebabCase}}/interactivity.ts.mustache +132 -0
  110. package/templates/_shared/compound/persistence/src/blocks/{{slugKebabCase}}/render.php.mustache +158 -0
  111. package/templates/_shared/compound/persistence/src/blocks/{{slugKebabCase}}/save.tsx.mustache +3 -0
  112. package/templates/_shared/compound/persistence/src/blocks/{{slugKebabCase}}/types.ts.mustache +56 -0
  113. package/templates/_shared/compound/persistence/src/blocks/{{slugKebabCase}}/validators.ts.mustache +32 -0
  114. package/templates/_shared/compound/persistence-auth/{{slugKebabCase}}.php.mustache +294 -0
  115. package/templates/_shared/compound/persistence-public/{{slugKebabCase}}.php.mustache +312 -0
  116. package/templates/_shared/migration-ui/common/src/admin/migration-dashboard.tsx +394 -0
  117. package/templates/_shared/migration-ui/common/src/migration-detector.ts +9 -0
  118. package/templates/_shared/migration-ui/common/src/migrations/helpers.ts +490 -0
  119. package/templates/_shared/migration-ui/common/src/migrations/index.ts +886 -0
  120. package/templates/_shared/persistence/auth/{{slugKebabCase}}.php.mustache +290 -0
  121. package/templates/_shared/persistence/core/package.json.mustache +46 -0
  122. package/templates/_shared/persistence/core/scripts/sync-rest-contracts.ts.mustache +113 -0
  123. package/templates/_shared/persistence/core/scripts/sync-types-to-block-json.ts.mustache +125 -0
  124. package/templates/_shared/persistence/core/src/api-types.ts.mustache +21 -0
  125. package/templates/_shared/persistence/core/src/api-validators.ts.mustache +32 -0
  126. package/templates/_shared/persistence/core/src/api.ts.mustache +68 -0
  127. package/templates/_shared/persistence/core/src/data.ts.mustache +192 -0
  128. package/templates/_shared/persistence/core/src/index.tsx.mustache +25 -0
  129. package/templates/_shared/persistence/core/src/interactivity.ts.mustache +134 -0
  130. package/templates/_shared/persistence/core/src/save.tsx.mustache +5 -0
  131. package/templates/_shared/persistence/core/src/validators.ts.mustache +32 -0
  132. package/templates/_shared/persistence/core/{{slugKebabCase}}.php.mustache +336 -0
  133. package/templates/_shared/persistence/public/{{slugKebabCase}}.php.mustache +308 -0
  134. package/templates/_shared/presets/test-preset/.wp-env.test.json.mustache +16 -0
  135. package/templates/_shared/presets/test-preset/playwright.config.ts.mustache +22 -0
  136. package/templates/_shared/presets/test-preset/scripts/wait-for-wp-env.mjs.mustache +102 -0
  137. package/templates/_shared/presets/test-preset/scripts/wp-env-utils.cjs.mustache +32 -0
  138. package/templates/_shared/presets/test-preset/tests/e2e/smoke.spec.ts.mustache +34 -0
  139. package/templates/_shared/presets/wp-env/.wp-env.json.mustache +16 -0
  140. package/templates/_shared/rest-helpers/auth/inc/rest-auth.php.mustache +37 -0
  141. package/templates/_shared/rest-helpers/public/inc/rest-public.php.mustache +314 -0
  142. package/templates/_shared/rest-helpers/shared/inc/rest-shared.php.mustache +58 -0
  143. package/templates/_shared/workspace/persistence-auth/inc/rest-auth.php.mustache +36 -0
  144. package/templates/_shared/workspace/persistence-auth/inc/rest-shared.php.mustache +55 -0
  145. package/templates/_shared/workspace/persistence-auth/server.php.mustache +237 -0
  146. package/templates/_shared/workspace/persistence-public/inc/rest-public.php.mustache +273 -0
  147. package/templates/_shared/workspace/persistence-public/inc/rest-shared.php.mustache +55 -0
  148. package/templates/_shared/workspace/persistence-public/server.php.mustache +252 -0
  149. package/templates/basic/src/block.json.mustache +51 -0
  150. package/templates/basic/src/edit.tsx.mustache +128 -0
  151. package/templates/basic/src/editor.scss.mustache +8 -0
  152. package/templates/basic/src/hooks.ts.mustache +18 -0
  153. package/templates/basic/src/index.tsx.mustache +45 -0
  154. package/templates/basic/src/save.tsx.mustache +30 -0
  155. package/templates/basic/src/style.scss.mustache +40 -0
  156. package/templates/basic/src/types.ts.mustache +56 -0
  157. package/templates/basic/src/validators.ts.mustache +26 -0
  158. package/templates/compound/src/blocks/{{slugKebabCase}}/block.json.mustache +37 -0
  159. package/templates/compound/src/blocks/{{slugKebabCase}}/children.ts.mustache +25 -0
  160. package/templates/compound/src/blocks/{{slugKebabCase}}/edit.tsx.mustache +93 -0
  161. package/templates/compound/src/blocks/{{slugKebabCase}}/hooks.ts.mustache +11 -0
  162. package/templates/compound/src/blocks/{{slugKebabCase}}/index.tsx.mustache +25 -0
  163. package/templates/compound/src/blocks/{{slugKebabCase}}/save.tsx.mustache +32 -0
  164. package/templates/compound/src/blocks/{{slugKebabCase}}/style.scss.mustache +31 -0
  165. package/templates/compound/src/blocks/{{slugKebabCase}}/types.ts.mustache +13 -0
  166. package/templates/compound/src/blocks/{{slugKebabCase}}/validators.ts.mustache +17 -0
  167. package/templates/compound/src/blocks/{{slugKebabCase}}-item/block.json.mustache +35 -0
  168. package/templates/compound/src/blocks/{{slugKebabCase}}-item/edit.tsx.mustache +50 -0
  169. package/templates/compound/src/blocks/{{slugKebabCase}}-item/hooks.ts.mustache +11 -0
  170. package/templates/compound/src/blocks/{{slugKebabCase}}-item/index.tsx.mustache +25 -0
  171. package/templates/compound/src/blocks/{{slugKebabCase}}-item/save.tsx.mustache +24 -0
  172. package/templates/compound/src/blocks/{{slugKebabCase}}-item/types.ts.mustache +12 -0
  173. package/templates/compound/src/blocks/{{slugKebabCase}}-item/validators.ts.mustache +17 -0
  174. package/templates/interactivity/package.json.mustache +42 -0
  175. package/templates/interactivity/src/block.json.mustache +73 -0
  176. package/templates/interactivity/src/edit.tsx.mustache +270 -0
  177. package/templates/interactivity/src/index.tsx.mustache +32 -0
  178. package/templates/interactivity/src/interactivity.ts.mustache +152 -0
  179. package/templates/interactivity/src/save.tsx.mustache +101 -0
  180. package/templates/interactivity/src/style.scss.mustache +60 -0
  181. package/templates/interactivity/src/types.ts.mustache +32 -0
  182. package/templates/interactivity/src/validators.ts.mustache +36 -0
  183. package/templates/persistence/src/block.json.mustache +52 -0
  184. package/templates/persistence/src/edit.tsx.mustache +165 -0
  185. package/templates/persistence/src/render.php.mustache +126 -0
  186. package/templates/persistence/src/style.scss.mustache +46 -0
  187. package/templates/persistence/src/types.ts.mustache +55 -0
@@ -0,0 +1,597 @@
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 projectSchemaArrayItemsForAiStructuredOutput(items, path) {
153
+ return items.map((item, index) => projectSchemaObjectForAiStructuredOutput(item, `${path}/${index}`));
154
+ }
155
+ function projectSchemaPropertyMapForAiStructuredOutput(properties, path) {
156
+ return Object.fromEntries(Object.entries(properties).map(([key, value]) => [
157
+ key,
158
+ projectSchemaObjectForAiStructuredOutput(value, `${path}/${key}`),
159
+ ]));
160
+ }
161
+ function projectSchemaObjectForAiStructuredOutput(node, path) {
162
+ const projectedNode = cloneJsonSchemaNode(node);
163
+ const rawTypeTag = projectedNode[WP_TYPIA_OPENAPI_EXTENSION_KEYS.TYPE_TAG];
164
+ if (typeof rawTypeTag === "string") {
165
+ applyProjectedTypeTag(projectedNode, rawTypeTag, path);
166
+ }
167
+ delete projectedNode[WP_TYPIA_OPENAPI_EXTENSION_KEYS.TYPE_TAG];
168
+ for (const key of Object.keys(projectedNode)) {
169
+ if (isWpTypiaSchemaExtensionKey(key)) {
170
+ delete projectedNode[key];
171
+ continue;
172
+ }
173
+ const child = projectedNode[key];
174
+ if (Array.isArray(child)) {
175
+ projectedNode[key] = child.every(isJsonSchemaObject)
176
+ ? projectSchemaArrayItemsForAiStructuredOutput(child, `${path}/${key}`)
177
+ : child;
178
+ continue;
179
+ }
180
+ if (!isJsonSchemaObject(child)) {
181
+ continue;
182
+ }
183
+ if (key === "properties") {
184
+ projectedNode[key] = projectSchemaPropertyMapForAiStructuredOutput(child, `${path}/${key}`);
185
+ continue;
186
+ }
187
+ projectedNode[key] = projectSchemaObjectForAiStructuredOutput(child, `${path}/${key}`);
188
+ }
189
+ return projectedNode;
190
+ }
191
+ function manifestUnionToJsonSchema(union) {
192
+ const oneOf = Object.entries(union.branches).map(([branchKey, branch]) => {
193
+ if (branch.ts.kind !== "object") {
194
+ throw new Error(`Discriminated union branch "${branchKey}" must be an object to carry "${union.discriminator}".`);
195
+ }
196
+ const schema = manifestAttributeToJsonSchema(branch);
197
+ const objectSchema = addDiscriminatorToObjectBranch(schema, union.discriminator, branchKey);
198
+ if (!objectSchema) {
199
+ throw new Error(`Discriminated union branch "${branchKey}" is missing an object schema for "${union.discriminator}".`);
200
+ }
201
+ return objectSchema;
202
+ });
203
+ return {
204
+ discriminator: {
205
+ propertyName: union.discriminator,
206
+ },
207
+ oneOf,
208
+ };
209
+ }
210
+ /**
211
+ * Converts one manifest attribute definition into a JSON Schema fragment.
212
+ *
213
+ * @param attribute Manifest-derived attribute metadata.
214
+ * @returns A JSON-compatible schema fragment for the attribute.
215
+ */
216
+ export function manifestAttributeToJsonSchema(attribute) {
217
+ if (attribute.ts.union) {
218
+ const schema = manifestUnionToJsonSchema(attribute.ts.union);
219
+ if (attribute.typia.hasDefault) {
220
+ schema.default = attribute.typia.defaultValue ?? null;
221
+ }
222
+ return schema;
223
+ }
224
+ const schema = {};
225
+ const enumValues = Array.isArray(attribute.wp.enum) ? attribute.wp.enum : null;
226
+ if (enumValues && enumValues.length > 0) {
227
+ schema.enum = enumValues;
228
+ }
229
+ if (attribute.typia.hasDefault) {
230
+ schema.default = attribute.typia.defaultValue ?? null;
231
+ }
232
+ switch (attribute.ts.kind) {
233
+ case "string":
234
+ schema.type = "string";
235
+ break;
236
+ case "number":
237
+ schema.type = "number";
238
+ break;
239
+ case "boolean":
240
+ schema.type = "boolean";
241
+ break;
242
+ case "array":
243
+ schema.type = "array";
244
+ if (attribute.ts.items) {
245
+ schema.items = manifestAttributeToJsonSchema(attribute.ts.items);
246
+ }
247
+ break;
248
+ case "object": {
249
+ schema.type = "object";
250
+ schema.additionalProperties = false;
251
+ const properties = attribute.ts.properties ?? {};
252
+ schema.properties = Object.fromEntries(Object.entries(properties).map(([key, value]) => [
253
+ key,
254
+ manifestAttributeToJsonSchema(value),
255
+ ]));
256
+ const required = Object.entries(properties)
257
+ .filter(([, value]) => value.ts.required !== false)
258
+ .map(([key]) => key);
259
+ if (required.length > 0) {
260
+ schema.required = required;
261
+ }
262
+ break;
263
+ }
264
+ case "union":
265
+ if (attribute.ts.union) {
266
+ return manifestUnionToJsonSchema(attribute.ts.union);
267
+ }
268
+ schema.oneOf = [];
269
+ break;
270
+ default:
271
+ schema.type = attribute.wp.type ?? "string";
272
+ break;
273
+ }
274
+ applyCommonConstraints(schema, attribute.typia.constraints);
275
+ return schema;
276
+ }
277
+ /**
278
+ * Builds a full JSON Schema document from a Typia manifest document.
279
+ *
280
+ * @param doc Manifest-derived attribute document.
281
+ * @returns A draft 2020-12 JSON Schema document for the manifest root object.
282
+ */
283
+ export function manifestToJsonSchema(doc) {
284
+ const attributes = doc.attributes ?? {};
285
+ return {
286
+ $schema: "https://json-schema.org/draft/2020-12/schema",
287
+ additionalProperties: false,
288
+ properties: Object.fromEntries(Object.entries(attributes).map(([key, value]) => [
289
+ key,
290
+ manifestAttributeToJsonSchema(value),
291
+ ])),
292
+ required: Object.entries(attributes)
293
+ .filter(([, value]) => value.ts.required !== false)
294
+ .map(([key]) => key),
295
+ title: doc.sourceType ?? "TypiaDocument",
296
+ type: "object",
297
+ };
298
+ }
299
+ /**
300
+ * Projects one generated wp-typia JSON Schema document into a consumer-facing profile.
301
+ *
302
+ * @param schema Existing generated JSON Schema document.
303
+ * @param options Projection profile options.
304
+ * @returns A cloned schema document adjusted for the requested profile.
305
+ */
306
+ export function projectJsonSchemaDocument(schema, options) {
307
+ if (options.profile === "rest") {
308
+ return cloneJsonSchemaNode(schema);
309
+ }
310
+ if (options.profile === "ai-structured-output") {
311
+ return projectSchemaObjectForAiStructuredOutput(schema, "#");
312
+ }
313
+ throw new Error(`Unsupported JSON Schema projection profile "${String(options.profile)}".`);
314
+ }
315
+ /**
316
+ * Wraps a manifest-derived JSON Schema document in a minimal OpenAPI 3.1 shell.
317
+ *
318
+ * @param doc Manifest-derived attribute document.
319
+ * @param info Optional OpenAPI document metadata.
320
+ * @returns An OpenAPI document containing the schema as a single component.
321
+ */
322
+ export function manifestToOpenApi(doc, info = {}) {
323
+ const schemaName = doc.sourceType ?? "TypiaDocument";
324
+ return {
325
+ components: {
326
+ schemas: {
327
+ [schemaName]: manifestToJsonSchema(doc),
328
+ },
329
+ },
330
+ info: {
331
+ title: info.title ?? schemaName,
332
+ version: info.version ?? "1.0.0",
333
+ ...(info.description ? { description: info.description } : {}),
334
+ },
335
+ openapi: "3.1.0",
336
+ paths: {},
337
+ };
338
+ }
339
+ function createOpenApiSchemaRef(schemaName) {
340
+ return {
341
+ $ref: `#/components/schemas/${schemaName}`,
342
+ };
343
+ }
344
+ function formatEndpointDescription(endpoint) {
345
+ return `${endpoint.operationId} (${endpoint.method} ${endpoint.path})`;
346
+ }
347
+ function normalizeWordPressAuthDefinition(wordpressAuth) {
348
+ if (!wordpressAuth) {
349
+ return undefined;
350
+ }
351
+ if (wordpressAuth.mechanism !==
352
+ WP_TYPIA_OPENAPI_LITERALS.WORDPRESS_PUBLIC_TOKEN_MECHANISM) {
353
+ return {
354
+ mechanism: wordpressAuth.mechanism,
355
+ };
356
+ }
357
+ return {
358
+ mechanism: wordpressAuth.mechanism,
359
+ publicTokenField: wordpressAuth.publicTokenField ??
360
+ WP_TYPIA_OPENAPI_LITERALS.PUBLIC_WRITE_TOKEN_FIELD,
361
+ };
362
+ }
363
+ function deriveLegacyAuthModeFromNormalizedAuth(auth, wordpressAuth) {
364
+ if (auth === "public") {
365
+ return "public-read";
366
+ }
367
+ if (auth === "authenticated" &&
368
+ wordpressAuth?.mechanism ===
369
+ WP_TYPIA_OPENAPI_LITERALS.WORDPRESS_REST_NONCE_MECHANISM) {
370
+ return "authenticated-rest-nonce";
371
+ }
372
+ if (auth === "public-write-protected" &&
373
+ wordpressAuth?.mechanism ===
374
+ WP_TYPIA_OPENAPI_LITERALS.WORDPRESS_PUBLIC_TOKEN_MECHANISM) {
375
+ return "public-signed-token";
376
+ }
377
+ return undefined;
378
+ }
379
+ function compareNormalizedEndpointAuth(left, right) {
380
+ return (left.auth === right.auth &&
381
+ left.authMode === right.authMode &&
382
+ left.wordpressAuth?.mechanism === right.wordpressAuth?.mechanism &&
383
+ left.wordpressAuth?.publicTokenField === right.wordpressAuth?.publicTokenField);
384
+ }
385
+ /**
386
+ * Normalizes endpoint auth metadata into backend-neutral intent plus optional
387
+ * WordPress adapter details.
388
+ *
389
+ * This public runtime helper accepts either the authored `auth` and optional
390
+ * `wordpressAuth` shape or the deprecated `authMode` field. It validates that
391
+ * the provided fields are compatible, resolves the legacy compatibility mode,
392
+ * and returns a normalized definition without mutating the input endpoint.
393
+ *
394
+ * @param endpoint - Endpoint auth fields to normalize, including `auth`,
395
+ * `authMode`, `wordpressAuth`, and optional identity fields used in error
396
+ * messages (`operationId`, `path`, and `method`).
397
+ * @returns The normalized auth definition for the endpoint.
398
+ * @throws When `auth` and deprecated `authMode` conflict.
399
+ * @throws When `wordpressAuth` is attached to the `public` auth intent.
400
+ * @throws When the selected `wordpressAuth` mechanism is incompatible with the
401
+ * chosen auth intent.
402
+ * @throws When neither `auth` nor deprecated `authMode` is defined.
403
+ */
404
+ export function normalizeEndpointAuthDefinition(endpoint) {
405
+ const endpointDescription = typeof endpoint.operationId === "string" &&
406
+ typeof endpoint.path === "string" &&
407
+ typeof endpoint.method === "string"
408
+ ? formatEndpointDescription(endpoint)
409
+ : "the current endpoint";
410
+ const nextWordPressAuth = normalizeWordPressAuthDefinition(endpoint.wordpressAuth);
411
+ let normalized = null;
412
+ if (endpoint.auth) {
413
+ normalized = {
414
+ auth: endpoint.auth,
415
+ authMode: deriveLegacyAuthModeFromNormalizedAuth(endpoint.auth, nextWordPressAuth),
416
+ ...(nextWordPressAuth ? { wordpressAuth: nextWordPressAuth } : {}),
417
+ };
418
+ if (endpoint.auth === "public" && nextWordPressAuth) {
419
+ throw new Error(`Endpoint "${endpointDescription}" cannot attach wordpressAuth when auth intent is "public".`);
420
+ }
421
+ if (endpoint.auth === "authenticated" &&
422
+ nextWordPressAuth?.mechanism ===
423
+ WP_TYPIA_OPENAPI_LITERALS.WORDPRESS_PUBLIC_TOKEN_MECHANISM) {
424
+ throw new Error(`Endpoint "${endpointDescription}" uses auth intent "authenticated" but wordpressAuth mechanism "${nextWordPressAuth.mechanism}" only supports "public-write-protected".`);
425
+ }
426
+ if (endpoint.auth === "public-write-protected" &&
427
+ nextWordPressAuth?.mechanism ===
428
+ WP_TYPIA_OPENAPI_LITERALS.WORDPRESS_REST_NONCE_MECHANISM) {
429
+ throw new Error(`Endpoint "${endpointDescription}" uses auth intent "public-write-protected" but wordpressAuth mechanism "${nextWordPressAuth.mechanism}" only supports "authenticated".`);
430
+ }
431
+ }
432
+ let legacyNormalized = null;
433
+ if (endpoint.authMode) {
434
+ legacyNormalized =
435
+ endpoint.authMode === "public-read"
436
+ ? {
437
+ auth: "public",
438
+ authMode: "public-read",
439
+ }
440
+ : endpoint.authMode === "authenticated-rest-nonce"
441
+ ? {
442
+ auth: "authenticated",
443
+ authMode: "authenticated-rest-nonce",
444
+ wordpressAuth: {
445
+ mechanism: WP_TYPIA_OPENAPI_LITERALS.WORDPRESS_REST_NONCE_MECHANISM,
446
+ },
447
+ }
448
+ : {
449
+ auth: "public-write-protected",
450
+ authMode: "public-signed-token",
451
+ wordpressAuth: {
452
+ mechanism: WP_TYPIA_OPENAPI_LITERALS.WORDPRESS_PUBLIC_TOKEN_MECHANISM,
453
+ publicTokenField: WP_TYPIA_OPENAPI_LITERALS.PUBLIC_WRITE_TOKEN_FIELD,
454
+ },
455
+ };
456
+ }
457
+ if (normalized && legacyNormalized) {
458
+ if (!compareNormalizedEndpointAuth(normalized, legacyNormalized)) {
459
+ throw new Error(`Endpoint "${endpointDescription}" defines conflicting auth metadata between auth/wordpressAuth and deprecated authMode.`);
460
+ }
461
+ return normalized;
462
+ }
463
+ if (normalized) {
464
+ return normalized;
465
+ }
466
+ if (legacyNormalized) {
467
+ return legacyNormalized;
468
+ }
469
+ throw new Error(`Endpoint "${endpointDescription}" must define either auth or deprecated authMode.`);
470
+ }
471
+ function getContractSchemaName(contractKey, contract, endpoint, role = "contract") {
472
+ if (!contract) {
473
+ if (endpoint) {
474
+ throw new Error(`Missing ${role} contract "${contractKey}" while building endpoint "${formatEndpointDescription(endpoint)}"`);
475
+ }
476
+ throw new Error(`Missing OpenAPI contract definition for "${contractKey}"`);
477
+ }
478
+ return contract.schemaName ?? contract.document.sourceType ?? contractKey;
479
+ }
480
+ function buildQueryParameters(contract) {
481
+ const attributes = contract.document.attributes ?? {};
482
+ return Object.entries(attributes).map(([name, attribute]) => ({
483
+ in: WP_TYPIA_OPENAPI_LITERALS.QUERY_LOCATION,
484
+ name,
485
+ required: attribute.ts.required !== false,
486
+ schema: manifestAttributeToJsonSchema(attribute),
487
+ }));
488
+ }
489
+ function createSuccessResponse(schemaName) {
490
+ return {
491
+ content: {
492
+ [WP_TYPIA_OPENAPI_LITERALS.JSON_CONTENT_TYPE]: {
493
+ schema: createOpenApiSchemaRef(schemaName),
494
+ },
495
+ },
496
+ description: WP_TYPIA_OPENAPI_LITERALS.SUCCESS_RESPONSE_DESCRIPTION,
497
+ };
498
+ }
499
+ function buildEndpointOpenApiOperation(endpoint, contracts) {
500
+ const normalizedAuth = normalizeEndpointAuthDefinition(endpoint);
501
+ const operation = {
502
+ operationId: endpoint.operationId,
503
+ responses: {
504
+ "200": createSuccessResponse(getContractSchemaName(endpoint.responseContract, contracts[endpoint.responseContract], endpoint, "response")),
505
+ },
506
+ tags: [...endpoint.tags],
507
+ [WP_TYPIA_OPENAPI_EXTENSION_KEYS.AUTH_INTENT]: normalizedAuth.auth,
508
+ ...(normalizedAuth.authMode
509
+ ? {
510
+ [WP_TYPIA_OPENAPI_EXTENSION_KEYS.AUTH_POLICY]: normalizedAuth.authMode,
511
+ }
512
+ : {}),
513
+ };
514
+ if (typeof endpoint.summary === "string" && endpoint.summary.length > 0) {
515
+ operation.summary = endpoint.summary;
516
+ }
517
+ if (typeof endpoint.queryContract === "string") {
518
+ operation.parameters = buildQueryParameters(contracts[endpoint.queryContract] ??
519
+ (() => {
520
+ throw new Error(`Missing query contract "${endpoint.queryContract}" while building endpoint "${formatEndpointDescription(endpoint)}"`);
521
+ })());
522
+ }
523
+ if (typeof endpoint.bodyContract === "string") {
524
+ operation.requestBody = {
525
+ content: {
526
+ [WP_TYPIA_OPENAPI_LITERALS.JSON_CONTENT_TYPE]: {
527
+ schema: createOpenApiSchemaRef(getContractSchemaName(endpoint.bodyContract, contracts[endpoint.bodyContract], endpoint, "request body")),
528
+ },
529
+ },
530
+ required: true,
531
+ };
532
+ }
533
+ if (normalizedAuth.wordpressAuth?.mechanism ===
534
+ WP_TYPIA_OPENAPI_LITERALS.WORDPRESS_REST_NONCE_MECHANISM) {
535
+ operation.security = [
536
+ {
537
+ [WP_TYPIA_OPENAPI_LITERALS.WP_REST_NONCE_SCHEME]: [],
538
+ },
539
+ ];
540
+ }
541
+ else if (normalizedAuth.wordpressAuth?.mechanism ===
542
+ WP_TYPIA_OPENAPI_LITERALS.WORDPRESS_PUBLIC_TOKEN_MECHANISM) {
543
+ operation[WP_TYPIA_OPENAPI_EXTENSION_KEYS.PUBLIC_TOKEN_FIELD] =
544
+ normalizedAuth.wordpressAuth.publicTokenField ??
545
+ WP_TYPIA_OPENAPI_LITERALS.PUBLIC_WRITE_TOKEN_FIELD;
546
+ }
547
+ return operation;
548
+ }
549
+ /**
550
+ * Build a complete OpenAPI 3.1 document from contract manifests and route metadata.
551
+ *
552
+ * @param options Aggregate contract and endpoint definitions for the REST surface.
553
+ * @returns A JSON-compatible OpenAPI document with paths, components, and auth metadata.
554
+ */
555
+ export function buildEndpointOpenApiDocument(options) {
556
+ const contractEntries = Object.entries(options.contracts);
557
+ const schemas = Object.fromEntries(contractEntries.map(([contractKey, contract]) => [
558
+ getContractSchemaName(contractKey, contract),
559
+ manifestToJsonSchema(contract.document),
560
+ ]));
561
+ const paths = {};
562
+ const topLevelTags = [...new Set(options.endpoints.flatMap((endpoint) => endpoint.tags))]
563
+ .filter((tag) => typeof tag === "string" && tag.length > 0)
564
+ .map((name) => ({ name }));
565
+ const usesWpRestNonce = options.endpoints.some((endpoint) => normalizeEndpointAuthDefinition(endpoint).wordpressAuth?.mechanism ===
566
+ WP_TYPIA_OPENAPI_LITERALS.WORDPRESS_REST_NONCE_MECHANISM);
567
+ for (const endpoint of options.endpoints) {
568
+ const pathItem = paths[endpoint.path] ?? {};
569
+ pathItem[endpoint.method.toLowerCase()] = buildEndpointOpenApiOperation(endpoint, options.contracts);
570
+ paths[endpoint.path] = pathItem;
571
+ }
572
+ return {
573
+ components: {
574
+ schemas,
575
+ ...(usesWpRestNonce
576
+ ? {
577
+ securitySchemes: {
578
+ [WP_TYPIA_OPENAPI_LITERALS.WP_REST_NONCE_SCHEME]: {
579
+ description: "WordPress REST nonce sent in the X-WP-Nonce header.",
580
+ in: "header",
581
+ name: WP_TYPIA_OPENAPI_LITERALS.WP_REST_NONCE_HEADER,
582
+ type: "apiKey",
583
+ },
584
+ },
585
+ }
586
+ : {}),
587
+ },
588
+ info: {
589
+ title: options.info?.title ?? "Typia REST API",
590
+ version: options.info?.version ?? "1.0.0",
591
+ ...(options.info?.description ? { description: options.info.description } : {}),
592
+ },
593
+ openapi: "3.1.0",
594
+ paths,
595
+ ...(topLevelTags.length > 0 ? { tags: topLevelTags } : {}),
596
+ };
597
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Scaffold-time starter manifest builders for generated projects.
3
+ *
4
+ * These helpers create placeholder `typia.manifest.json` artifacts so fresh
5
+ * scaffolds can resolve runtime imports before the first canonical sync run.
6
+ */
7
+ import type { ManifestDocument } from "./migration-types.js";
8
+ import type { ScaffoldTemplateVariables } from "./scaffold.js";
9
+ /**
10
+ * Builds the starter manifest used by generated compound child blocks.
11
+ */
12
+ export declare function buildCompoundChildStarterManifestDocument(childTypeName: string, childTitle: string, bodyPlaceholder?: string): ManifestDocument;
13
+ /**
14
+ * Returns the starter manifest files that should be seeded for a built-in
15
+ * template before the first sync.
16
+ */
17
+ export declare function getStarterManifestFiles(templateId: string, variables: ScaffoldTemplateVariables): Array<{
18
+ document: ManifestDocument;
19
+ relativePath: string;
20
+ }>;
21
+ /**
22
+ * Serializes a starter manifest using the generated-project JSON formatting
23
+ * convention.
24
+ */
25
+ export declare function stringifyStarterManifest(document: ManifestDocument): string;