@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
@@ -1,11 +1,101 @@
1
1
  import { HOOKED_BLOCK_ANCHOR_PATTERN, HOOKED_BLOCK_POSITION_IDS, } from "./hooked-blocks.js";
2
2
  import { toSnakeCase, } from "./string-case.js";
3
- import { ADD_BLOCK_TEMPLATE_IDS, EDITOR_PLUGIN_SLOT_IDS, REST_RESOURCE_METHOD_IDS, resolveEditorPluginSlotAlias, } from "./cli-add-types.js";
3
+ import { ADD_BLOCK_TEMPLATE_IDS, EDITOR_PLUGIN_SLOT_IDS, INTEGRATION_ENV_SERVICE_IDS, MANUAL_REST_CONTRACT_AUTH_IDS, MANUAL_REST_CONTRACT_HTTP_METHOD_IDS, REST_RESOURCE_METHOD_IDS, resolveEditorPluginSlotAlias, } from "./cli-add-types.js";
4
4
  const WORKSPACE_GENERATED_SLUG_PATTERN = /^[a-z][a-z0-9-]*$/;
5
+ const WORDPRESS_POST_TYPE_PATTERN = /^[a-z0-9_][a-z0-9_-]*$/u;
6
+ const TYPESCRIPT_IDENTIFIER_PATTERN = /^[A-Za-z_$][A-Za-z0-9_$]*$/u;
7
+ const TYPESCRIPT_RESERVED_IDENTIFIERS = new Set([
8
+ "abstract",
9
+ "any",
10
+ "as",
11
+ "asserts",
12
+ "async",
13
+ "await",
14
+ "bigint",
15
+ "boolean",
16
+ "break",
17
+ "case",
18
+ "catch",
19
+ "class",
20
+ "const",
21
+ "constructor",
22
+ "continue",
23
+ "debugger",
24
+ "declare",
25
+ "default",
26
+ "delete",
27
+ "do",
28
+ "else",
29
+ "enum",
30
+ "export",
31
+ "extends",
32
+ "false",
33
+ "finally",
34
+ "for",
35
+ "from",
36
+ "function",
37
+ "get",
38
+ "global",
39
+ "if",
40
+ "implements",
41
+ "import",
42
+ "in",
43
+ "infer",
44
+ "instanceof",
45
+ "interface",
46
+ "intrinsic",
47
+ "is",
48
+ "keyof",
49
+ "let",
50
+ "module",
51
+ "namespace",
52
+ "never",
53
+ "new",
54
+ "null",
55
+ "number",
56
+ "object",
57
+ "of",
58
+ "out",
59
+ "override",
60
+ "package",
61
+ "private",
62
+ "protected",
63
+ "public",
64
+ "readonly",
65
+ "require",
66
+ "return",
67
+ "satisfies",
68
+ "set",
69
+ "static",
70
+ "string",
71
+ "super",
72
+ "switch",
73
+ "symbol",
74
+ "this",
75
+ "throw",
76
+ "true",
77
+ "try",
78
+ "type",
79
+ "typeof",
80
+ "undefined",
81
+ "unique",
82
+ "unknown",
83
+ "using",
84
+ "var",
85
+ "void",
86
+ "while",
87
+ "with",
88
+ "yield",
89
+ ]);
5
90
  /**
6
91
  * Namespace format accepted by plugin-level REST resources.
7
92
  */
8
93
  export const REST_RESOURCE_NAMESPACE_PATTERN = /^[a-z][a-z0-9-]*(?:\/[a-z0-9-]+)+$/u;
