@wp-typia/project-tools 0.23.0 → 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (228) hide show
  1. package/dist/runtime/ai-feature-artifacts.js +4 -1
  2. package/dist/runtime/block-generator-service-spec.js +2 -1
  3. package/dist/runtime/built-in-block-non-ts-basic-artifacts.d.ts +9 -0
  4. package/dist/runtime/built-in-block-non-ts-basic-artifacts.js +84 -0
  5. package/dist/runtime/built-in-block-non-ts-compound-artifacts.d.ts +9 -0
  6. package/dist/runtime/built-in-block-non-ts-compound-artifacts.js +36 -0
  7. package/dist/runtime/built-in-block-non-ts-compound-templates.d.ts +23 -0
  8. package/dist/runtime/built-in-block-non-ts-compound-templates.js +453 -0
  9. package/dist/runtime/built-in-block-non-ts-family-artifacts.d.ts +8 -26
  10. package/dist/runtime/built-in-block-non-ts-family-artifacts.js +8 -1034
  11. package/dist/runtime/built-in-block-non-ts-interactivity-artifacts.d.ts +9 -0
  12. package/dist/runtime/built-in-block-non-ts-interactivity-artifacts.js +83 -0
  13. package/dist/runtime/built-in-block-non-ts-persistence-artifacts.d.ts +9 -0
  14. package/dist/runtime/built-in-block-non-ts-persistence-artifacts.js +33 -0
  15. package/dist/runtime/built-in-block-non-ts-persistence-templates.d.ts +23 -0
  16. package/dist/runtime/built-in-block-non-ts-persistence-templates.js +395 -0
  17. package/dist/runtime/cli-add-block-json.js +5 -1
  18. package/dist/runtime/cli-add-collision.js +8 -0
  19. package/dist/runtime/cli-add-help.js +14 -10
  20. package/dist/runtime/cli-add-kind-ids.d.ts +1 -1
  21. package/dist/runtime/cli-add-kind-ids.js +1 -0
  22. package/dist/runtime/cli-add-types.d.ts +45 -6
  23. package/dist/runtime/cli-add-types.js +2 -0
  24. package/dist/runtime/cli-add-validation.d.ts +7 -0
  25. package/dist/runtime/cli-add-validation.js +9 -0
  26. package/dist/runtime/cli-add-workspace-ability-anchors.d.ts +24 -0
  27. package/dist/runtime/cli-add-workspace-ability-anchors.js +294 -0
  28. package/dist/runtime/cli-add-workspace-ability-registry.d.ts +10 -0
  29. package/dist/runtime/cli-add-workspace-ability-registry.js +51 -0
  30. package/dist/runtime/cli-add-workspace-ability-scaffold.d.ts +1 -1
  31. package/dist/runtime/cli-add-workspace-ability-scaffold.js +5 -308
  32. package/dist/runtime/cli-add-workspace-admin-view-scaffold.js +6 -2
  33. package/dist/runtime/cli-add-workspace-admin-view-templates-core-data.d.ts +34 -0
  34. package/dist/runtime/cli-add-workspace-admin-view-templates-core-data.js +483 -0
  35. package/dist/runtime/cli-add-workspace-admin-view-templates-default.d.ts +30 -0
  36. package/dist/runtime/cli-add-workspace-admin-view-templates-default.js +310 -0
  37. package/dist/runtime/cli-add-workspace-admin-view-templates-rest.d.ts +25 -0
  38. package/dist/runtime/cli-add-workspace-admin-view-templates-rest.js +124 -0
  39. package/dist/runtime/cli-add-workspace-admin-view-templates-settings.d.ts +34 -0
  40. package/dist/runtime/cli-add-workspace-admin-view-templates-settings.js +370 -0
  41. package/dist/runtime/cli-add-workspace-admin-view-templates-shared.d.ts +49 -0
  42. package/dist/runtime/cli-add-workspace-admin-view-templates-shared.js +259 -0
  43. package/dist/runtime/cli-add-workspace-admin-view-templates.d.ts +18 -27
  44. package/dist/runtime/cli-add-workspace-admin-view-templates.js +30 -1326
  45. package/dist/runtime/cli-add-workspace-ai-anchors.d.ts +4 -4
  46. package/dist/runtime/cli-add-workspace-ai-anchors.js +8 -233
  47. package/dist/runtime/cli-add-workspace-ai-scaffold.js +4 -2
  48. package/dist/runtime/cli-add-workspace-ai-source-emitters.d.ts +1 -4
  49. package/dist/runtime/cli-add-workspace-ai-source-emitters.js +1 -129
  50. package/dist/runtime/cli-add-workspace-ai-sync-rest-anchors.d.ts +5 -0
  51. package/dist/runtime/cli-add-workspace-ai-sync-rest-anchors.js +236 -0
  52. package/dist/runtime/cli-add-workspace-ai-sync-script-source.d.ts +4 -0
  53. package/dist/runtime/cli-add-workspace-ai-sync-script-source.js +145 -0
  54. package/dist/runtime/cli-add-workspace-assets.d.ts +6 -63
  55. package/dist/runtime/cli-add-workspace-assets.js +6 -950
  56. package/dist/runtime/cli-add-workspace-binding-source-anchors.d.ts +23 -0
  57. package/dist/runtime/cli-add-workspace-binding-source-anchors.js +112 -0
  58. package/dist/runtime/cli-add-workspace-binding-source-source-emitters.d.ts +33 -0
  59. package/dist/runtime/cli-add-workspace-binding-source-source-emitters.js +436 -0
  60. package/dist/runtime/cli-add-workspace-binding-source-types.d.ts +20 -0
  61. package/dist/runtime/cli-add-workspace-binding-source-types.js +1 -0
  62. package/dist/runtime/cli-add-workspace-binding-source.d.ts +40 -0
  63. package/dist/runtime/cli-add-workspace-binding-source.js +275 -0
  64. package/dist/runtime/cli-add-workspace-block-style.d.ts +22 -0
  65. package/dist/runtime/cli-add-workspace-block-style.js +148 -0
  66. package/dist/runtime/cli-add-workspace-block-transform.d.ts +32 -0
  67. package/dist/runtime/cli-add-workspace-block-transform.js +197 -0
  68. package/dist/runtime/cli-add-workspace-contract.js +1 -1
  69. package/dist/runtime/cli-add-workspace-core-variation.d.ts +20 -0
  70. package/dist/runtime/cli-add-workspace-core-variation.js +322 -0
  71. package/dist/runtime/cli-add-workspace-editor-plugin-anchors.d.ts +37 -0
  72. package/dist/runtime/cli-add-workspace-editor-plugin-anchors.js +206 -0
  73. package/dist/runtime/cli-add-workspace-editor-plugin-source-emitters.d.ts +47 -0
  74. package/dist/runtime/cli-add-workspace-editor-plugin-source-emitters.js +219 -0
  75. package/dist/runtime/cli-add-workspace-editor-plugin.d.ts +22 -0
  76. package/dist/runtime/cli-add-workspace-editor-plugin.js +78 -0
  77. package/dist/runtime/cli-add-workspace-hooked-block.d.ts +23 -0
  78. package/dist/runtime/cli-add-workspace-hooked-block.js +57 -0
  79. package/dist/runtime/cli-add-workspace-integration-env-files.d.ts +33 -0
  80. package/dist/runtime/cli-add-workspace-integration-env-files.js +65 -0
  81. package/dist/runtime/cli-add-workspace-integration-env-package-json.d.ts +38 -0
  82. package/dist/runtime/cli-add-workspace-integration-env-package-json.js +122 -0
  83. package/dist/runtime/cli-add-workspace-integration-env-source-emitters.d.ts +44 -0
  84. package/dist/runtime/cli-add-workspace-integration-env-source-emitters.js +262 -0
  85. package/dist/runtime/cli-add-workspace-integration-env.d.ts +3 -1
  86. package/dist/runtime/cli-add-workspace-integration-env.js +10 -313
  87. package/dist/runtime/cli-add-workspace-pattern-anchors.d.ts +10 -0
  88. package/dist/runtime/cli-add-workspace-pattern-anchors.js +95 -0
  89. package/dist/runtime/cli-add-workspace-pattern-options.d.ts +20 -0
  90. package/dist/runtime/cli-add-workspace-pattern-options.js +113 -0
  91. package/dist/runtime/cli-add-workspace-pattern-source-emitters.d.ts +20 -0
  92. package/dist/runtime/cli-add-workspace-pattern-source-emitters.js +57 -0
  93. package/dist/runtime/cli-add-workspace-pattern.d.ts +42 -0
  94. package/dist/runtime/cli-add-workspace-pattern.js +99 -0
  95. package/dist/runtime/cli-add-workspace-post-meta.js +1 -1
  96. package/dist/runtime/cli-add-workspace-registration-hooks.d.ts +50 -0
  97. package/dist/runtime/cli-add-workspace-registration-hooks.js +162 -0
  98. package/dist/runtime/cli-add-workspace-rest-anchors.d.ts +9 -4
  99. package/dist/runtime/cli-add-workspace-rest-anchors.js +9 -428
  100. package/dist/runtime/cli-add-workspace-rest-bootstrap-anchors.d.ts +17 -0
  101. package/dist/runtime/cli-add-workspace-rest-bootstrap-anchors.js +108 -0
  102. package/dist/runtime/cli-add-workspace-rest-contract-sync-anchors.d.ts +9 -0
  103. package/dist/runtime/cli-add-workspace-rest-contract-sync-anchors.js +142 -0
  104. package/dist/runtime/cli-add-workspace-rest-generated-source-emitters.d.ts +51 -0
  105. package/dist/runtime/cli-add-workspace-rest-generated-source-emitters.js +415 -0
  106. package/dist/runtime/cli-add-workspace-rest-generated.d.ts +9 -0
  107. package/dist/runtime/cli-add-workspace-rest-generated.js +160 -0
  108. package/dist/runtime/cli-add-workspace-rest-manual-source-emitters.d.ts +80 -0
  109. package/dist/runtime/cli-add-workspace-rest-manual-source-emitters.js +238 -0
  110. package/dist/runtime/cli-add-workspace-rest-manual.d.ts +8 -0
  111. package/dist/runtime/cli-add-workspace-rest-manual.js +266 -0
  112. package/dist/runtime/cli-add-workspace-rest-php-templates.d.ts +18 -0
  113. package/dist/runtime/cli-add-workspace-rest-php-templates.js +359 -0
  114. package/dist/runtime/cli-add-workspace-rest-resource-php-routing-template.d.ts +33 -0
  115. package/dist/runtime/cli-add-workspace-rest-resource-php-routing-template.js +145 -0
  116. package/dist/runtime/cli-add-workspace-rest-resource-sync-anchors.d.ts +9 -0
  117. package/dist/runtime/cli-add-workspace-rest-resource-sync-anchors.js +162 -0
  118. package/dist/runtime/cli-add-workspace-rest-schema-helper-php-template.d.ts +7 -0
  119. package/dist/runtime/cli-add-workspace-rest-schema-helper-php-template.js +193 -0
  120. package/dist/runtime/cli-add-workspace-rest-source-emitters.d.ts +5 -91
  121. package/dist/runtime/cli-add-workspace-rest-source-emitters.js +5 -642
  122. package/dist/runtime/cli-add-workspace-rest-source-utils.d.ts +17 -0
  123. package/dist/runtime/cli-add-workspace-rest-source-utils.js +50 -0
  124. package/dist/runtime/cli-add-workspace-rest-sync-script-shared.d.ts +56 -0
  125. package/dist/runtime/cli-add-workspace-rest-sync-script-shared.js +122 -0
  126. package/dist/runtime/cli-add-workspace-rest-types.d.ts +108 -0
  127. package/dist/runtime/cli-add-workspace-rest-types.js +1 -0
  128. package/dist/runtime/cli-add-workspace-rest.d.ts +3 -20
  129. package/dist/runtime/cli-add-workspace-rest.js +33 -788
  130. package/dist/runtime/cli-add-workspace-variation.d.ts +22 -0
  131. package/dist/runtime/cli-add-workspace-variation.js +162 -0
  132. package/dist/runtime/cli-add-workspace.d.ts +42 -107
  133. package/dist/runtime/cli-add-workspace.js +42 -674
  134. package/dist/runtime/cli-add.d.ts +3 -3
  135. package/dist/runtime/cli-add.js +2 -2
  136. package/dist/runtime/cli-core.d.ts +3 -2
  137. package/dist/runtime/cli-core.js +2 -2
  138. package/dist/runtime/cli-diagnostics.d.ts +3 -1
  139. package/dist/runtime/cli-diagnostics.js +17 -5
  140. package/dist/runtime/cli-doctor-workspace-bindings.js +63 -1
  141. package/dist/runtime/cli-doctor-workspace-block-addons.d.ts +12 -0
  142. package/dist/runtime/cli-doctor-workspace-block-addons.js +162 -0
  143. package/dist/runtime/cli-doctor-workspace-block-iframe.d.ts +9 -0
  144. package/dist/runtime/cli-doctor-workspace-block-iframe.js +228 -0
  145. package/dist/runtime/cli-doctor-workspace-block-metadata.d.ts +11 -0
  146. package/dist/runtime/cli-doctor-workspace-block-metadata.js +111 -0
  147. package/dist/runtime/cli-doctor-workspace-blocks.js +6 -424
  148. package/dist/runtime/cli-doctor-workspace-features-abilities.d.ts +11 -0
  149. package/dist/runtime/cli-doctor-workspace-features-abilities.js +112 -0
  150. package/dist/runtime/cli-doctor-workspace-features-admin-views.d.ts +11 -0
  151. package/dist/runtime/cli-doctor-workspace-features-admin-views.js +128 -0
  152. package/dist/runtime/cli-doctor-workspace-features-ai.d.ts +11 -0
  153. package/dist/runtime/cli-doctor-workspace-features-ai.js +57 -0
  154. package/dist/runtime/cli-doctor-workspace-features-editor-plugins.d.ts +11 -0
  155. package/dist/runtime/cli-doctor-workspace-features-editor-plugins.js +80 -0
  156. package/dist/runtime/cli-doctor-workspace-features-post-meta.d.ts +11 -0
  157. package/dist/runtime/cli-doctor-workspace-features-post-meta.js +77 -0
  158. package/dist/runtime/cli-doctor-workspace-features-rest.d.ts +11 -0
  159. package/dist/runtime/cli-doctor-workspace-features-rest.js +120 -0
  160. package/dist/runtime/cli-doctor-workspace-features.js +14 -487
  161. package/dist/runtime/cli-doctor.d.ts +54 -3
  162. package/dist/runtime/cli-doctor.js +92 -10
  163. package/dist/runtime/cli-help.js +12 -7
  164. package/dist/runtime/cli-init-package-json.js +4 -2
  165. package/dist/runtime/cli-prompt.d.ts +16 -2
  166. package/dist/runtime/cli-prompt.js +29 -12
  167. package/dist/runtime/cli-scaffold.d.ts +2 -1
  168. package/dist/runtime/cli-scaffold.js +19 -10
  169. package/dist/runtime/external-template-guards.js +4 -6
  170. package/dist/runtime/index.d.ts +6 -3
  171. package/dist/runtime/index.js +4 -2
  172. package/dist/runtime/json-utils.d.ts +62 -4
  173. package/dist/runtime/json-utils.js +78 -4
  174. package/dist/runtime/local-dev-presets.js +6 -2
  175. package/dist/runtime/migration-ui-capability.js +4 -1
  176. package/dist/runtime/migration-utils.js +4 -1
  177. package/dist/runtime/package-managers.js +6 -1
  178. package/dist/runtime/package-versions.d.ts +1 -0
  179. package/dist/runtime/package-versions.js +16 -3
  180. package/dist/runtime/pattern-catalog.d.ts +122 -0
  181. package/dist/runtime/pattern-catalog.js +471 -0
  182. package/dist/runtime/post-meta-binding-fields.d.ts +46 -0
  183. package/dist/runtime/post-meta-binding-fields.js +135 -0
  184. package/dist/runtime/scaffold-bootstrap.js +7 -2
  185. package/dist/runtime/scaffold-package-manager-files.js +5 -1
  186. package/dist/runtime/scaffold-repository-reference.js +4 -2
  187. package/dist/runtime/scaffold-template-variables.js +2 -1
  188. package/dist/runtime/scaffold.d.ts +18 -1
  189. package/dist/runtime/scaffold.js +55 -2
  190. package/dist/runtime/temp-roots.js +4 -1
  191. package/dist/runtime/template-layers.js +4 -1
  192. package/dist/runtime/template-registry.js +9 -3
  193. package/dist/runtime/template-source-contracts.d.ts +2 -0
  194. package/dist/runtime/template-source-normalization.js +2 -1
  195. package/dist/runtime/template-source-remote.js +18 -5
  196. package/dist/runtime/template-source-seeds.js +10 -3
  197. package/dist/runtime/typia-llm-json-schema.d.ts +24 -0
  198. package/dist/runtime/typia-llm-json-schema.js +33 -0
  199. package/dist/runtime/typia-llm-openapi-constraints.d.ts +20 -0
  200. package/dist/runtime/typia-llm-openapi-constraints.js +254 -0
  201. package/dist/runtime/typia-llm-projection.d.ts +25 -0
  202. package/dist/runtime/typia-llm-projection.js +58 -0
  203. package/dist/runtime/typia-llm-render.d.ts +21 -0
  204. package/dist/runtime/typia-llm-render.js +252 -0
  205. package/dist/runtime/typia-llm-sync.d.ts +10 -0
  206. package/dist/runtime/typia-llm-sync.js +63 -0
  207. package/dist/runtime/typia-llm-types.d.ts +197 -0
  208. package/dist/runtime/typia-llm-types.js +1 -0
  209. package/dist/runtime/typia-llm.d.ts +9 -255
  210. package/dist/runtime/typia-llm.js +5 -634
  211. package/dist/runtime/workspace-inventory-mutations.js +15 -1
  212. package/dist/runtime/workspace-inventory-parser-entries.d.ts +17 -0
  213. package/dist/runtime/workspace-inventory-parser-entries.js +157 -0
  214. package/dist/runtime/workspace-inventory-parser-validation.d.ts +104 -0
  215. package/dist/runtime/workspace-inventory-parser-validation.js +34 -0
  216. package/dist/runtime/workspace-inventory-parser.d.ts +3 -45
  217. package/dist/runtime/workspace-inventory-parser.js +3 -581
  218. package/dist/runtime/workspace-inventory-section-descriptors.d.ts +19 -0
  219. package/dist/runtime/workspace-inventory-section-descriptors.js +443 -0
  220. package/dist/runtime/workspace-inventory-templates.d.ts +3 -3
  221. package/dist/runtime/workspace-inventory-templates.js +10 -1
  222. package/dist/runtime/workspace-inventory-types.d.ts +10 -1
  223. package/dist/runtime/workspace-project.js +4 -6
  224. package/package.json +8 -3
  225. package/templates/_shared/compound/core/scripts/block-config.ts.mustache +22 -0
  226. package/templates/_shared/compound/core/scripts/sync-types-to-block-json.ts.mustache +103 -2
  227. package/templates/_shared/compound/core/src/inner-blocks-templates.ts.mustache +13 -0
  228. package/templates/_shared/compound/persistence/scripts/block-config.ts.mustache +22 -1
