@wp-typia/project-tools 0.22.10 → 0.23.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 (155) 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/cli-add-block-json.js +5 -1
  4. package/dist/runtime/cli-add-collision.d.ts +25 -0
  5. package/dist/runtime/cli-add-collision.js +76 -0
  6. package/dist/runtime/cli-add-help.js +12 -2
  7. package/dist/runtime/cli-add-kind-ids.d.ts +1 -1
  8. package/dist/runtime/cli-add-kind-ids.js +3 -0
  9. package/dist/runtime/cli-add-types.d.ts +129 -0
  10. package/dist/runtime/cli-add-types.js +26 -0
  11. package/dist/runtime/cli-add-validation.d.ts +97 -1
  12. package/dist/runtime/cli-add-validation.js +313 -1
  13. package/dist/runtime/cli-add-workspace-ability-scaffold.js +4 -1
  14. package/dist/runtime/cli-add-workspace-admin-view-scaffold.js +79 -20
  15. package/dist/runtime/cli-add-workspace-admin-view-source.js +11 -2
  16. package/dist/runtime/cli-add-workspace-admin-view-templates-core-data.d.ts +34 -0
  17. package/dist/runtime/cli-add-workspace-admin-view-templates-core-data.js +483 -0
  18. package/dist/runtime/cli-add-workspace-admin-view-templates-default.d.ts +30 -0
  19. package/dist/runtime/cli-add-workspace-admin-view-templates-default.js +310 -0
  20. package/dist/runtime/cli-add-workspace-admin-view-templates-rest.d.ts +25 -0
  21. package/dist/runtime/cli-add-workspace-admin-view-templates-rest.js +124 -0
  22. package/dist/runtime/cli-add-workspace-admin-view-templates-settings.d.ts +34 -0
  23. package/dist/runtime/cli-add-workspace-admin-view-templates-settings.js +370 -0
  24. package/dist/runtime/cli-add-workspace-admin-view-templates-shared.d.ts +49 -0
  25. package/dist/runtime/cli-add-workspace-admin-view-templates-shared.js +259 -0
  26. package/dist/runtime/cli-add-workspace-admin-view-templates.d.ts +19 -10
  27. package/dist/runtime/cli-add-workspace-admin-view-templates.js +31 -971
  28. package/dist/runtime/cli-add-workspace-admin-view-types.d.ts +21 -0
  29. package/dist/runtime/cli-add-workspace-admin-view-types.js +22 -0
  30. package/dist/runtime/cli-add-workspace-ai-anchors.js +125 -32
  31. package/dist/runtime/cli-add-workspace-ai-source-emitters.js +17 -1
  32. package/dist/runtime/cli-add-workspace-contract-source-emitters.d.ts +15 -0
  33. package/dist/runtime/cli-add-workspace-contract-source-emitters.js +42 -0
  34. package/dist/runtime/cli-add-workspace-contract.d.ts +15 -0
  35. package/dist/runtime/cli-add-workspace-contract.js +65 -0
  36. package/dist/runtime/cli-add-workspace-integration-env.d.ts +26 -0
  37. package/dist/runtime/cli-add-workspace-integration-env.js +428 -0
  38. package/dist/runtime/cli-add-workspace-post-meta-anchors.d.ts +23 -0
  39. package/dist/runtime/cli-add-workspace-post-meta-anchors.js +244 -0
  40. package/dist/runtime/cli-add-workspace-post-meta-source-emitters.d.ts +63 -0
  41. package/dist/runtime/cli-add-workspace-post-meta-source-emitters.js +179 -0
  42. package/dist/runtime/cli-add-workspace-post-meta.d.ts +15 -0
  43. package/dist/runtime/cli-add-workspace-post-meta.js +107 -0
  44. package/dist/runtime/cli-add-workspace-rest-anchors.d.ts +9 -0
  45. package/dist/runtime/cli-add-workspace-rest-anchors.js +326 -21
  46. package/dist/runtime/cli-add-workspace-rest-generated.d.ts +9 -0
  47. package/dist/runtime/cli-add-workspace-rest-generated.js +158 -0
  48. package/dist/runtime/cli-add-workspace-rest-manual.d.ts +8 -0
  49. package/dist/runtime/cli-add-workspace-rest-manual.js +279 -0
  50. package/dist/runtime/cli-add-workspace-rest-php-templates.d.ts +24 -0
  51. package/dist/runtime/cli-add-workspace-rest-php-templates.js +678 -0
  52. package/dist/runtime/cli-add-workspace-rest-source-emitters.d.ts +98 -2
  53. package/dist/runtime/cli-add-workspace-rest-source-emitters.js +323 -29
  54. package/dist/runtime/cli-add-workspace-rest-types.d.ts +108 -0
  55. package/dist/runtime/cli-add-workspace-rest-types.js +1 -0
  56. package/dist/runtime/cli-add-workspace-rest.d.ts +3 -7
  57. package/dist/runtime/cli-add-workspace-rest.js +34 -481
  58. package/dist/runtime/cli-add-workspace.d.ts +15 -0
  59. package/dist/runtime/cli-add-workspace.js +15 -0
  60. package/dist/runtime/cli-add.d.ts +1 -1
  61. package/dist/runtime/cli-add.js +1 -1
  62. package/dist/runtime/cli-core.d.ts +3 -2
  63. package/dist/runtime/cli-core.js +3 -2
  64. package/dist/runtime/cli-diagnostics.d.ts +3 -1
  65. package/dist/runtime/cli-diagnostics.js +17 -5
  66. package/dist/runtime/cli-doctor-environment.js +1 -3
  67. package/dist/runtime/cli-doctor-workspace-bindings.js +4 -1
  68. package/dist/runtime/cli-doctor-workspace-block-addons.d.ts +12 -0
  69. package/dist/runtime/cli-doctor-workspace-block-addons.js +134 -0
  70. package/dist/runtime/cli-doctor-workspace-block-iframe.d.ts +9 -0
  71. package/dist/runtime/cli-doctor-workspace-block-iframe.js +228 -0
  72. package/dist/runtime/cli-doctor-workspace-block-metadata.d.ts +11 -0
  73. package/dist/runtime/cli-doctor-workspace-block-metadata.js +111 -0
  74. package/dist/runtime/cli-doctor-workspace-blocks.js +6 -424
  75. package/dist/runtime/cli-doctor-workspace-features-abilities.d.ts +11 -0
  76. package/dist/runtime/cli-doctor-workspace-features-abilities.js +112 -0
  77. package/dist/runtime/cli-doctor-workspace-features-admin-views.d.ts +11 -0
  78. package/dist/runtime/cli-doctor-workspace-features-admin-views.js +128 -0
  79. package/dist/runtime/cli-doctor-workspace-features-ai.d.ts +11 -0
  80. package/dist/runtime/cli-doctor-workspace-features-ai.js +57 -0
  81. package/dist/runtime/cli-doctor-workspace-features-editor-plugins.d.ts +11 -0
  82. package/dist/runtime/cli-doctor-workspace-features-editor-plugins.js +80 -0
  83. package/dist/runtime/cli-doctor-workspace-features-post-meta.d.ts +11 -0
  84. package/dist/runtime/cli-doctor-workspace-features-post-meta.js +77 -0
  85. package/dist/runtime/cli-doctor-workspace-features-rest.d.ts +11 -0
  86. package/dist/runtime/cli-doctor-workspace-features-rest.js +120 -0
  87. package/dist/runtime/cli-doctor-workspace-features.js +14 -369
  88. package/dist/runtime/cli-doctor-workspace-package.d.ts +25 -3
  89. package/dist/runtime/cli-doctor-workspace-package.js +35 -13
  90. package/dist/runtime/cli-doctor-workspace-shared.d.ts +2 -0
  91. package/dist/runtime/cli-doctor-workspace-shared.js +2 -0
  92. package/dist/runtime/cli-doctor-workspace.js +8 -3
  93. package/dist/runtime/cli-doctor.d.ts +52 -3
  94. package/dist/runtime/cli-doctor.js +79 -8
  95. package/dist/runtime/cli-help.js +10 -0
  96. package/dist/runtime/cli-init-package-json.js +4 -2
  97. package/dist/runtime/cli-init-templates.js +11 -1
  98. package/dist/runtime/cli-prompt.d.ts +16 -2
  99. package/dist/runtime/cli-prompt.js +29 -12
  100. package/dist/runtime/cli-scaffold.d.ts +2 -1
  101. package/dist/runtime/cli-scaffold.js +19 -10
  102. package/dist/runtime/contract-artifacts.d.ts +14 -0
  103. package/dist/runtime/contract-artifacts.js +15 -0
  104. package/dist/runtime/external-template-guards.js +4 -6
  105. package/dist/runtime/index.d.ts +2 -2
  106. package/dist/runtime/index.js +1 -1
  107. package/dist/runtime/json-utils.d.ts +62 -4
  108. package/dist/runtime/json-utils.js +78 -4
  109. package/dist/runtime/local-dev-presets.js +4 -1
  110. package/dist/runtime/migration-ui-capability.js +4 -1
  111. package/dist/runtime/migration-utils.js +4 -1
  112. package/dist/runtime/package-managers.js +6 -1
  113. package/dist/runtime/package-versions.js +6 -1
  114. package/dist/runtime/rest-resource-artifacts.d.ts +57 -1
  115. package/dist/runtime/rest-resource-artifacts.js +97 -1
  116. package/dist/runtime/scaffold-bootstrap.js +7 -2
  117. package/dist/runtime/scaffold-package-manager-files.js +5 -1
  118. package/dist/runtime/scaffold-repository-reference.js +4 -2
  119. package/dist/runtime/scaffold-template-variables.js +2 -1
  120. package/dist/runtime/scaffold.d.ts +18 -1
  121. package/dist/runtime/scaffold.js +55 -2
  122. package/dist/runtime/temp-roots.js +4 -1
  123. package/dist/runtime/template-layers.js +4 -1
  124. package/dist/runtime/template-registry.js +9 -3
  125. package/dist/runtime/template-render.d.ts +1 -1
  126. package/dist/runtime/template-render.js +1 -1
  127. package/dist/runtime/template-source-cache-markers.d.ts +37 -0
  128. package/dist/runtime/template-source-cache-markers.js +125 -0
  129. package/dist/runtime/template-source-cache.d.ts +1 -4
  130. package/dist/runtime/template-source-cache.js +16 -122
  131. package/dist/runtime/template-source-contracts.d.ts +2 -0
  132. package/dist/runtime/template-source-external.d.ts +4 -2
  133. package/dist/runtime/template-source-external.js +4 -2
  134. package/dist/runtime/template-source-normalization.js +2 -1
  135. package/dist/runtime/template-source-remote.d.ts +8 -4
  136. package/dist/runtime/template-source-remote.js +26 -9
  137. package/dist/runtime/template-source-seeds.js +10 -3
  138. package/dist/runtime/workspace-inventory-mutations.js +54 -4
  139. package/dist/runtime/workspace-inventory-parser-entries.d.ts +17 -0
  140. package/dist/runtime/workspace-inventory-parser-entries.js +157 -0
  141. package/dist/runtime/workspace-inventory-parser-validation.d.ts +104 -0
  142. package/dist/runtime/workspace-inventory-parser-validation.js +34 -0
  143. package/dist/runtime/workspace-inventory-parser.d.ts +3 -44
  144. package/dist/runtime/workspace-inventory-parser.js +7 -464
  145. package/dist/runtime/workspace-inventory-read.d.ts +9 -2
  146. package/dist/runtime/workspace-inventory-read.js +9 -2
  147. package/dist/runtime/workspace-inventory-section-descriptors.d.ts +19 -0
  148. package/dist/runtime/workspace-inventory-section-descriptors.js +435 -0
  149. package/dist/runtime/workspace-inventory-templates.d.ts +16 -1
  150. package/dist/runtime/workspace-inventory-templates.js +75 -4
  151. package/dist/runtime/workspace-inventory-types.d.ts +52 -2
  152. package/dist/runtime/workspace-inventory.d.ts +2 -2
  153. package/dist/runtime/workspace-inventory.js +1 -1
  154. package/dist/runtime/workspace-project.js +4 -6
  155. package/package.json +2 -2