94
+ const PHP_IDENTIFIER_PATTERN = "[A-Za-z_][A-Za-z0-9_]*";
95
+ const PHP_QUALIFIED_NAME_PATTERN = new RegExp(`^\\\\?${PHP_IDENTIFIER_PATTERN}(?:\\\\${PHP_IDENTIFIER_PATTERN})*$`, "u");
96
+ const PHP_CALLBACK_REFERENCE_PATTERN = new RegExp(`^\\\\?${PHP_IDENTIFIER_PATTERN}(?:\\\\${PHP_IDENTIFIER_PATTERN})*(?:::${PHP_IDENTIFIER_PATTERN})?$`, "u");
97
+ const REST_ROUTE_NAMED_CAPTURE_PATTERN = /\(\?P<([A-Za-z_][A-Za-z0-9_]*)>/gu;
98
+ const REST_ROUTE_UNSUPPORTED_CAPTURE_PATTERN = /\((?!\?(?:P<[A-Za-z_][A-Za-z0-9_]*>|:))/u;
9
99
  /**
10
100
  * Validate a normalized workspace-generated slug.
11
101
  *
@@ -24,6 +114,28 @@ export function assertValidGeneratedSlug(label, slug, usage) {
24
114
  }
25
115
  return slug;
26
116
  }
117
+ /**
118
+ * Validate a source type name used by generated schema artifact workflows.
119
+ *
120
+ * @param label Human-readable field label used in error messages.
121
+ * @param value TypeScript identifier candidate from CLI input or defaults.
122
+ * @param usage CLI usage hint shown when the identifier is empty.
123
+ * @returns The trimmed, validated TypeScript identifier.
124
+ * @throws {Error} When the value is empty or not a TypeScript identifier.
125
+ */
126
+ export function assertValidTypeScriptIdentifier(label, value, usage) {
127
+ const trimmed = value.trim();
128
+ if (!trimmed) {
129
+ throw new Error(`${label} is required. Use \`${usage}\`.`);
130
+ }
131
+ if (!TYPESCRIPT_IDENTIFIER_PATTERN.test(trimmed)) {
132
+ throw new Error(`${label} must be a valid TypeScript identifier, such as ExternalRetrieveResponse.`);
133
+ }
134
+ if (TYPESCRIPT_RESERVED_IDENTIFIERS.has(trimmed)) {
135
+ throw new Error(`${label} must not be a reserved TypeScript keyword, such as ${trimmed}.`);
136
+ }
137
+ return trimmed;
138
+ }
27
139
  /**
28
140
  * Validate a REST resource namespace.
29
141
  *
@@ -52,6 +164,44 @@ export function assertValidRestResourceNamespace(namespace) {
52
164
  export function resolveRestResourceNamespace(workspaceNamespace, namespace) {
53
165
  return assertValidRestResourceNamespace(namespace ?? `${workspaceNamespace}/v1`);
54
166
  }
167
+ /**
168
+ * Validate a WordPress post type key used as post-meta scope.
169
+ *
170
+ * @param postType Raw post type key from CLI input.
171
+ * @returns The trimmed post type key.
172
+ * @throws {Error} When the key is empty or contains unsupported characters.
173
+ */
174
+ export function assertValidPostMetaPostType(postType) {
175
+ const trimmed = postType.trim();
176
+ if (!trimmed) {
177
+ throw new Error("`wp-typia add post-meta` requires --post-type <post-type>.");
178
+ }
179
+ if (trimmed.length > 20) {
180
+ throw new Error("Post meta post type must be 20 characters or fewer.");
181
+ }
182
+ if (!WORDPRESS_POST_TYPE_PATTERN.test(trimmed)) {
183
+ throw new Error("Post meta post type must use a WordPress post type key such as `post` or `example_post_type`.");
184
+ }
185
+ return trimmed;
186
+ }
187
+ /**
188
+ * Resolve a post-meta key from explicit input or the workspace slug default.
189
+ *
190
+ * @param options Optional explicit meta key plus workspace prefix and slug.
191
+ * @returns A validated post-meta key.
192
+ * @throws {Error} When the key is empty or contains whitespace/control characters.
193
+ */
194
+ export function resolvePostMetaKey({ metaKey, phpPrefix, slug, }) {
195
+ const resolvedMetaKey = metaKey ?? `_${toSnakeCase(`${phpPrefix}_${slug}`)}`;
196
+ const trimmed = resolvedMetaKey.trim();
197
+ if (!trimmed) {
198
+ throw new Error("Post meta key cannot be empty.");
199
+ }
200
+ if (/[\p{Cc}\s]/u.test(trimmed)) {
201
+ throw new Error("Post meta key must not contain whitespace or control characters.");
202
+ }
203
+ return trimmed;
204
+ }
55
205
  /**
56
206
  * Parse and validate REST resource method ids from a comma-separated list.
57
207
  *
@@ -73,6 +223,154 @@ export function assertValidRestResourceMethods(methods) {
73
223
  }
74
224
  return normalizedMethods;
75
225
  }
226
+ /**
227
+ * Validate a PHP callable reference supplied to generated REST route metadata.
228
+ *
229
+ * @param label Human-readable field label for errors.
230
+ * @param callback Optional PHP function or `ClassName::method` callback string.
231
+ * @returns The trimmed callback when present.
232
+ * @throws {Error} When the callback cannot be emitted as a safe PHP callable string.
233
+ */
234
+ export function resolveOptionalPhpCallbackReference(label, callback) {
235
+ if (callback == null) {
236
+ return undefined;
237
+ }
238
+ const trimmed = callback.trim();
239
+ if (!trimmed) {
240
+ throw new Error(`${label} cannot be empty.`);
241
+ }
242
+ if (!PHP_CALLBACK_REFERENCE_PATTERN.test(trimmed)) {
243
+ throw new Error(`${label} must be a PHP function reference or ClassName::method callback.`);
244
+ }
245
+ return trimmed;
246
+ }
247
+ /**
248
+ * Validate a PHP class reference used by generated REST controller wrappers.
249
+ *
250
+ * @param label Human-readable field label for errors.
251
+ * @param classReference Optional PHP class name.
252
+ * @returns The trimmed class reference when present.
253
+ * @throws {Error} When the class reference cannot be emitted safely.
254
+ */
255
+ export function resolveOptionalPhpClassReference(label, classReference) {
256
+ if (classReference == null) {
257
+ return undefined;
258
+ }
259
+ const trimmed = classReference.trim();
260
+ if (!trimmed) {
261
+ throw new Error(`${label} cannot be empty.`);
262
+ }
263
+ if (!PHP_QUALIFIED_NAME_PATTERN.test(trimmed)) {
264
+ throw new Error(`${label} must be a PHP class reference such as Demo_Rest_Controller or Vendor\\Plugin\\Controller.`);
265
+ }
266
+ return trimmed;
267
+ }
268
+ /**
269
+ * Normalize and validate the HTTP method used by a manual REST contract.
270
+ *
271
+ * @param method Optional method input. Defaults to GET.
272
+ * @returns A canonical uppercase HTTP method.
273
+ * @throws {Error} When the method is unsupported.
274
+ */
275
+ export function assertValidManualRestContractHttpMethod(method = "GET") {
276
+ const normalized = method.trim().toUpperCase();
277
+ if (MANUAL_REST_CONTRACT_HTTP_METHOD_IDS.includes(normalized)) {
278
+ return normalized;
279
+ }
280
+ throw new Error(`Manual REST contract method must be one of: ${MANUAL_REST_CONTRACT_HTTP_METHOD_IDS.join(", ")}.`);
281
+ }
282
+ /**
283
+ * Normalize and validate the auth intent used by a manual REST contract.
284
+ *
285
+ * @param auth Optional auth intent input. Defaults to public.
286
+ * @returns A canonical auth intent.
287
+ * @throws {Error} When the auth intent is unsupported.
288
+ */
289
+ export function assertValidManualRestContractAuth(auth = "public") {
290
+ const normalized = auth.trim().toLowerCase();
291
+ if (MANUAL_REST_CONTRACT_AUTH_IDS.includes(normalized)) {
292
+ return normalized;
293
+ }
294
+ throw new Error(`Manual REST contract auth must be one of: ${MANUAL_REST_CONTRACT_AUTH_IDS.join(", ")}.`);
295
+ }
296
+ /**
297
+ * Normalize and validate a manual REST contract route path pattern.
298
+ *
299
+ * @param slug Generated contract slug used for the default route path.
300
+ * @param pathPattern Optional route path pattern, relative to the namespace.
301
+ * @returns A route pattern with a leading slash.
302
+ * @throws {Error} When the path pattern is empty or clearly not a route path.
303
+ */
304
+ function resolveRestRoutePathPattern(options) {
305
+ const explicitPath = typeof options.pathPattern === "string" ? options.pathPattern.trim() : undefined;
306
+ if (explicitPath === "") {
307
+ throw new Error(options.emptyMessage);
308
+ }
309
+ const trimmed = explicitPath ?? options.defaultPath;
310
+ if (/^https?:\/\//iu.test(trimmed)) {
311
+ throw new Error(`${options.label} must be a route pattern relative to the namespace, not an absolute URL.`);
312
+ }
313
+ const withLeadingSlash = trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
314
+ if (!withLeadingSlash || withLeadingSlash === "/") {
315
+ throw new Error(options.emptyMessage);
316
+ }
317
+ if (/\s/u.test(withLeadingSlash)) {
318
+ throw new Error(`${options.label} must not contain whitespace.`);
319
+ }
320
+ return withLeadingSlash;
321
+ }
322
+ /**
323
+ * Check whether a generated REST item route keeps the generated `id` contract aligned.
324
+ *
325
+ * @param routePattern Route pattern relative to the namespace.
326
+ * @returns True when the pattern has no regex groups or uses only `(?P<id>...)`.
327
+ */
328
+ export function isGeneratedRestResourceRoutePatternCompatible(routePattern) {
329
+ const namedCaptures = Array.from(routePattern.matchAll(REST_ROUTE_NAMED_CAPTURE_PATTERN), (match) => match[1]);
330
+ const hasRegexGroup = routePattern.includes("(");
331
+ const hasUnsupportedCapture = REST_ROUTE_UNSUPPORTED_CAPTURE_PATTERN.test(routePattern);
332
+ return (!hasUnsupportedCapture &&
333
+ (!hasRegexGroup || (namedCaptures.length === 1 && namedCaptures[0] === "id")));
334
+ }
335
+ /**
336
+ * Collect unique WordPress named capture parameters from a REST route pattern.
337
+ *
338
+ * @param routePattern Route pattern relative to the namespace.
339
+ * @returns Capture names in first-seen order.
340
+ */
341
+ export function collectRestRouteNamedCaptureNames(routePattern) {
342
+ return Array.from(new Set(Array.from(routePattern.matchAll(REST_ROUTE_NAMED_CAPTURE_PATTERN), (match) => match[1])));
343
+ }
344
+ export function resolveManualRestContractPathPattern(slug, pathPattern) {
345
+ return resolveRestRoutePathPattern({
346
+ defaultPath: `/${slug}`,
347
+ emptyMessage: "Manual REST contract path is required. Use `--path <route-pattern>` such as `/external-record/(?P<id>[\\d]+)`.",
348
+ label: "Manual REST contract path",
349
+ pathPattern,
350
+ });
351
+ }
352
+ /**
353
+ * Normalize and validate a generated REST resource item route pattern.
354
+ *
355
+ * @param slug Generated resource slug used for the default item route path.
356
+ * @param routePattern Optional route path pattern, relative to the namespace.
357
+ * @returns A route pattern with a leading slash.
358
+ * @throws {Error} When the route pattern is empty or clearly not a route path.
359
+ */
360
+ export function resolveGeneratedRestResourceRoutePattern(slug, routePattern) {
361
+ const resolvedRoutePattern = resolveRestRoutePathPattern({
362
+ defaultPath: `/${slug}/item`,
363
+ emptyMessage: "Generated REST resource route pattern is required. Use `--route-pattern <route-pattern>` such as `/records/(?P<id>[\\d]+)`.",
364
+ label: "Generated REST resource route pattern",
365
+ pathPattern: routePattern,
366
+ });
367
+ const hasExplicitRoutePattern = typeof routePattern === "string" && routePattern.trim().length > 0;
368
+ if (hasExplicitRoutePattern &&
369
+ !isGeneratedRestResourceRoutePatternCompatible(resolvedRoutePattern)) {
370
+ throw new Error("Generated REST resource route pattern must use only an `(?P<id>...)` named capture when using regex groups.");
371
+ }
372
+ return resolvedRoutePattern;
373
+ }
76
374
  /**
77
375
  * Validate a hooked block insertion position.
78
376
  *
@@ -145,3 +443,17 @@ export function assertValidEditorPluginSlot(slot = "sidebar") {
145
443
  }
146
444
  throw new Error(`Editor plugin slot must be one of: ${EDITOR_PLUGIN_SLOT_IDS.join(", ")}. Legacy aliases: PluginSidebar, PluginDocumentSettingPanel.`);
147
445
  }
446
+ /**
447
+ * Validate and normalize the optional integration environment service starter.
448
+ *
449
+ * @param service Optional service starter id. Defaults to `none`.
450
+ * @returns The canonical integration environment service id.
451
+ * @throws {Error} When the service starter is unsupported.
452
+ */
453
+ export function assertValidIntegrationEnvService(service = "none") {
454
+ const trimmed = service.trim();
455
+ if (INTEGRATION_ENV_SERVICE_IDS.includes(trimmed)) {
456
+ return trimmed;
457
+ }
458
+ throw new Error(`Integration environment service must be one of: ${INTEGRATION_ENV_SERVICE_IDS.join(", ")}.`);
459
+ }
@@ -4,6 +4,7 @@ import { syncTypeSchemas } from "@wp-typia/block-runtime/metadata-core";
4
4
  import semver from "semver";
5
5
  import { appendWorkspaceInventoryEntries, readWorkspaceInventoryAsync, } from "./workspace-inventory.js";
6
6
  import { pathExists, readOptionalUtf8File } from "./fs-async.js";
7
+ import { readJsonFile } from "./json-utils.js";
7
8
  import { buildAbilityClientSource, buildAbilityConfigEntry, buildAbilityConfigSource, buildAbilityDataSource, buildAbilityPhpSource, buildAbilityRegistrySource, buildAbilitySyncScriptSource, buildAbilityTypesSource, } from "./cli-add-workspace-ability-templates.js";
8
9
  import { ABILITY_EDITOR_ASSET, ABILITY_EDITOR_SCRIPT, ABILITY_REGISTRY_END_MARKER, ABILITY_REGISTRY_START_MARKER, ABILITY_SERVER_GLOB, WP_ABILITIES_SCRIPT_MODULE_ID, WP_CORE_ABILITIES_SCRIPT_MODULE_ID, } from "./cli-add-workspace-ability-types.js";
9
10
  import { getWorkspaceBootstrapPath, patchFile, } from "./cli-add-shared.js";
@@ -163,7 +164,9 @@ function ${enqueueFunctionName}() {
163
164
  }
164
165
  async function ensureAbilityPackageScripts(workspace) {
165
166
  const packageJsonPath = path.join(workspace.projectDir, "package.json");
166
- const packageJson = JSON.parse(await fsp.readFile(packageJsonPath, "utf8"));
167
+ const packageJson = await readJsonFile(packageJsonPath, {
168
+ context: "workspace package manifest",
169
+ });
167
170
  const nextScripts = {
168
171
  ...(packageJson.scripts ?? {}),
169
172
  "sync-abilities": packageJson.scripts?.["sync-abilities"] ?? "tsx scripts/sync-abilities.ts",
@@ -1,9 +1,11 @@
1
1
  import { promises as fsp } from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { pathExists, readOptionalUtf8File } from './fs-async.js';
4
+ import { safeJsonParse } from './json-utils.js';
4
5
  import { appendWorkspaceInventoryEntries, readWorkspaceInventoryAsync, } from './workspace-inventory.js';
5
- import { buildAdminViewConfigEntry, buildAdminViewConfigSource, buildAdminViewEntrySource, buildAdminViewPhpSource, buildAdminViewRegistrySource, buildAdminViewScreenSource, buildAdminViewStyleSource, buildAdminViewTypesSource, buildCoreDataAdminViewDataSource, buildCoreDataAdminViewScreenSource, buildDefaultAdminViewDataSource, buildRestAdminViewDataSource, } from './cli-add-workspace-admin-view-templates.js';
6
- import { ADMIN_VIEWS_PHP_GLOB, isAdminViewCoreDataSource, } from './cli-add-workspace-admin-view-types.js';
6
+ import { buildAdminViewConfigEntry, buildAdminViewConfigSource, buildAdminViewEntrySource, buildAdminViewPhpSource, buildAdminViewRegistrySource, buildAdminViewScreenSource, buildAdminViewStyleSource, buildAdminViewTypesSource, buildCoreDataAdminViewDataSource, buildCoreDataAdminViewScreenSource, buildDefaultAdminViewDataSource, buildRestAdminViewDataSource, buildRestSettingsAdminViewConfigSource, buildRestSettingsAdminViewDataSource, buildRestSettingsAdminViewScreenSource, buildRestSettingsAdminViewTypesSource, } from './cli-add-workspace-admin-view-templates.js';
7
+ import { ADMIN_VIEWS_PHP_GLOB, isAdminViewCoreDataSource, isAdminViewManualSettingsRestResource, } from './cli-add-workspace-admin-view-types.js';
8
+ import { buildManualRestContractApiSource } from './cli-add-workspace-rest-source-emitters.js';
7
9
  import { getWorkspaceBootstrapPath, patchFile, } from './cli-add-shared.js';
8
10
  import { appendPhpSnippetBeforeClosingTag, executeWorkspaceMutationPlan, insertPhpSnippetBeforeWorkspaceAnchors, } from './cli-add-workspace-mutation.js';
9
11
  import { DEFAULT_WORDPRESS_CORE_DATA_VERSION, DEFAULT_WORDPRESS_DATA_VERSION, DEFAULT_WORDPRESS_DATAVIEWS_VERSION, DEFAULT_WP_TYPIA_DATAVIEWS_VERSION, resolveManagedPackageVersionRange, } from './package-versions.js';
@@ -12,7 +14,34 @@ function detectJsonIndent(source) {
12
14
  const indentMatch = /\n([ \t]+)"/u.exec(source);
13
15
  return indentMatch?.[1] ?? 2;
14
16
  }
15
- async function ensureAdminViewPackageDependencies(workspace, adminViewSource) {
17
+ const LEGACY_MANUAL_REST_API_SOURCE_PATTERN = /^\s*export\s+\*\s+from\s+["']\.\/api-client["'];?\s*$/u;
18
+ const MANUAL_REST_API_DIRECT_CALL_EXPORT_PATTERN = /(?:^|\n)\s*export\s+(?:(?:async\s+)?function|const|let|var)\s+callManualRestContract\b/u;
19
+ const MANUAL_REST_API_NAMED_EXPORT_PATTERN = /(?:^|\n)\s*export\s*\{([^}]*)\}/gu;
20
+ function hasManualRestContractCallerExport(apiSource) {
21
+ if (MANUAL_REST_API_DIRECT_CALL_EXPORT_PATTERN.test(apiSource)) {
22
+ return true;
23
+ }
24
+ return Array.from(apiSource.matchAll(MANUAL_REST_API_NAMED_EXPORT_PATTERN)).some((match) => match[1].split(',').some((specifier) => {
25
+ const [localName, exportedName] = specifier.trim().split(/\s+as\s+/u);
26
+ return (exportedName ?? localName)?.trim() === 'callManualRestContract';
27
+ }));
28
+ }
29
+ async function ensureManualSettingsRestApiShim(workspace, restResource) {
30
+ const apiPath = path.join(workspace.projectDir, restResource.apiFile);
31
+ const apiSource = await fsp.readFile(apiPath, 'utf8');
32
+ if (hasManualRestContractCallerExport(apiSource)) {
33
+ return;
34
+ }
35
+ if (!LEGACY_MANUAL_REST_API_SOURCE_PATTERN.test(apiSource)) {
36
+ throw new Error(`Manual REST resource source "${restResource.slug}" must export callManualRestContract from ${restResource.apiFile}. Restore the generated manual REST api.ts or update it before scaffolding a settings admin view.`);
37
+ }
38
+ await fsp.writeFile(apiPath, buildManualRestContractApiSource({
39
+ bodyTypeName: restResource.bodyTypeName,
40
+ queryTypeName: restResource.queryTypeName,
41
+ restResourceSlug: restResource.slug,
42
+ }), 'utf8');
43
+ }
44
+ async function ensureAdminViewPackageDependencies(workspace, adminViewSource, restResource) {
16
45
  const packageJsonPath = path.join(workspace.projectDir, 'package.json');
17
46
  const wpTypiaDataViewsVersion = resolveManagedPackageVersionRange({
18
47
  fallback: DEFAULT_WP_TYPIA_DATAVIEWS_VERSION,
@@ -32,7 +61,11 @@ async function ensureAdminViewPackageDependencies(workspace, adminViewSource) {
32
61
  packageName: '@wordpress/data',
33
62
  });
34
63
  await patchFile(packageJsonPath, (source) => {
35
- const packageJson = JSON.parse(source);
64
+ const packageJson = safeJsonParse(source, {
65
+ context: 'admin view package manifest',
66
+ filePath: packageJsonPath,
67
+ });
68
+ const needsDataViews = !isAdminViewManualSettingsRestResource(restResource);
36
69
  const coreDataDependencies = isAdminViewCoreDataSource(adminViewSource)
37
70
  ? {
38
71
  '@wordpress/core-data': packageJson.dependencies?.['@wordpress/core-data'] ??
@@ -43,14 +76,22 @@ async function ensureAdminViewPackageDependencies(workspace, adminViewSource) {
43
76
  : {};
44
77
  const nextDependencies = {
45
78
  ...(packageJson.dependencies ?? {}),
46
- '@wordpress/dataviews': packageJson.dependencies?.['@wordpress/dataviews'] ??
47
- wordpressDataViewsVersion,
79
+ ...(needsDataViews
80
+ ? {
81
+ '@wordpress/dataviews': packageJson.dependencies?.['@wordpress/dataviews'] ??
82
+ wordpressDataViewsVersion,
83
+ }
84
+ : {}),
48
85
  ...coreDataDependencies,
49
86
  };
50
87
  const nextDevDependencies = {
51
88
  ...(packageJson.devDependencies ?? {}),
52
- '@wp-typia/dataviews': packageJson.devDependencies?.['@wp-typia/dataviews'] ??
53
- wpTypiaDataViewsVersion,
89
+ ...(needsDataViews
90
+ ? {
91
+ '@wp-typia/dataviews': packageJson.devDependencies?.['@wp-typia/dataviews'] ??
92
+ wpTypiaDataViewsVersion,
93
+ }
94
+ : {}),
54
95
  };
55
96
  if (JSON.stringify(nextDependencies) ===
56
97
  JSON.stringify(packageJson.dependencies ?? {}) &&
@@ -193,6 +234,10 @@ export async function scaffoldAdminViewWorkspace(options) {
193
234
  const webpackConfigPath = path.join(workspace.projectDir, 'webpack.config.js');
194
235
  const adminViewsIndexPath = await resolveAdminViewRegistryPath(workspace.projectDir);
195
236
  const adminViewDir = path.join(workspace.projectDir, 'src', 'admin-views', adminViewSlug);
237
+ const manualSettingsRestResource = isAdminViewManualSettingsRestResource(restResource) ? restResource : undefined;
238
+ const manualSettingsRestApiPath = manualSettingsRestResource
239
+ ? path.join(workspace.projectDir, manualSettingsRestResource.apiFile)
240
+ : undefined;
196
241
  const adminViewPhpPath = path.join(workspace.projectDir, 'inc', 'admin-views', `${adminViewSlug}.php`);
197
242
  await executeWorkspaceMutationPlan({
198
243
  filePaths: [
@@ -202,26 +247,40 @@ export async function scaffoldAdminViewWorkspace(options) {
202
247
  buildScriptPath,
203
248
  packageJsonPath,
204
249
  webpackConfigPath,
250
+ ...(manualSettingsRestApiPath ? [manualSettingsRestApiPath] : []),
205
251
  ],
206
252
  targetPaths: [adminViewDir, adminViewPhpPath],
207
253
  run: async () => {
208
254
  await fsp.mkdir(adminViewDir, { recursive: true });
209
255
  await fsp.mkdir(path.dirname(adminViewPhpPath), { recursive: true });
210
- await ensureAdminViewPackageDependencies(workspace, parsedSource);
256
+ await ensureAdminViewPackageDependencies(workspace, parsedSource, restResource);
211
257
  await ensureAdminViewBootstrapAnchors(workspace);
212
258
  await ensureAdminViewBuildScriptAnchors(workspace);
213
259
  await ensureAdminViewWebpackAnchors(workspace);
214
- await fsp.writeFile(path.join(adminViewDir, 'types.ts'), buildAdminViewTypesSource(adminViewSlug, restResource, coreDataSource), 'utf8');
215
- await fsp.writeFile(path.join(adminViewDir, 'config.ts'), buildAdminViewConfigSource(adminViewSlug, workspace.workspace.textDomain, parsedSource, restResource), 'utf8');
216
- await fsp.writeFile(path.join(adminViewDir, 'data.ts'), coreDataSource
217
- ? buildCoreDataAdminViewDataSource(adminViewSlug, coreDataSource)
218
- : restResource
219
- ? buildRestAdminViewDataSource(adminViewSlug, restResource)
220
- : buildDefaultAdminViewDataSource(adminViewSlug), 'utf8');
221
- await fsp.writeFile(path.join(adminViewDir, 'Screen.tsx'), coreDataSource
222
- ? buildCoreDataAdminViewScreenSource(adminViewSlug, workspace.workspace.textDomain)
223
- : buildAdminViewScreenSource(adminViewSlug, workspace.workspace.textDomain), 'utf8');
224
- await fsp.writeFile(path.join(adminViewDir, 'index.tsx'), buildAdminViewEntrySource(adminViewSlug), 'utf8');
260
+ if (manualSettingsRestResource) {
261
+ await ensureManualSettingsRestApiShim(workspace, manualSettingsRestResource);
262
+ }
263
+ await fsp.writeFile(path.join(adminViewDir, 'types.ts'), manualSettingsRestResource
264
+ ? buildRestSettingsAdminViewTypesSource(adminViewSlug, manualSettingsRestResource)
265
+ : buildAdminViewTypesSource(adminViewSlug, restResource, coreDataSource), 'utf8');
266
+ await fsp.writeFile(path.join(adminViewDir, 'config.ts'), manualSettingsRestResource
267
+ ? buildRestSettingsAdminViewConfigSource(adminViewSlug, workspace.workspace.textDomain, manualSettingsRestResource)
268
+ : buildAdminViewConfigSource(adminViewSlug, workspace.workspace.textDomain, parsedSource, restResource), 'utf8');
269
+ await fsp.writeFile(path.join(adminViewDir, 'data.ts'), manualSettingsRestResource
270
+ ? buildRestSettingsAdminViewDataSource(adminViewSlug, manualSettingsRestResource)
271
+ : coreDataSource
272
+ ? buildCoreDataAdminViewDataSource(adminViewSlug, coreDataSource)
273
+ : restResource
274
+ ? buildRestAdminViewDataSource(adminViewSlug, restResource)
275
+ : buildDefaultAdminViewDataSource(adminViewSlug), 'utf8');
276
+ await fsp.writeFile(path.join(adminViewDir, 'Screen.tsx'), manualSettingsRestResource
277
+ ? buildRestSettingsAdminViewScreenSource(adminViewSlug, workspace.workspace.textDomain)
278
+ : coreDataSource
279
+ ? buildCoreDataAdminViewScreenSource(adminViewSlug, workspace.workspace.textDomain)
280
+ : buildAdminViewScreenSource(adminViewSlug, workspace.workspace.textDomain), 'utf8');
281
+ await fsp.writeFile(path.join(adminViewDir, 'index.tsx'), buildAdminViewEntrySource(adminViewSlug, {
282
+ includeDataViewsStyle: !manualSettingsRestResource,
283
+ }), 'utf8');
225
284
  await fsp.writeFile(path.join(adminViewDir, 'style.scss'), buildAdminViewStyleSource(), 'utf8');
226
285
  await fsp.writeFile(adminViewPhpPath, buildAdminViewPhpSource(adminViewSlug, workspace), 'utf8');
227
286
  await writeAdminViewRegistry(workspace.projectDir, adminViewSlug);
@@ -1,5 +1,5 @@
1
1
  import { assertValidGeneratedSlug } from './cli-add-shared.js';
2
- import { ADMIN_VIEW_CORE_DATA_ENTITY_KIND_IDS, ADMIN_VIEW_CORE_DATA_ENTITY_NAME_PATTERN, ADMIN_VIEW_CORE_DATA_ENTITY_SEGMENT_PATTERN, ADMIN_VIEW_CORE_DATA_SOURCE_KIND, ADMIN_VIEW_REST_SOURCE_KIND, ADMIN_VIEW_SOURCE_USAGE, isAdminViewCoreDataSource, isAdminViewRestResourceSource, } from './cli-add-workspace-admin-view-types.js';
2
+ import { ADMIN_VIEW_CORE_DATA_ENTITY_KIND_IDS, ADMIN_VIEW_CORE_DATA_ENTITY_NAME_PATTERN, ADMIN_VIEW_CORE_DATA_ENTITY_SEGMENT_PATTERN, ADMIN_VIEW_CORE_DATA_SOURCE_KIND, ADMIN_VIEW_REST_SOURCE_KIND, ADMIN_VIEW_SOURCE_USAGE, hasAdminViewManualSettingsRouteParameters, isAdminViewCoreDataSource, isAdminViewManualSettingsRestResource, isAdminViewRestResourceSource, } from './cli-add-workspace-admin-view-types.js';
3
3
  /**
4
4
  * Assert that admin-view package dependencies are available before file writes.
5
5
  *
@@ -75,8 +75,17 @@ export function resolveRestResourceSource(restResources, source) {
75
75
  if (!restResource) {
76
76
  throw new Error(`Unknown REST resource source "${source.slug}". Choose one of: ${restResources.map((entry) => entry.slug).join(', ') || '<none>'}.`);
77
77
  }
78
+ if (isAdminViewManualSettingsRestResource(restResource)) {
79
+ if (hasAdminViewManualSettingsRouteParameters(restResource)) {
80
+ throw new Error(`REST resource source "${source.slug}" uses route parameters or regex groups and cannot scaffold a singleton admin settings form. Use a manual REST contract without path parameters or build a custom admin UI.`);
81
+ }
82
+ return restResource;
83
+ }
84
+ if (restResource.mode === 'manual') {
85
+ throw new Error(`REST resource source "${source.slug}" must define a request body type before it can scaffold an admin settings form.`);
86
+ }
78
87
  if (!restResource.methods.includes('list')) {
79
- throw new Error(`REST resource source "${source.slug}" must include the list method for DataViews pagination.`);
88
+ throw new Error(`REST resource source "${source.slug}" must include the list method for DataViews pagination or be a manual settings contract with a body type.`);
80
89
  }
81
90
  return restResource;
82
91
  }
@@ -0,0 +1,34 @@
1
+ import { type AdminViewCoreDataSource } from './cli-add-workspace-admin-view-types.js';
2
+ /**
3
+ * Builds TypeScript item and dataset types for a core-data-backed admin view.
4
+ *
5
+ * @param adminViewSlug - Admin-view slug used to derive generated type names.
6
+ * @param coreDataSource - WordPress core-data entity source for the admin view.
7
+ * @returns Generated TypeScript source for core-data admin-view types.
8
+ */
9
+ export declare function buildCoreDataAdminViewTypesSource(adminViewSlug: string, coreDataSource: AdminViewCoreDataSource): string;
10
+ /**
11
+ * Builds a DataViews config module for a core-data-backed admin view.
12
+ *
13
+ * @param adminViewSlug - Admin-view slug used to derive generated identifiers.
14
+ * @param textDomain - WordPress i18n text domain for generated labels.
15
+ * @param coreDataSource - WordPress core-data entity source for field selection.
16
+ * @returns Generated TypeScript source for the core-data DataViews config.
17
+ */
18
+ export declare function buildCoreDataAdminViewConfigSource(adminViewSlug: string, textDomain: string, coreDataSource: AdminViewCoreDataSource): string;
19
+ /**
20
+ * Builds data hooks for loading records from the WordPress core-data store.
21
+ *
22
+ * @param adminViewSlug - Admin-view slug used to derive generated identifiers.
23
+ * @param coreDataSource - WordPress core-data entity source for generated hooks.
24
+ * @returns Generated TypeScript source for core-data data access.
25
+ */
26
+ export declare function buildCoreDataAdminViewDataSource(adminViewSlug: string, coreDataSource: AdminViewCoreDataSource): string;
27
+ /**
28
+ * Builds the React screen module for a core-data-backed admin view.
29
+ *
30
+ * @param adminViewSlug - Admin-view slug used to derive generated identifiers.
31
+ * @param textDomain - WordPress i18n text domain for generated labels.
32
+ * @returns Generated TSX source for the core-data admin-view screen.
33
+ */
34
+ export declare function buildCoreDataAdminViewScreenSource(adminViewSlug: string, textDomain: string): string;