@@ -0,0 +1,266 @@
1
+ import { promises as fsp } from "node:fs";
2
+ import path from "node:path";
3
+ import { ensureBlockConfigCanAddRestManifests } from "./cli-add-block-legacy-validator.js";
4
+ import { assertValidManualRestContractAuth, assertValidManualRestContractHttpMethod, assertValidTypeScriptIdentifier, collectRestRouteNamedCaptureNames, resolveOptionalPhpCallbackReference, resolveOptionalPhpClassReference, resolveManualRestContractPathPattern, rollbackWorkspaceMutation, snapshotWorkspaceFiles, } from "./cli-add-shared.js";
5
+ import { ensureRestResourceSyncScriptAnchors, } from "./cli-add-workspace-rest-resource-sync-anchors.js";
6
+ import { buildManualRestContractApiSource, buildManualRestContractConfigEntry, buildManualRestContractTypesSource, buildManualRestContractValidatorsSource, } from "./cli-add-workspace-rest-manual-source-emitters.js";
7
+ import { syncManualRestContractArtifacts } from "./rest-resource-artifacts.js";
8
+ import { toPascalCase, toTitleCase } from "./string-case.js";
9
+ import { appendWorkspaceInventoryEntries } from "./workspace-inventory.js";
10
+ const MANUAL_REST_REQUEST_BODY_FIELD_NAMES = new Set(["payload", "comment"]);
11
+ const MANUAL_REST_RESPONSE_FIELD_NAMES = new Set([
12
+ "id",
13
+ "status",
14
+ "message",
15
+ "updatedAt",
16
+ ]);
17
+ function resolveManualRestSecretPreserveOnEmpty(value) {
18
+ return value ?? true;
19
+ }
20
+ function resolveManualRestSecretStateFieldCandidate(options) {
21
+ const candidates = [
22
+ options.secretStateFieldName,
23
+ options.secretHasValueFieldName,
24
+ options.secretMaskedResponseFieldName,
25
+ ].filter((value) => typeof value === "string");
26
+ const distinct = Array.from(new Set(candidates));
27
+ if (distinct.length > 1) {
28
+ throw new Error("Manual REST contract secret state, has-value, and masked response field flags must match when combined.");
29
+ }
30
+ return distinct[0];
31
+ }
32
+ function resolveManualRestRoutePathPattern(options) {
33
+ const trimmedPathPattern = typeof options.pathPattern === "string"
34
+ ? options.pathPattern.trim()
35
+ : undefined;
36
+ const trimmedRoutePattern = typeof options.routePattern === "string"
37
+ ? options.routePattern.trim()
38
+ : undefined;
39
+ if (trimmedPathPattern &&
40
+ trimmedRoutePattern &&
41
+ trimmedPathPattern !== trimmedRoutePattern) {
42
+ throw new Error("Manual REST contract --path and --route-pattern must match when both are provided. Use one route pattern flag for provider routes.");
43
+ }
44
+ return resolveManualRestContractPathPattern(options.restResourceSlug, options.pathPattern ?? options.routePattern);
45
+ }
46
+ /**
47
+ * Scaffold a type-only external REST contract for workspace consumers.
48
+ *
49
+ * @param options Resolved workspace and raw manual-mode command options.
50
+ * @returns Resolved scaffold metadata for the manual REST contract.
51
+ */
52
+ export async function scaffoldManualRestContract({ auth, bodyTypeName, controllerClass, controllerExtends, method, namespace, pathPattern, permissionCallback, queryTypeName, responseTypeName, restResourceSlug, routePattern, secretFieldName, secretHasValueFieldName, secretMaskedResponseFieldName, secretPreserveOnEmpty, secretStateFieldName, workspace, }) {
53
+ const blockConfigPath = path.join(workspace.projectDir, "scripts", "block-config.ts");
54
+ const syncRestScriptPath = path.join(workspace.projectDir, "scripts", "sync-rest-contracts.ts");
55
+ const restResourceDir = path.join(workspace.projectDir, "src", "rest", restResourceSlug);
56
+ const typesFilePath = path.join(restResourceDir, "api-types.ts");
57
+ const validatorsFilePath = path.join(restResourceDir, "api-validators.ts");
58
+ const apiFilePath = path.join(restResourceDir, "api.ts");
59
+ const pascalCase = toPascalCase(restResourceSlug);
60
+ const resolvedAuth = assertValidManualRestContractAuth(auth);
61
+ const resolvedMethod = assertValidManualRestContractHttpMethod(method);
62
+ const resolvedPathPattern = resolveManualRestRoutePathPattern({
63
+ pathPattern,
64
+ restResourceSlug,
65
+ routePattern,
66
+ });
67
+ const pathParameterNames = collectRestRouteNamedCaptureNames(resolvedPathPattern);
68
+ const resolvedPermissionCallback = resolveOptionalPhpCallbackReference("Manual REST contract permission callback", permissionCallback);
69
+ const resolvedControllerClass = resolveOptionalPhpClassReference("Manual REST contract controller class", controllerClass);
70
+ const resolvedControllerExtends = resolveOptionalPhpClassReference("Manual REST contract controller base class", controllerExtends);
71
+ if (resolvedControllerExtends && !resolvedControllerClass) {
72
+ throw new Error("Manual REST contract controller base class requires --controller-class.");
73
+ }
74
+ const resolvedQueryTypeName = assertValidTypeScriptIdentifier("Manual REST contract query type", queryTypeName ?? `${pascalCase}Query`, "wp-typia add rest-resource <name> --manual [--query-type <ExportedQueryType>]");
75
+ const resolvedResponseTypeName = assertValidTypeScriptIdentifier("Manual REST contract response type", responseTypeName ?? `${pascalCase}Response`, "wp-typia add rest-resource <name> --manual [--response-type <ExportedResponseType>]");
76
+ const defaultsToBody = bodyTypeName == null && ["PATCH", "POST", "PUT"].includes(resolvedMethod);
77
+ const resolvedBodyTypeName = bodyTypeName != null || defaultsToBody
78
+ ? assertValidTypeScriptIdentifier("Manual REST contract body type", bodyTypeName ?? `${pascalCase}Request`, "wp-typia add rest-resource <name> --manual [--body-type <ExportedBodyType>]")
79
+ : undefined;
80
+ if (resolvedMethod === "GET" && resolvedBodyTypeName) {
81
+ throw new Error("Manual REST contract GET routes cannot define a body type. Remove --body-type or use POST, PUT, or PATCH.");
82
+ }
83
+ const secretStateFieldCandidate = resolveManualRestSecretStateFieldCandidate({
84
+ secretHasValueFieldName,
85
+ secretMaskedResponseFieldName,
86
+ secretStateFieldName,
87
+ });
88
+ if (secretPreserveOnEmpty !== undefined && !secretFieldName) {
89
+ throw new Error("Manual REST contract --secret-preserve-on-empty requires --secret-field.");
90
+ }
91
+ if (secretStateFieldCandidate !== undefined && !secretFieldName) {
92
+ throw new Error("Manual REST contract secret state, has-value, and masked response field flags require --secret-field.");
93
+ }
94
+ if (secretFieldName && !resolvedBodyTypeName) {
95
+ throw new Error("Manual REST contract secret fields require a request body. Use POST, PUT, or PATCH so a request body is generated.");
96
+ }
97
+ const resolvedSecretFieldName = secretFieldName
98
+ ? assertValidTypeScriptIdentifier("Manual REST contract secret field", secretFieldName, "wp-typia add rest-resource <name> --manual --method POST --secret-field <field>")
99
+ : undefined;
100
+ const resolvedSecretPreserveOnEmpty = resolvedSecretFieldName
101
+ ? resolveManualRestSecretPreserveOnEmpty(secretPreserveOnEmpty)
102
+ : undefined;
103
+ const resolvedSecretStateFieldName = resolvedSecretFieldName
104
+ ? assertValidTypeScriptIdentifier("Manual REST contract secret state field", secretStateFieldCandidate ??
105
+ `has${toPascalCase(resolvedSecretFieldName)}`, "wp-typia add rest-resource <name> --manual --method POST --secret-state-field <field>")
106
+ : undefined;
107
+ if (resolvedSecretFieldName &&
108
+ MANUAL_REST_REQUEST_BODY_FIELD_NAMES.has(resolvedSecretFieldName)) {
109
+ throw new Error(`Manual REST contract secret field must not reuse scaffolded request body fields: ${Array.from(MANUAL_REST_REQUEST_BODY_FIELD_NAMES).join(", ")}.`);
110
+ }
111
+ if (resolvedSecretStateFieldName &&
112
+ MANUAL_REST_RESPONSE_FIELD_NAMES.has(resolvedSecretStateFieldName)) {
113
+ throw new Error(`Manual REST contract secret state field must not reuse scaffolded response fields: ${Array.from(MANUAL_REST_RESPONSE_FIELD_NAMES).join(", ")}.`);
114
+ }
115
+ if (resolvedSecretFieldName &&
116
+ resolvedSecretStateFieldName &&
117
+ resolvedSecretFieldName === resolvedSecretStateFieldName) {
118
+ throw new Error("Manual REST contract secret state field must be different from the raw secret field.");
119
+ }
120
+ const manualTypeNames = [
121
+ resolvedQueryTypeName,
122
+ resolvedResponseTypeName,
123
+ resolvedBodyTypeName,
124
+ ].filter((value) => value != null);
125
+ const duplicateManualTypeNames = manualTypeNames.filter((name, index) => manualTypeNames.indexOf(name) !== index);
126
+ if (duplicateManualTypeNames.length > 0) {
127
+ throw new Error(`Manual REST contract type names must be unique: ${Array.from(new Set(duplicateManualTypeNames)).join(", ")}. Use distinct --query-type, --body-type, and --response-type values.`);
128
+ }
129
+ const mutationSnapshot = {
130
+ fileSources: await snapshotWorkspaceFiles([
131
+ blockConfigPath,
132
+ syncRestScriptPath,
133
+ ]),
134
+ snapshotDirs: [],
135
+ targetPaths: [restResourceDir],
136
+ };
137
+ try {
138
+ await fsp.mkdir(restResourceDir, { recursive: true });
139
+ await ensureRestResourceSyncScriptAnchors(workspace);
140
+ await fsp.writeFile(typesFilePath, buildManualRestContractTypesSource({
141
+ ...(resolvedBodyTypeName
142
+ ? { bodyTypeName: resolvedBodyTypeName }
143
+ : {}),
144
+ pathParameterNames,
145
+ queryTypeName: resolvedQueryTypeName,
146
+ responseTypeName: resolvedResponseTypeName,
147
+ restResourceSlug,
148
+ ...(resolvedSecretFieldName
149
+ ? { secretFieldName: resolvedSecretFieldName }
150
+ : {}),
151
+ ...(resolvedSecretPreserveOnEmpty !== undefined
152
+ ? { secretPreserveOnEmpty: resolvedSecretPreserveOnEmpty }
153
+ : {}),
154
+ ...(resolvedSecretStateFieldName
155
+ ? { secretStateFieldName: resolvedSecretStateFieldName }
156
+ : {}),
157
+ }), "utf8");
158
+ await fsp.writeFile(validatorsFilePath, buildManualRestContractValidatorsSource({
159
+ ...(resolvedBodyTypeName
160
+ ? { bodyTypeName: resolvedBodyTypeName }
161
+ : {}),
162
+ queryTypeName: resolvedQueryTypeName,
163
+ responseTypeName: resolvedResponseTypeName,
164
+ }), "utf8");
165
+ await fsp.writeFile(apiFilePath, buildManualRestContractApiSource({
166
+ ...(resolvedBodyTypeName
167
+ ? { bodyTypeName: resolvedBodyTypeName }
168
+ : {}),
169
+ queryTypeName: resolvedQueryTypeName,
170
+ restResourceSlug,
171
+ }), "utf8");
172
+ await syncManualRestContractArtifacts({
173
+ clientFile: `src/rest/${restResourceSlug}/api-client.ts`,
174
+ outputDir: restResourceDir,
175
+ projectDir: workspace.projectDir,
176
+ typesFile: `src/rest/${restResourceSlug}/api-types.ts`,
177
+ validatorsFile: `src/rest/${restResourceSlug}/api-validators.ts`,
178
+ variables: {
179
+ auth: resolvedAuth,
180
+ ...(resolvedBodyTypeName
181
+ ? { bodyTypeName: resolvedBodyTypeName }
182
+ : {}),
183
+ method: resolvedMethod,
184
+ namespace,
185
+ pascalCase,
186
+ pathPattern: resolvedPathPattern,
187
+ queryTypeName: resolvedQueryTypeName,
188
+ responseTypeName: resolvedResponseTypeName,
189
+ slugKebabCase: restResourceSlug,
190
+ title: toTitleCase(restResourceSlug),
191
+ },
192
+ });
193
+ await appendWorkspaceInventoryEntries(workspace.projectDir, {
194
+ restResourceEntries: [
195
+ buildManualRestContractConfigEntry({
196
+ auth: resolvedAuth,
197
+ ...(resolvedBodyTypeName
198
+ ? { bodyTypeName: resolvedBodyTypeName }
199
+ : {}),
200
+ ...(resolvedControllerClass
201
+ ? { controllerClass: resolvedControllerClass }
202
+ : {}),
203
+ ...(resolvedControllerExtends
204
+ ? { controllerExtends: resolvedControllerExtends }
205
+ : {}),
206
+ method: resolvedMethod,
207
+ namespace,
208
+ pathPattern: resolvedPathPattern,
209
+ ...(resolvedPermissionCallback
210
+ ? { permissionCallback: resolvedPermissionCallback }
211
+ : {}),
212
+ queryTypeName: resolvedQueryTypeName,
213
+ responseTypeName: resolvedResponseTypeName,
214
+ restResourceSlug,
215
+ ...(resolvedSecretFieldName
216
+ ? { secretFieldName: resolvedSecretFieldName }
217
+ : {}),
218
+ ...(resolvedSecretPreserveOnEmpty !== undefined
219
+ ? { secretPreserveOnEmpty: resolvedSecretPreserveOnEmpty }
220
+ : {}),
221
+ ...(resolvedSecretStateFieldName
222
+ ? { secretStateFieldName: resolvedSecretStateFieldName }
223
+ : {}),
224
+ }),
225
+ ],
226
+ transformSource: ensureBlockConfigCanAddRestManifests,
227
+ });
228
+ return {
229
+ auth: resolvedAuth,
230
+ ...(resolvedBodyTypeName
231
+ ? { bodyTypeName: resolvedBodyTypeName }
232
+ : {}),
233
+ ...(resolvedControllerClass
234
+ ? { controllerClass: resolvedControllerClass }
235
+ : {}),
236
+ ...(resolvedControllerExtends
237
+ ? { controllerExtends: resolvedControllerExtends }
238
+ : {}),
239
+ method: resolvedMethod,
240
+ methods: [],
241
+ mode: "manual",
242
+ namespace,
243
+ pathPattern: resolvedPathPattern,
244
+ ...(resolvedPermissionCallback
245
+ ? { permissionCallback: resolvedPermissionCallback }
246
+ : {}),
247
+ projectDir: workspace.projectDir,
248
+ queryTypeName: resolvedQueryTypeName,
249
+ restResourceSlug,
250
+ responseTypeName: resolvedResponseTypeName,
251
+ ...(resolvedSecretFieldName
252
+ ? { secretFieldName: resolvedSecretFieldName }
253
+ : {}),
254
+ ...(resolvedSecretPreserveOnEmpty !== undefined
255
+ ? { secretPreserveOnEmpty: resolvedSecretPreserveOnEmpty }
256
+ : {}),
257
+ ...(resolvedSecretStateFieldName
258
+ ? { secretStateFieldName: resolvedSecretStateFieldName }
259
+ : {}),
260
+ };
261
+ }
262
+ catch (error) {
263
+ await rollbackWorkspaceMutation(mutationSnapshot);
264
+ throw error;
265
+ }
266
+ }
@@ -0,0 +1,18 @@
1
+ import { type RestResourceMethodId } from "./cli-add-shared.js";
2
+ export { buildWorkspaceRestSchemaHelperPhpSource, } from "./cli-add-workspace-rest-schema-helper-php-template.js";
3
+ /**
4
+ * Build the PHP route/controller glue for generated workspace REST resources.
5
+ *
6
+ * @param restResourceSlug Normalized REST resource slug.
7
+ * @param namespace WordPress REST namespace, such as `vendor/v1`.
8
+ * @param phpPrefix Plugin PHP function prefix.
9
+ * @param methods REST operations to expose.
10
+ * @param options Optional generated route and controller customizations.
11
+ * @returns A complete PHP source file for the generated REST resource.
12
+ */
13
+ export declare function buildRestResourcePhpSource(restResourceSlug: string, namespace: string, phpPrefix: string, methods: RestResourceMethodId[], options: {
14
+ controllerClass?: string;
15
+ controllerExtends?: string;
16
+ permissionCallback?: string;
17
+ routePattern: string;
18
+ }): string;
@@ -0,0 +1,359 @@
1
+ import { quotePhpString } from "./php-utils.js";
2
+ import { toTitleCase } from "./string-case.js";
3
+ import { buildRestResourceControllerBootstrapSource, buildRestResourceControllerClassSource, buildRestResourceRouteRegistrations, } from "./cli-add-workspace-rest-resource-php-routing-template.js";
4
+ export { buildWorkspaceRestSchemaHelperPhpSource, } from "./cli-add-workspace-rest-schema-helper-php-template.js";
5
+ /**
6
+ * Build the PHP route/controller glue for generated workspace REST resources.
7
+ *
8
+ * @param restResourceSlug Normalized REST resource slug.
9
+ * @param namespace WordPress REST namespace, such as `vendor/v1`.
10
+ * @param phpPrefix Plugin PHP function prefix.
11
+ * @param methods REST operations to expose.
12
+ * @param options Optional generated route and controller customizations.
13
+ * @returns A complete PHP source file for the generated REST resource.
14
+ */
15
+ export function buildRestResourcePhpSource(restResourceSlug, namespace, phpPrefix, methods, options) {
16
+ const restResourceTitle = toTitleCase(restResourceSlug);
17
+ const restResourcePhpId = restResourceSlug.replace(/-/g, "_");
18
+ const canWriteFunctionName = `${phpPrefix}_${restResourcePhpId}_can_manage_rest_resource`;
19
+ const getItemsFunctionName = `${phpPrefix}_${restResourcePhpId}_get_rest_resource_items`;
20
+ const validatePayloadFunctionName = `${phpPrefix}_${restResourcePhpId}_validate_rest_resource_payload`;
21
+ const normalizeItemFunctionName = `${phpPrefix}_${restResourcePhpId}_normalize_rest_resource_item`;
22
+ const saveItemsFunctionName = `${phpPrefix}_${restResourcePhpId}_save_rest_resource_items`;
23
+ const getOptionNameFunctionName = `${phpPrefix}_${restResourcePhpId}_get_rest_resource_option_name`;
24
+ const listHandlerName = `${phpPrefix}_${restResourcePhpId}_handle_list_rest_resource`;
25
+ const readHandlerName = `${phpPrefix}_${restResourcePhpId}_handle_read_rest_resource`;
26
+ const createHandlerName = `${phpPrefix}_${restResourcePhpId}_handle_create_rest_resource`;
27
+ const updateHandlerName = `${phpPrefix}_${restResourcePhpId}_handle_update_rest_resource`;
28
+ const deleteHandlerName = `${phpPrefix}_${restResourcePhpId}_handle_delete_rest_resource`;
29
+ const registerRoutesFunctionName = `${phpPrefix}_${restResourcePhpId}_register_rest_routes`;
30
+ const controllerVariableName = options.controllerClass ? "$controller" : undefined;
31
+ const routeRegistrations = buildRestResourceRouteRegistrations(restResourceSlug, methods, {
32
+ canWriteFunctionName,
33
+ createHandlerName,
34
+ deleteHandlerName,
35
+ listHandlerName,
36
+ readHandlerName,
37
+ updateHandlerName,
38
+ }, {
39
+ ...(controllerVariableName ? { controllerVariableName } : {}),
40
+ ...(options.permissionCallback
41
+ ? { permissionCallback: options.permissionCallback }
42
+ : {}),
43
+ routePattern: options.routePattern,
44
+ });
45
+ const controllerClassSource = options.controllerClass
46
+ ? buildRestResourceControllerClassSource({
47
+ controllerClass: options.controllerClass,
48
+ ...(options.controllerExtends
49
+ ? { controllerExtends: options.controllerExtends }
50
+ : {}),
51
+ functions: {
52
+ canWriteFunctionName,
53
+ createHandlerName,
54
+ deleteHandlerName,
55
+ listHandlerName,
56
+ readHandlerName,
57
+ updateHandlerName,
58
+ },
59
+ })
60
+ : "";
61
+ const controllerBootstrapSource = buildRestResourceControllerBootstrapSource(options.controllerClass);
62
+ return `<?php
63
+ if ( ! defined( 'ABSPATH' ) ) {
64
+ \treturn;
65
+ }
66
+
67
+ if ( ! function_exists( '${getOptionNameFunctionName}' ) ) {
68
+ \tfunction ${getOptionNameFunctionName}() {
69
+ \t\treturn ${quotePhpString(`${phpPrefix}_${restResourcePhpId}_rest_resource_items`)};
70
+ \t}
71
+ }
72
+
73
+ if ( ! function_exists( '${normalizeItemFunctionName}' ) ) {
74
+ \tfunction ${normalizeItemFunctionName}( array $item ) {
75
+ \t\treturn array(
76
+ \t\t\t'id' => isset( $item['id'] ) ? (int) $item['id'] : 0,
77
+ \t\t\t'title' => isset( $item['title'] ) ? (string) $item['title'] : '',
78
+ \t\t\t'content' => isset( $item['content'] ) ? (string) $item['content'] : '',
79
+ \t\t\t'status' => isset( $item['status'] ) && 'published' === $item['status'] ? 'published' : 'draft',
80
+ \t\t\t'updatedAt' => isset( $item['updatedAt'] ) ? (string) $item['updatedAt'] : gmdate( 'c' ),
81
+ \t\t);
82
+ \t}
83
+ }
84
+
85
+ if ( ! function_exists( '${getItemsFunctionName}' ) ) {
86
+ \tfunction ${getItemsFunctionName}() {
87
+ \t\t$seed_items = array(
88
+ \t\t\tarray(
89
+ \t\t\t\t'id' => 1,
90
+ \t\t\t\t'title' => ${quotePhpString(`${restResourceTitle} Starter`)},
91
+ \t\t\t\t'content' => ${quotePhpString(`Replace this seeded ${restResourceTitle.toLowerCase()} content with your plugin data source.`)},
92
+ \t\t\t\t'status' => 'draft',
93
+ \t\t\t\t'updatedAt' => '2026-01-01T00:00:00Z',
94
+ \t\t\t),
95
+ \t\t);
96
+ \t\t$items = get_option( ${getOptionNameFunctionName}(), $seed_items );
97
+
98
+ \t\tif ( ! is_array( $items ) ) {
99
+ \t\t\t$items = $seed_items;
100
+ \t\t}
101
+
102
+ \t\treturn array_values(
103
+ \t\t\tarray_map(
104
+ \t\t\t\t'${normalizeItemFunctionName}',
105
+ \t\t\t\tarray_filter(
106
+ \t\t\t\t\t$items,
107
+ \t\t\t\t\t'is_array'
108
+ \t\t\t\t)
109
+ \t\t\t)
110
+ \t\t);
111
+ \t}
112
+ }
113
+
114
+ if ( ! function_exists( '${saveItemsFunctionName}' ) ) {
115
+ \tfunction ${saveItemsFunctionName}( array $items ) {
116
+ \t\tupdate_option(
117
+ \t\t\t${getOptionNameFunctionName}(),
118
+ \t\t\tarray_values(
119
+ \t\t\t\tarray_map(
120
+ \t\t\t\t\t'${normalizeItemFunctionName}',
121
+ \t\t\t\t\t$items
122
+ \t\t\t\t)
123
+ \t\t\t),
124
+ \t\t\tfalse
125
+ \t\t);
126
+ \t}
127
+ }
128
+
129
+ if ( ! function_exists( '${validatePayloadFunctionName}' ) ) {
130
+ \tfunction ${validatePayloadFunctionName}( $value, $schema_name, $param_name ) {
131
+ \t\tif ( ! function_exists( '${phpPrefix}_validate_and_sanitize_rest_payload' ) ) {
132
+ \t\t\treturn new WP_Error(
133
+ \t\t\t\t'missing_rest_schema_helper',
134
+ \t\t\t\t'Missing REST schema helper. Ensure inc/rest-schema.php is loaded before REST resources.',
135
+ \t\t\t\tarray( 'status' => 500 )
136
+ \t\t\t);
137
+ \t\t}
138
+
139
+ \t\treturn ${phpPrefix}_validate_and_sanitize_rest_payload(
140
+ \t\t\t$value,
141
+ \t\t\t$schema_name,
142
+ \t\t\t$param_name,
143
+ \t\t\tarray( 'resource' => ${quotePhpString(restResourceSlug)} )
144
+ \t\t);
145
+ \t}
146
+ }
147
+
148
+ if ( ! function_exists( '${canWriteFunctionName}' ) ) {
149
+ \tfunction ${canWriteFunctionName}() {
150
+ \t\treturn current_user_can( 'edit_posts' );
151
+ \t}
152
+ }
153
+
154
+ if ( ! function_exists( '${listHandlerName}' ) ) {
155
+ \tfunction ${listHandlerName}( WP_REST_Request $request ) {
156
+ \t\t$payload_input = array();
157
+ \t\t$page = $request->get_param( 'page' );
158
+ \t\t$per_page = $request->get_param( 'perPage' );
159
+ \t\t$search = $request->get_param( 'search' );
160
+
161
+ \t\tif ( null !== $page ) {
162
+ \t\t\t$payload_input['page'] = $page;
163
+ \t\t}
164
+ \t\tif ( null !== $per_page ) {
165
+ \t\t\t$payload_input['perPage'] = $per_page;
166
+ \t\t}
167
+ \t\tif ( null !== $search ) {
168
+ \t\t\t$payload_input['search'] = $search;
169
+ \t\t}
170
+
171
+ \t\t$payload = ${validatePayloadFunctionName}(
172
+ \t\t\t$payload_input,
173
+ \t\t\t'list-query',
174
+ \t\t\t'query'
175
+ \t\t);
176
+
177
+ \t\tif ( is_wp_error( $payload ) ) {
178
+ \t\t\treturn $payload;
179
+ \t\t}
180
+
181
+ \t\t$page = isset( $payload['page'] ) ? max( 1, (int) $payload['page'] ) : 1;
182
+ \t\t$per_page = isset( $payload['perPage'] ) ? min( 50, max( 1, (int) $payload['perPage'] ) ) : 10;
183
+ \t\t$search = isset( $payload['search'] ) ? strtolower( (string) $payload['search'] ) : '';
184
+ \t\t$items = ${getItemsFunctionName}();
185
+
186
+ \t\tif ( '' !== $search ) {
187
+ \t\t\t$items = array_values(
188
+ \t\t\t\tarray_filter(
189
+ \t\t\t\t\t$items,
190
+ \t\t\t\t\tstatic function ( $item ) use ( $search ) {
191
+ \t\t\t\t\t\treturn false !== strpos( strtolower( (string) ( $item['title'] ?? '' ) ), $search ) ||
192
+ \t\t\t\t\t\t\tfalse !== strpos( strtolower( (string) ( $item['content'] ?? '' ) ), $search );
193
+ \t\t\t\t\t}
194
+ \t\t\t\t)
195
+ \t\t\t);
196
+ \t\t}
197
+
198
+ \t\t$total = count( $items );
199
+ \t\t$items = array_slice( $items, ( $page - 1 ) * $per_page, $per_page );
200
+
201
+ \t\treturn rest_ensure_response(
202
+ \t\t\tarray(
203
+ \t\t\t\t'items' => $items,
204
+ \t\t\t\t'page' => $page,
205
+ \t\t\t\t'perPage' => $per_page,
206
+ \t\t\t\t'total' => $total,
207
+ \t\t\t)
208
+ \t\t);
209
+ \t}
210
+ }
211
+
212
+ if ( ! function_exists( '${readHandlerName}' ) ) {
213
+ \tfunction ${readHandlerName}( WP_REST_Request $request ) {
214
+ \t\t$payload = ${validatePayloadFunctionName}(
215
+ \t\t\tarray(
216
+ \t\t\t\t'id' => $request->get_param( 'id' ),
217
+ \t\t\t),
218
+ \t\t\t'read-query',
219
+ \t\t\t'query'
220
+ \t\t);
221
+
222
+ \t\tif ( is_wp_error( $payload ) ) {
223
+ \t\t\treturn $payload;
224
+ \t\t}
225
+
226
+ \t\tforeach ( ${getItemsFunctionName}() as $item ) {
227
+ \t\t\tif ( (int) $item['id'] === (int) $payload['id'] ) {
228
+ \t\t\t\treturn rest_ensure_response( $item );
229
+ \t\t\t}
230
+ \t\t}
231
+
232
+ \t\treturn new WP_Error( 'rest_not_found', 'Resource not found.', array( 'status' => 404 ) );
233
+ \t}
234
+ }
235
+
236
+ if ( ! function_exists( '${createHandlerName}' ) ) {
237
+ \tfunction ${createHandlerName}( WP_REST_Request $request ) {
238
+ \t\t$payload = ${validatePayloadFunctionName}( $request->get_json_params(), 'create-request', 'body' );
239
+ \t\tif ( is_wp_error( $payload ) ) {
240
+ \t\t\treturn $payload;
241
+ \t\t}
242
+
243
+ \t\t$items = ${getItemsFunctionName}();
244
+ \t\t$next_id = 1;
245
+ \t\tforeach ( $items as $item ) {
246
+ \t\t\t$next_id = max( $next_id, (int) $item['id'] + 1 );
247
+ \t\t}
248
+
249
+ \t\t$record = ${normalizeItemFunctionName}(
250
+ \t\t\tarray(
251
+ \t\t\t\t'id' => $next_id,
252
+ \t\t\t\t'title' => (string) $payload['title'],
253
+ \t\t\t\t'content' => isset( $payload['content'] ) ? (string) $payload['content'] : '',
254
+ \t\t\t\t'status' => isset( $payload['status'] ) ? (string) $payload['status'] : 'draft',
255
+ \t\t\t\t'updatedAt' => gmdate( 'c' ),
256
+ \t\t\t)
257
+ \t\t);
258
+
259
+ \t\t$items[] = $record;
260
+ \t\t${saveItemsFunctionName}( $items );
261
+
262
+ \t\treturn rest_ensure_response( $record );
263
+ \t}
264
+ }
265
+
266
+ if ( ! function_exists( '${updateHandlerName}' ) ) {
267
+ \tfunction ${updateHandlerName}( WP_REST_Request $request ) {
268
+ \t\t$query = ${validatePayloadFunctionName}(
269
+ \t\t\tarray(
270
+ \t\t\t\t'id' => $request->get_param( 'id' ),
271
+ \t\t\t),
272
+ \t\t\t'update-query',
273
+ \t\t\t'query'
274
+ \t\t);
275
+ \t\tif ( is_wp_error( $query ) ) {
276
+ \t\t\treturn $query;
277
+ \t\t}
278
+
279
+ \t\t$payload = ${validatePayloadFunctionName}( $request->get_json_params(), 'update-request', 'body' );
280
+ \t\tif ( is_wp_error( $payload ) ) {
281
+ \t\t\treturn $payload;
282
+ \t\t}
283
+
284
+ \t\t$items = ${getItemsFunctionName}();
285
+ \t\tforeach ( $items as $index => $item ) {
286
+ \t\t\tif ( (int) $item['id'] !== (int) $query['id'] ) {
287
+ \t\t\t\tcontinue;
288
+ \t\t\t}
289
+
290
+ \t\t\t$items[ $index ] = ${normalizeItemFunctionName}(
291
+ \t\t\t\tarray(
292
+ \t\t\t\t\t'id' => $item['id'],
293
+ \t\t\t\t\t'title' => isset( $payload['title'] ) ? (string) $payload['title'] : (string) $item['title'],
294
+ \t\t\t\t\t'content' => array_key_exists( 'content', $payload ) ? (string) $payload['content'] : (string) $item['content'],
295
+ \t\t\t\t\t'status' => isset( $payload['status'] ) ? (string) $payload['status'] : (string) $item['status'],
296
+ \t\t\t\t\t'updatedAt' => gmdate( 'c' ),
297
+ \t\t\t\t)
298
+ \t\t\t);
299
+
300
+ \t\t\t${saveItemsFunctionName}( $items );
301
+ \t\t\treturn rest_ensure_response( $items[ $index ] );
302
+ \t\t}
303
+
304
+ \t\treturn new WP_Error( 'rest_not_found', 'Resource not found.', array( 'status' => 404 ) );
305
+ \t}
306
+ }
307
+
308
+ if ( ! function_exists( '${deleteHandlerName}' ) ) {
309
+ \tfunction ${deleteHandlerName}( WP_REST_Request $request ) {
310
+ \t\t$query = ${validatePayloadFunctionName}(
311
+ \t\t\tarray(
312
+ \t\t\t\t'id' => $request->get_param( 'id' ),
313
+ \t\t\t),
314
+ \t\t\t'delete-query',
315
+ \t\t\t'query'
316
+ \t\t);
317
+ \t\tif ( is_wp_error( $query ) ) {
318
+ \t\t\treturn $query;
319
+ \t\t}
320
+
321
+ \t\t$items = ${getItemsFunctionName}();
322
+ \t\t$filtered = array_values(
323
+ \t\t\tarray_filter(
324
+ \t\t\t\t$items,
325
+ \t\t\t\tstatic function ( $item ) use ( $query ) {
326
+ \t\t\t\t\treturn (int) $item['id'] !== (int) $query['id'];
327
+ \t\t\t\t}
328
+ \t\t\t)
329
+ \t\t);
330
+ \t\t$was_deleted = count( $filtered ) !== count( $items );
331
+
332
+ \t\tif ( ! $was_deleted ) {
333
+ \t\t\treturn new WP_Error( 'rest_not_found', 'Resource not found.', array( 'status' => 404 ) );
334
+ \t\t}
335
+
336
+ \t\t${saveItemsFunctionName}( $filtered );
337
+
338
+ \t\treturn rest_ensure_response(
339
+ \t\t\tarray(
340
+ \t\t\t\t'deleted' => true,
341
+ \t\t\t\t'id' => (int) $query['id'],
342
+ \t\t\t)
343
+ \t\t);
344
+ \t}
345
+ }
346
+
347
+ ${controllerClassSource}
348
+ if ( ! function_exists( '${registerRoutesFunctionName}' ) ) {
349
+ \tfunction ${registerRoutesFunctionName}() {
350
+ \t\t$namespace = ${quotePhpString(namespace)};
351
+
352
+ ${controllerBootstrapSource}
353
+ ${routeRegistrations}
354
+ \t}
355
+ }
356
+
357
+ add_action( 'rest_api_init', '${registerRoutesFunctionName}' );
358
+ `;
359
+ }
@@ -0,0 +1,33 @@
1
+ import { type RestResourceMethodId } from "./cli-add-shared.js";
2
+ export interface RestResourcePhpFunctionNames {
3
+ canWriteFunctionName: string;
4
+ createHandlerName: string;
5
+ deleteHandlerName: string;
6
+ listHandlerName: string;
7
+ readHandlerName: string;
8
+ updateHandlerName: string;
9
+ }
10
+ /**
11
+ * Build the `register_rest_route` calls for generated REST resource PHP files.
12
+ */
13
+ export declare function buildRestResourceRouteRegistrations(restResourceSlug: string, methods: RestResourceMethodId[], functions: RestResourcePhpFunctionNames, options: {
14
+ controllerVariableName?: string;
15
+ permissionCallback?: string;
16
+ routePattern: string;
17
+ }): string;
18
+ /**
19
+ * Normalize a configured PHP class name into a global `::class` reference.
20
+ */
21
+ export declare function toPhpClassConstantReference(classReference: string): string;
22
+ /**
23
+ * Build an optional controller shim class for generated REST resource handlers.
24
+ */
25
+ export declare function buildRestResourceControllerClassSource(options: {
26
+ controllerClass: string;
27
+ controllerExtends?: string;
28
+ functions: RestResourcePhpFunctionNames;
29
+ }): string;
30
+ /**
31
+ * Build the controller instantiation block used inside REST route registration.
32
+ */
33
+ export declare function buildRestResourceControllerBootstrapSource(controllerClass: string | undefined): string;