@@ -3,6 +3,47 @@ import { getWorkspaceBootstrapPath, patchFile, } from "./cli-add-shared.js";
3
3
  import { appendPhpSnippetBeforeClosingTag, insertPhpSnippetBeforeWorkspaceAnchors, } from "./cli-add-workspace-mutation.js";
4
4
  import { hasPhpFunctionDefinition } from "./php-utils.js";
5
5
  const REST_RESOURCE_SERVER_GLOB = "/inc/rest/*.php";
6
+ const REST_SCHEMA_HELPER_PATH = "/inc/rest-schema.php";
7
+ /**
8
+ * Ensure the workspace bootstrap loads the shared REST schema helper file.
9
+ *
10
+ * @param workspace Resolved workspace project metadata and PHP prefix.
11
+ * @returns A promise that resolves after the bootstrap is patched.
12
+ * @throws When an existing loader does not reference `inc/rest-schema.php`.
13
+ */
14
+ export async function ensureRestSchemaHelperBootstrapAnchors(workspace) {
15
+ const bootstrapPath = getWorkspaceBootstrapPath(workspace);
16
+ await patchFile(bootstrapPath, (source) => {
17
+ let nextSource = source;
18
+ const loadFunctionName = `${workspace.workspace.phpPrefix}_load_rest_schema_helpers`;
19
+ const loadCall = `${loadFunctionName}();`;
20
+ const helperFunction = `
21
+
22
+ function ${loadFunctionName}() {
23
+ \t$helper_path = __DIR__ . '${REST_SCHEMA_HELPER_PATH}';
24
+ \tif ( is_readable( $helper_path ) ) {
25
+ \t\trequire_once $helper_path;
26
+ \t}
27
+ }
28
+
29
+ ${loadCall}
30
+ `;
31
+ if (!hasPhpFunctionDefinition(nextSource, loadFunctionName)) {
32
+ nextSource = insertPhpSnippetBeforeWorkspaceAnchors(nextSource, helperFunction);
33
+ }
34
+ else if (!nextSource.includes(REST_SCHEMA_HELPER_PATH)) {
35
+ throw new Error([
36
+ `Unable to patch ${path.basename(bootstrapPath)} in ensureRestSchemaHelperBootstrapAnchors.`,
37
+ `The existing ${loadFunctionName}() definition does not include ${REST_SCHEMA_HELPER_PATH}.`,
38
+ "Restore the generated bootstrap shape or load inc/rest-schema.php manually before retrying.",
39
+ ].join(" "));
40
+ }
41
+ else if (!nextSource.includes(loadCall)) {
42
+ nextSource = appendPhpSnippetBeforeClosingTag(nextSource, loadCall);
43
+ }
44
+ return nextSource;
45
+ });
46
+ }
6
47
  export async function ensureRestResourceBootstrapAnchors(workspace) {
7
48
  const bootstrapPath = getWorkspaceBootstrapPath(workspace);
8
49
  await patchFile(bootstrapPath, (source) => {
@@ -50,23 +91,296 @@ function replaceRequiredSyncRestSource(nextSource, target, anchor, replacement,
50
91
  assertSyncRestAnchor(nextSource, target, anchorDescription, hasAnchor, syncRestScriptPath);
51
92
  return nextSource.replace(anchor, replacement);
52
93
  }
94
+ function getSyncRestPatchErrorMessage(functionName, syncRestScriptPath, anchorDescription, subject) {
95
+ return [
96
+ `${functionName} could not patch ${path.basename(syncRestScriptPath)}.`,
97
+ `Missing expected ${anchorDescription} anchor in scripts/sync-rest-contracts.ts.`,
98
+ `Restore the generated template or add the ${subject} wiring manually before retrying.`,
99
+ ].join(" ");
100
+ }
101
+ function replaceBlockConfigImportForContracts(nextSource, syncRestScriptPath) {
102
+ const importPatterns = [
103
+ /^import\s*\{\n(?:\t[^\n]*\n)+\} from ["']\.\/block-config["'];?$/mu,
104
+ /^import\s*\{[^\n]*\}\s*from\s*["']\.\/block-config["'];?$/mu,
105
+ ];
106
+ const importMatch = importPatterns.map((pattern) => pattern.exec(nextSource)).find(Boolean) ??
107
+ null;
108
+ if (!importMatch) {
109
+ throw new Error(getSyncRestPatchErrorMessage("ensureContractSyncScriptAnchors", syncRestScriptPath, "block-config import", "CONTRACTS"));
110
+ }
111
+ const importSource = importMatch[0];
112
+ if (importSource.includes("CONTRACTS") &&
113
+ importSource.includes("WorkspaceContractConfig")) {
114
+ return nextSource;
115
+ }
116
+ if (!importSource.includes("BLOCKS") ||
117
+ !importSource.includes("WorkspaceBlockConfig")) {
118
+ throw new Error(getSyncRestPatchErrorMessage("ensureContractSyncScriptAnchors", syncRestScriptPath, "BLOCKS import", "CONTRACTS"));
119
+ }
120
+ const hasAiFeatures = importSource.includes("AI_FEATURES");
121
+ const hasAiFeatureConfig = importSource.includes("WorkspaceAiFeatureConfig");
122
+ const hasRestResources = importSource.includes("REST_RESOURCES");
123
+ const hasRestResourceConfig = importSource.includes("WorkspaceRestResourceConfig");
124
+ const hasPostMeta = importSource.includes("POST_META");
125
+ const hasPostMetaConfig = importSource.includes("WorkspacePostMetaConfig");
126
+ const replacement = [
127
+ "import {",
128
+ ...(hasAiFeatures ? ["\tAI_FEATURES,"] : []),
129
+ "\tBLOCKS,",
130
+ "\tCONTRACTS,",
131
+ ...(hasPostMeta ? ["\tPOST_META,"] : []),
132
+ ...(hasRestResources ? ["\tREST_RESOURCES,"] : []),
133
+ ...(hasAiFeatureConfig ? ["\ttype WorkspaceAiFeatureConfig,"] : []),
134
+ "\ttype WorkspaceBlockConfig,",
135
+ "\ttype WorkspaceContractConfig,",
136
+ ...(hasPostMetaConfig ? ["\ttype WorkspacePostMetaConfig,"] : []),
137
+ ...(hasRestResourceConfig ? ["\ttype WorkspaceRestResourceConfig,"] : []),
138
+ "} from './block-config';",
139
+ ].join("\n");
140
+ return nextSource.replace(importSource, replacement);
141
+ }
142
+ function replaceBlockConfigImportForRestResources(nextSource, syncRestScriptPath) {
143
+ const importPatterns = [
144
+ /^import\s*\{\n(?:\t[^\n]*\n)+\} from ["']\.\/block-config["'];?$/mu,
145
+ /^import\s*\{[^\n]*\}\s*from\s*["']\.\/block-config["'];?$/mu,
146
+ ];
147
+ const importMatch = importPatterns.map((pattern) => pattern.exec(nextSource)).find(Boolean) ??
148
+ null;
149
+ if (!importMatch) {
150
+ throw new Error(getSyncRestPatchErrorMessage("ensureRestResourceSyncScriptAnchors", syncRestScriptPath, "block-config import", "REST_RESOURCES"));
151
+ }
152
+ const importSource = importMatch[0];
153
+ if (importSource.includes("REST_RESOURCES") &&
154
+ importSource.includes("WorkspaceRestResourceConfig")) {
155
+ return nextSource;
156
+ }
157
+ if (!importSource.includes("BLOCKS") ||
158
+ !importSource.includes("WorkspaceBlockConfig")) {
159
+ throw new Error(getSyncRestPatchErrorMessage("ensureRestResourceSyncScriptAnchors", syncRestScriptPath, "BLOCKS import", "REST_RESOURCES"));
160
+ }
161
+ const hasAiFeatures = importSource.includes("AI_FEATURES");
162
+ const hasAiFeatureConfig = importSource.includes("WorkspaceAiFeatureConfig");
163
+ const hasContracts = importSource.includes("CONTRACTS");
164
+ const hasContractConfig = importSource.includes("WorkspaceContractConfig");
165
+ const hasPostMeta = importSource.includes("POST_META");
166
+ const hasPostMetaConfig = importSource.includes("WorkspacePostMetaConfig");
167
+ const replacement = [
168
+ "import {",
169
+ ...(hasAiFeatures ? ["\tAI_FEATURES,"] : []),
170
+ "\tBLOCKS,",
171
+ ...(hasContracts ? ["\tCONTRACTS,"] : []),
172
+ ...(hasPostMeta ? ["\tPOST_META,"] : []),
173
+ "\tREST_RESOURCES,",
174
+ ...(hasAiFeatureConfig ? ["\ttype WorkspaceAiFeatureConfig,"] : []),
175
+ "\ttype WorkspaceBlockConfig,",
176
+ ...(hasContractConfig ? ["\ttype WorkspaceContractConfig,"] : []),
177
+ ...(hasPostMetaConfig ? ["\ttype WorkspacePostMetaConfig,"] : []),
178
+ "\ttype WorkspaceRestResourceConfig,",
179
+ "} from './block-config';",
180
+ ].join("\n");
181
+ return nextSource.replace(importSource, replacement);
182
+ }
183
+ function replaceRequiredContractSyncRestSource(nextSource, target, anchor, replacement, anchorDescription, syncRestScriptPath) {
184
+ if (nextSource.includes(target)) {
185
+ return nextSource;
186
+ }
187
+ const hasAnchor = typeof anchor === "string" ? nextSource.includes(anchor) : anchor.test(nextSource);
188
+ if (!hasAnchor) {
189
+ throw new Error(getSyncRestPatchErrorMessage("ensureContractSyncScriptAnchors", syncRestScriptPath, anchorDescription, "CONTRACTS"));
190
+ }
191
+ return nextSource.replace(anchor, replacement);
192
+ }
193
+ function formatNoResourcesSubject(subjects) {
194
+ if (subjects.length <= 2) {
195
+ return subjects.join(" or ");
196
+ }
197
+ const lastSubject = subjects[subjects.length - 1];
198
+ return `${subjects.slice(0, -1).join(", ")}, or ${lastSubject}`;
199
+ }
200
+ function buildContractNoResourcesGuard({ hasAiFeatures, hasPostMeta, hasRestResources, }) {
201
+ const condition = ["restBlocks.length === 0 &&", "standaloneContracts.length === 0"];
202
+ if (hasPostMeta) {
203
+ condition[condition.length - 1] = `${condition[condition.length - 1]} &&`;
204
+ condition.push("postMetaContracts.length === 0");
205
+ }
206
+ if (hasRestResources) {
207
+ condition[condition.length - 1] = `${condition[condition.length - 1]} &&`;
208
+ condition.push("restResources.length === 0");
209
+ }
210
+ if (hasAiFeatures) {
211
+ condition[condition.length - 1] = `${condition[condition.length - 1]} &&`;
212
+ condition.push("aiFeatures.length === 0");
213
+ }
214
+ const noResourcesSubject = formatNoResourcesSubject([
215
+ "REST-enabled workspace blocks",
216
+ "standalone contracts",
217
+ ...(hasPostMeta ? ["post meta contracts"] : []),
218
+ ...(hasRestResources ? ["plugin-level REST resources"] : []),
219
+ ...(hasAiFeatures ? ["AI features"] : []),
220
+ ]);
221
+ return [
222
+ "if (",
223
+ ...condition.map((line) => `\t\t${line}`),
224
+ "\t) {",
225
+ "\t\tconsole.log(",
226
+ "\t\t\toptions.check",
227
+ `\t\t\t\t? 'ℹ️ No ${noResourcesSubject} are registered yet. \`sync-rest --check\` is already clean.'`,
228
+ `\t\t\t\t: 'ℹ️ No ${noResourcesSubject} are registered yet.'`,
229
+ "\t\t);",
230
+ "\t\treturn;",
231
+ "\t}",
232
+ ].join("\n");
233
+ }
234
+ const NO_RESOURCES_GUARD_PATTERN = /if \(\s*restBlocks\.length === 0(?:\s*&&\s*standaloneContracts\.length === 0)?(?:\s*&&\s*postMetaContracts\.length === 0)?(?:\s*&&\s*restResources\.length === 0)?(?:\s*&&\s*aiFeatures\.length === 0)?\s*\) \{[\s\S]*?\n\t\treturn;\n\t\}/u;
235
+ function replaceNoResourcesGuard(nextSource, replacement, functionName, syncRestScriptPath, subject) {
236
+ if (!NO_RESOURCES_GUARD_PATTERN.test(nextSource)) {
237
+ throw new Error(getSyncRestPatchErrorMessage(functionName, syncRestScriptPath, "no-resources guard", subject));
238
+ }
239
+ return nextSource.replace(NO_RESOURCES_GUARD_PATTERN, replacement);
240
+ }
241
+ function insertStandaloneContractFilter(nextSource, syncRestScriptPath) {
242
+ if (nextSource.includes("const standaloneContracts = CONTRACTS.filter")) {
243
+ return nextSource;
244
+ }
245
+ const restResourcesFilter = "const restResources = REST_RESOURCES.filter( isWorkspaceRestResource );";
246
+ if (nextSource.includes(restResourcesFilter)) {
247
+ return nextSource.replace(restResourcesFilter, [
248
+ "const standaloneContracts = CONTRACTS.filter( isWorkspaceStandaloneContract );",
249
+ restResourcesFilter,
250
+ ].join("\n\t"));
251
+ }
252
+ const restBlocksFilter = "const restBlocks = BLOCKS.filter( isRestEnabledBlock );";
253
+ return replaceRequiredContractSyncRestSource(nextSource, "const standaloneContracts = CONTRACTS.filter", restBlocksFilter, [
254
+ restBlocksFilter,
255
+ "const standaloneContracts = CONTRACTS.filter( isWorkspaceStandaloneContract );",
256
+ ].join("\n\t"), "restBlocks filter", syncRestScriptPath);
257
+ }
258
+ function insertStandaloneContractNoResourcesGuard(nextSource, syncRestScriptPath) {
259
+ const hasRestResources = nextSource.includes("const restResources = REST_RESOURCES.filter( isWorkspaceRestResource );");
260
+ const hasAiFeatures = nextSource.includes("const aiFeatures = AI_FEATURES.filter( isWorkspaceAiFeature );");
261
+ const hasPostMeta = nextSource.includes("const postMetaContracts = POST_META.filter( isWorkspacePostMetaContract );");
262
+ return replaceNoResourcesGuard(nextSource, buildContractNoResourcesGuard({
263
+ hasAiFeatures,
264
+ hasPostMeta,
265
+ hasRestResources,
266
+ }), "ensureContractSyncScriptAnchors", syncRestScriptPath, "CONTRACTS");
267
+ }
268
+ function buildRestResourceNoResourcesGuard({ hasAiFeatures, hasPostMeta, hasStandaloneContracts, }) {
269
+ const condition = ["restBlocks.length === 0"];
270
+ if (hasStandaloneContracts) {
271
+ condition[condition.length - 1] = `${condition[condition.length - 1]} &&`;
272
+ condition.push("standaloneContracts.length === 0");
273
+ }
274
+ if (hasPostMeta) {
275
+ condition[condition.length - 1] = `${condition[condition.length - 1]} &&`;
276
+ condition.push("postMetaContracts.length === 0");
277
+ }
278
+ condition[condition.length - 1] = `${condition[condition.length - 1]} &&`;
279
+ condition.push("restResources.length === 0");
280
+ if (hasAiFeatures) {
281
+ condition[condition.length - 1] = `${condition[condition.length - 1]} &&`;
282
+ condition.push("aiFeatures.length === 0");
283
+ }
284
+ const noResourcesSubject = formatNoResourcesSubject([
285
+ "REST-enabled workspace blocks",
286
+ ...(hasStandaloneContracts ? ["standalone contracts"] : []),
287
+ ...(hasPostMeta ? ["post meta contracts"] : []),
288
+ "plugin-level REST resources",
289
+ ...(hasAiFeatures ? ["AI features"] : []),
290
+ ]);
291
+ return [
292
+ "if (",
293
+ ...condition.map((line) => `\t\t${line}`),
294
+ "\t) {",
295
+ "\t\tconsole.log(",
296
+ "\t\t\toptions.check",
297
+ `\t\t\t\t? 'ℹ️ No ${noResourcesSubject} are registered yet. \`sync-rest --check\` is already clean.'`,
298
+ `\t\t\t\t: 'ℹ️ No ${noResourcesSubject} are registered yet.'`,
299
+ "\t\t);",
300
+ "\t\treturn;",
301
+ "\t}",
302
+ ].join("\n");
303
+ }
304
+ function insertRestResourceNoResourcesGuard(nextSource, syncRestScriptPath) {
305
+ const hasStandaloneContracts = nextSource.includes("const standaloneContracts = CONTRACTS.filter( isWorkspaceStandaloneContract );");
306
+ const hasAiFeatures = nextSource.includes("const aiFeatures = AI_FEATURES.filter( isWorkspaceAiFeature );");
307
+ const hasPostMeta = nextSource.includes("const postMetaContracts = POST_META.filter( isWorkspacePostMetaContract );");
308
+ return replaceNoResourcesGuard(nextSource, buildRestResourceNoResourcesGuard({
309
+ hasAiFeatures,
310
+ hasPostMeta,
311
+ hasStandaloneContracts,
312
+ }), "ensureRestResourceSyncScriptAnchors", syncRestScriptPath, "REST_RESOURCES");
313
+ }
314
+ function insertStandaloneContractSyncLoop(nextSource, syncRestScriptPath) {
315
+ if (nextSource.includes("for ( const contract of standaloneContracts )")) {
316
+ return nextSource;
317
+ }
318
+ const loopSource = [
319
+ "\tfor ( const contract of standaloneContracts ) {",
320
+ "\t\tawait syncTypeSchemas(",
321
+ "\t\t\t{",
322
+ "\t\t\t\tjsonSchemaFile: contract.schemaFile,",
323
+ "\t\t\t\tsourceTypeName: contract.sourceTypeName,",
324
+ "\t\t\t\ttypesFile: contract.typesFile,",
325
+ "\t\t\t},",
326
+ "\t\t\t{",
327
+ "\t\t\t\tcheck: options.check,",
328
+ "\t\t\t}",
329
+ "\t\t);",
330
+ "\t}",
331
+ ].join("\n");
332
+ const resourceLoopAnchor = "\n\tfor ( const resource of restResources ) {";
333
+ if (nextSource.includes(resourceLoopAnchor)) {
334
+ return nextSource.replace(resourceLoopAnchor, `\n${loopSource}\n${resourceLoopAnchor}`);
335
+ }
336
+ const consoleLogPattern = /\n\tconsole\.log\(\n\t\toptions\.check/u;
337
+ return replaceRequiredContractSyncRestSource(nextSource, "for ( const contract of standaloneContracts )", consoleLogPattern, [
338
+ "",
339
+ loopSource,
340
+ "",
341
+ "\tconsole.log(",
342
+ "\t\toptions.check",
343
+ ].join("\n"), "success log insertion point", syncRestScriptPath);
344
+ }
345
+ export async function ensureContractSyncScriptAnchors(workspace) {
346
+ const syncRestScriptPath = path.join(workspace.projectDir, "scripts", "sync-rest-contracts.ts");
347
+ await patchFile(syncRestScriptPath, (source) => {
348
+ let nextSource = replaceBlockConfigImportForContracts(source, syncRestScriptPath);
349
+ const helperInsertionAnchor = "async function assertTypeArtifactsCurrent";
350
+ nextSource = replaceRequiredContractSyncRestSource(nextSource, "function isWorkspaceStandaloneContract(", helperInsertionAnchor, [
351
+ "function isWorkspaceStandaloneContract(",
352
+ "\tcontract: WorkspaceContractConfig",
353
+ "): contract is WorkspaceContractConfig & {",
354
+ "\tschemaFile: string;",
355
+ "\tsourceTypeName: string;",
356
+ "\ttypesFile: string;",
357
+ "} {",
358
+ "\treturn (",
359
+ "\t\ttypeof contract.schemaFile === 'string' &&",
360
+ "\t\ttypeof contract.sourceTypeName === 'string' &&",
361
+ "\t\ttypeof contract.typesFile === 'string'",
362
+ "\t);",
363
+ "}",
364
+ "",
365
+ "async function assertTypeArtifactsCurrent",
366
+ ].join("\n"), "type artifact assertion helper", syncRestScriptPath);
367
+ nextSource = insertStandaloneContractFilter(nextSource, syncRestScriptPath);
368
+ nextSource = insertStandaloneContractNoResourcesGuard(nextSource, syncRestScriptPath);
369
+ nextSource = insertStandaloneContractSyncLoop(nextSource, syncRestScriptPath);
370
+ nextSource = nextSource.replace("✅ REST contract schemas, portable API clients, and endpoint-aware OpenAPI documents are already up to date for workspace blocks and plugin-level resources!", "✅ REST contract schemas, standalone schemas, portable API clients, and endpoint-aware OpenAPI documents are already up to date for workspace blocks, standalone contracts, and plugin-level resources!");
371
+ nextSource = nextSource.replace("✅ REST contract schemas, portable API clients, and endpoint-aware OpenAPI documents generated for workspace blocks and plugin-level resources!", "✅ REST contract schemas, standalone schemas, portable API clients, and endpoint-aware OpenAPI documents generated for workspace blocks, standalone contracts, and plugin-level resources!");
372
+ nextSource = nextSource.replace("✅ REST contract schemas, portable API clients, and endpoint-aware OpenAPI documents are already up to date with the TypeScript types!", "✅ REST contract schemas, standalone schemas, portable API clients, and endpoint-aware OpenAPI documents are already up to date with the TypeScript types!");
373
+ nextSource = nextSource.replace("✅ REST contract schemas, portable API clients, and endpoint-aware OpenAPI documents generated from TypeScript types!", "✅ REST contract schemas, standalone schemas, portable API clients, and endpoint-aware OpenAPI documents generated from TypeScript types!");
374
+ return nextSource;
375
+ });
376
+ }
53
377
  export async function ensureRestResourceSyncScriptAnchors(workspace) {
54
378
  const syncRestScriptPath = path.join(workspace.projectDir, "scripts", "sync-rest-contracts.ts");
55
379
  await patchFile(syncRestScriptPath, (source) => {
56
- let nextSource = source;
57
- const importAnchor = "import { BLOCKS, type WorkspaceBlockConfig } from './block-config';";
380
+ let nextSource = replaceBlockConfigImportForRestResources(source, syncRestScriptPath);
58
381
  const helperInsertionAnchor = "async function assertTypeArtifactsCurrent";
59
382
  const restBlocksAnchor = "const restBlocks = BLOCKS.filter( isRestEnabledBlock );";
60
- const noResourcesPattern = /if \( restBlocks.length === 0 \) \{[\s\S]*?\n\t\treturn;\n\t\}/u;
61
383
  const consoleLogPattern = /\n\tconsole\.log\(\n\t\toptions\.check/u;
62
- nextSource = replaceRequiredSyncRestSource(nextSource, "REST_RESOURCES", importAnchor, [
63
- "import {",
64
- "\tBLOCKS,",
65
- "\tREST_RESOURCES,",
66
- "\ttype WorkspaceBlockConfig,",
67
- "\ttype WorkspaceRestResourceConfig,",
68
- "} from './block-config';",
69
- ].join("\n"), "BLOCKS import", syncRestScriptPath);
70
384
  nextSource = replaceRequiredSyncRestSource(nextSource, "function isWorkspaceRestResource(", helperInsertionAnchor, [
71
385
  "function isWorkspaceRestResource(",
72
386
  "\tresource: WorkspaceRestResourceConfig",
@@ -93,16 +407,7 @@ export async function ensureRestResourceSyncScriptAnchors(workspace) {
93
407
  "const restBlocks = BLOCKS.filter( isRestEnabledBlock );",
94
408
  "const restResources = REST_RESOURCES.filter( isWorkspaceRestResource );",
95
409
  ].join("\n"), "restBlocks filter", syncRestScriptPath);
96
- nextSource = replaceRequiredSyncRestSource(nextSource, "restBlocks.length === 0 && restResources.length === 0", noResourcesPattern, [
97
- "if ( restBlocks.length === 0 && restResources.length === 0 ) {",
98
- "\t\tconsole.log(",
99
- "\t\t\toptions.check",
100
- "\t\t\t\t? 'ℹ️ No REST-enabled workspace blocks or plugin-level REST resources are registered yet. `sync-rest --check` is already clean.'",
101
- "\t\t\t\t: 'ℹ️ No REST-enabled workspace blocks or plugin-level REST resources are registered yet.'",
102
- "\t\t);",
103
- "\t\treturn;",
104
- "\t}",
105
- ].join("\n"), "no-resources guard", syncRestScriptPath);
410
+ nextSource = insertRestResourceNoResourcesGuard(nextSource, syncRestScriptPath);
106
411
  nextSource = replaceRequiredSyncRestSource(nextSource, "for ( const resource of restResources ) {", consoleLogPattern, [
107
412
  "",
108
413
  "\tfor ( const resource of restResources ) {",
@@ -0,0 +1,9 @@
1
+ import { type GeneratedRestResourceScaffoldOptions, type RunAddRestResourceCommandResult } from "./cli-add-workspace-rest-types.js";
2
+ /**
3
+ * Scaffold generated TypeScript, PHP, schema, and inventory files for a
4
+ * workspace-owned REST resource.
5
+ *
6
+ * @param options Resolved workspace and raw generated-mode command options.
7
+ * @returns Resolved scaffold metadata for the generated REST resource.
8
+ */
9
+ export declare function scaffoldGeneratedRestResource({ controllerClass, controllerExtends, methods, namespace, permissionCallback, restResourceSlug, routePattern, workspace, }: GeneratedRestResourceScaffoldOptions): Promise<RunAddRestResourceCommandResult>;
@@ -0,0 +1,158 @@
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 { assertValidRestResourceMethods, getWorkspaceBootstrapPath, resolveGeneratedRestResourceRoutePattern, resolveOptionalPhpCallbackReference, resolveOptionalPhpClassReference, rollbackWorkspaceMutation, snapshotWorkspaceFiles, } from "./cli-add-shared.js";
5
+ import { ensureRestResourceBootstrapAnchors, ensureRestSchemaHelperBootstrapAnchors, ensureRestResourceSyncScriptAnchors, } from "./cli-add-workspace-rest-anchors.js";
6
+ import { buildRestResourcePhpSource, buildWorkspaceRestSchemaHelperPhpSource, } from "./cli-add-workspace-rest-php-templates.js";
7
+ import { buildRestResourceApiSource, buildRestResourceConfigEntry, buildRestResourceDataSource, buildRestResourceTypesSource, buildRestResourceValidatorsSource, } from "./cli-add-workspace-rest-source-emitters.js";
8
+ import { hasPhpFunctionDefinition } from "./php-utils.js";
9
+ import { syncRestResourceArtifacts } from "./rest-resource-artifacts.js";
10
+ import { toPascalCase, toTitleCase } from "./string-case.js";
11
+ import { appendWorkspaceInventoryEntries } from "./workspace-inventory.js";
12
+ async function ensureWorkspaceRestSchemaHelperFile(helperFilePath, phpPrefix) {
13
+ let currentSource = null;
14
+ try {
15
+ currentSource = await fsp.readFile(helperFilePath, "utf8");
16
+ }
17
+ catch (error) {
18
+ if (error.code !== "ENOENT") {
19
+ throw error;
20
+ }
21
+ }
22
+ if (currentSource === null) {
23
+ await fsp.mkdir(path.dirname(helperFilePath), { recursive: true });
24
+ await fsp.writeFile(helperFilePath, buildWorkspaceRestSchemaHelperPhpSource(phpPrefix), "utf8");
25
+ return;
26
+ }
27
+ const requiredFunctions = [
28
+ `${phpPrefix}_load_rest_schema`,
29
+ `${phpPrefix}_prepare_rest_schema_for_wordpress`,
30
+ `${phpPrefix}_get_wordpress_rest_schema`,
31
+ `${phpPrefix}_validate_and_sanitize_rest_payload`,
32
+ ];
33
+ const missingFunctions = requiredFunctions.filter((functionName) => !hasPhpFunctionDefinition(currentSource, functionName));
34
+ if (missingFunctions.length > 0) {
35
+ throw new Error([
36
+ `Existing ${path.relative(process.cwd(), helperFilePath)} is missing generated REST schema helper functions: ${missingFunctions.join(", ")}.`,
37
+ "Restore the generated inc/rest-schema.php helper or add these functions manually before retrying.",
38
+ ].join(" "));
39
+ }
40
+ }
41
+ /**
42
+ * Scaffold generated TypeScript, PHP, schema, and inventory files for a
43
+ * workspace-owned REST resource.
44
+ *
45
+ * @param options Resolved workspace and raw generated-mode command options.
46
+ * @returns Resolved scaffold metadata for the generated REST resource.
47
+ */
48
+ export async function scaffoldGeneratedRestResource({ controllerClass, controllerExtends, methods, namespace, permissionCallback, restResourceSlug, routePattern, workspace, }) {
49
+ const blockConfigPath = path.join(workspace.projectDir, "scripts", "block-config.ts");
50
+ const syncRestScriptPath = path.join(workspace.projectDir, "scripts", "sync-rest-contracts.ts");
51
+ const restResourceDir = path.join(workspace.projectDir, "src", "rest", restResourceSlug);
52
+ const typesFilePath = path.join(restResourceDir, "api-types.ts");
53
+ const validatorsFilePath = path.join(restResourceDir, "api-validators.ts");
54
+ const apiFilePath = path.join(restResourceDir, "api.ts");
55
+ const resolvedMethods = assertValidRestResourceMethods(methods);
56
+ const resolvedRoutePattern = resolveGeneratedRestResourceRoutePattern(restResourceSlug, routePattern);
57
+ const hasCustomRoutePattern = typeof routePattern === "string" && routePattern.trim().length > 0;
58
+ const resolvedPermissionCallback = resolveOptionalPhpCallbackReference("Generated REST resource permission callback", permissionCallback);
59
+ const resolvedControllerClass = resolveOptionalPhpClassReference("Generated REST resource controller class", controllerClass);
60
+ const resolvedControllerExtends = resolveOptionalPhpClassReference("Generated REST resource controller base class", controllerExtends);
61
+ if (resolvedControllerExtends && !resolvedControllerClass) {
62
+ throw new Error("Generated REST resource controller base class requires --controller-class.");
63
+ }
64
+ const bootstrapPath = getWorkspaceBootstrapPath(workspace);
65
+ const dataFilePath = path.join(restResourceDir, "data.ts");
66
+ const restSchemaHelperPath = path.join(workspace.projectDir, "inc", "rest-schema.php");
67
+ const phpFilePath = path.join(workspace.projectDir, "inc", "rest", `${restResourceSlug}.php`);
68
+ const mutationSnapshot = {
69
+ fileSources: await snapshotWorkspaceFiles([
70
+ blockConfigPath,
71
+ bootstrapPath,
72
+ restSchemaHelperPath,
73
+ syncRestScriptPath,
74
+ ]),
75
+ snapshotDirs: [],
76
+ targetPaths: [restResourceDir, restSchemaHelperPath, phpFilePath],
77
+ };
78
+ try {
79
+ await fsp.mkdir(restResourceDir, { recursive: true });
80
+ await fsp.mkdir(path.dirname(phpFilePath), { recursive: true });
81
+ await ensureWorkspaceRestSchemaHelperFile(restSchemaHelperPath, workspace.workspace.phpPrefix);
82
+ await ensureRestSchemaHelperBootstrapAnchors(workspace);
83
+ await ensureRestResourceBootstrapAnchors(workspace);
84
+ await ensureRestResourceSyncScriptAnchors(workspace);
85
+ await fsp.writeFile(typesFilePath, buildRestResourceTypesSource(restResourceSlug, resolvedMethods), "utf8");
86
+ await fsp.writeFile(validatorsFilePath, buildRestResourceValidatorsSource(restResourceSlug, resolvedMethods), "utf8");
87
+ await fsp.writeFile(apiFilePath, buildRestResourceApiSource(restResourceSlug, resolvedMethods), "utf8");
88
+ await fsp.writeFile(dataFilePath, buildRestResourceDataSource(restResourceSlug, resolvedMethods), "utf8");
89
+ await fsp.writeFile(phpFilePath, buildRestResourcePhpSource(restResourceSlug, namespace, workspace.workspace.phpPrefix, resolvedMethods, {
90
+ ...(resolvedControllerClass
91
+ ? { controllerClass: resolvedControllerClass }
92
+ : {}),
93
+ ...(resolvedControllerExtends
94
+ ? { controllerExtends: resolvedControllerExtends }
95
+ : {}),
96
+ ...(resolvedPermissionCallback
97
+ ? { permissionCallback: resolvedPermissionCallback }
98
+ : {}),
99
+ routePattern: resolvedRoutePattern,
100
+ }), "utf8");
101
+ await syncRestResourceArtifacts({
102
+ clientFile: `src/rest/${restResourceSlug}/api-client.ts`,
103
+ methods: resolvedMethods,
104
+ outputDir: restResourceDir,
105
+ projectDir: workspace.projectDir,
106
+ typesFile: `src/rest/${restResourceSlug}/api-types.ts`,
107
+ validatorsFile: `src/rest/${restResourceSlug}/api-validators.ts`,
108
+ variables: {
109
+ namespace,
110
+ pascalCase: toPascalCase(restResourceSlug),
111
+ ...(hasCustomRoutePattern ? { routePattern: resolvedRoutePattern } : {}),
112
+ slugKebabCase: restResourceSlug,
113
+ title: toTitleCase(restResourceSlug),
114
+ },
115
+ });
116
+ await appendWorkspaceInventoryEntries(workspace.projectDir, {
117
+ restResourceEntries: [
118
+ buildRestResourceConfigEntry({
119
+ ...(resolvedControllerClass
120
+ ? { controllerClass: resolvedControllerClass }
121
+ : {}),
122
+ ...(resolvedControllerExtends
123
+ ? { controllerExtends: resolvedControllerExtends }
124
+ : {}),
125
+ methods: resolvedMethods,
126
+ namespace,
127
+ ...(resolvedPermissionCallback
128
+ ? { permissionCallback: resolvedPermissionCallback }
129
+ : {}),
130
+ restResourceSlug,
131
+ ...(hasCustomRoutePattern ? { routePattern: resolvedRoutePattern } : {}),
132
+ }),
133
+ ],
134
+ transformSource: ensureBlockConfigCanAddRestManifests,
135
+ });
136
+ return {
137
+ ...(resolvedControllerClass
138
+ ? { controllerClass: resolvedControllerClass }
139
+ : {}),
140
+ ...(resolvedControllerExtends
141
+ ? { controllerExtends: resolvedControllerExtends }
142
+ : {}),
143
+ methods: resolvedMethods,
144
+ mode: "generated",
145
+ namespace,
146
+ ...(resolvedPermissionCallback
147
+ ? { permissionCallback: resolvedPermissionCallback }
148
+ : {}),
149
+ projectDir: workspace.projectDir,
150
+ restResourceSlug,
151
+ ...(hasCustomRoutePattern ? { routePattern: resolvedRoutePattern } : {}),
152
+ };
153
+ }
154
+ catch (error) {
155
+ await rollbackWorkspaceMutation(mutationSnapshot);
156
+ throw error;
157
+ }
158
+ }
@@ -0,0 +1,8 @@
1
+ import { type ManualRestContractScaffoldOptions, type RunAddRestResourceCommandResult } from "./cli-add-workspace-rest-types.js";
2
+ /**
3
+ * Scaffold a type-only external REST contract for workspace consumers.
4
+ *
5
+ * @param options Resolved workspace and raw manual-mode command options.
6
+ * @returns Resolved scaffold metadata for the manual REST contract.
7
+ */
8
+ export declare function scaffoldManualRestContract({ auth, bodyTypeName, controllerClass, controllerExtends, method, namespace, pathPattern, permissionCallback, queryTypeName, responseTypeName, restResourceSlug, routePattern, secretFieldName, secretHasValueFieldName, secretMaskedResponseFieldName, secretPreserveOnEmpty, secretStateFieldName, workspace, }: ManualRestContractScaffoldOptions): Promise<RunAddRestResourceCommandResult>;