@wp-typia/project-tools 0.20.2 → 0.21.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 (47) hide show
  1. package/dist/runtime/cli-add-shared.d.ts +73 -5
  2. package/dist/runtime/cli-add-shared.js +58 -11
  3. package/dist/runtime/cli-add-workspace-ability.js +11 -57
  4. package/dist/runtime/cli-add-workspace-admin-view.d.ts +23 -0
  5. package/dist/runtime/cli-add-workspace-admin-view.js +872 -0
  6. package/dist/runtime/cli-add-workspace-ai-anchors.js +2 -5
  7. package/dist/runtime/cli-add-workspace-ai-source-emitters.d.ts +0 -4
  8. package/dist/runtime/cli-add-workspace-ai-source-emitters.js +7 -17
  9. package/dist/runtime/cli-add-workspace-ai.js +4 -6
  10. package/dist/runtime/cli-add-workspace-assets.d.ts +13 -5
  11. package/dist/runtime/cli-add-workspace-assets.js +290 -106
  12. package/dist/runtime/cli-add-workspace-rest-anchors.js +2 -5
  13. package/dist/runtime/cli-add-workspace-rest-source-emitters.d.ts +0 -1
  14. package/dist/runtime/cli-add-workspace-rest-source-emitters.js +7 -14
  15. package/dist/runtime/cli-add-workspace-rest.js +4 -6
  16. package/dist/runtime/cli-add-workspace.d.ts +58 -1
  17. package/dist/runtime/cli-add-workspace.js +588 -18
  18. package/dist/runtime/cli-add.d.ts +1 -1
  19. package/dist/runtime/cli-add.js +1 -1
  20. package/dist/runtime/cli-core.d.ts +8 -5
  21. package/dist/runtime/cli-core.js +7 -4
  22. package/dist/runtime/cli-diagnostics.d.ts +83 -1
  23. package/dist/runtime/cli-diagnostics.js +85 -2
  24. package/dist/runtime/cli-doctor-workspace.js +552 -13
  25. package/dist/runtime/cli-doctor.d.ts +4 -2
  26. package/dist/runtime/cli-doctor.js +2 -1
  27. package/dist/runtime/cli-help.js +19 -9
  28. package/dist/runtime/cli-init.d.ts +67 -3
  29. package/dist/runtime/cli-init.js +603 -64
  30. package/dist/runtime/cli-validation.js +4 -3
  31. package/dist/runtime/index.d.ts +9 -4
  32. package/dist/runtime/index.js +7 -3
  33. package/dist/runtime/package-json-types.d.ts +12 -0
  34. package/dist/runtime/package-json-types.js +1 -0
  35. package/dist/runtime/package-versions.d.ts +17 -2
  36. package/dist/runtime/package-versions.js +46 -1
  37. package/dist/runtime/php-utils.d.ts +16 -0
  38. package/dist/runtime/php-utils.js +59 -0
  39. package/dist/runtime/scaffold-answer-resolution.js +7 -6
  40. package/dist/runtime/scaffold-apply-utils.d.ts +2 -3
  41. package/dist/runtime/scaffold-apply-utils.js +3 -43
  42. package/dist/runtime/template-source-cache.d.ts +112 -0
  43. package/dist/runtime/template-source-cache.js +434 -0
  44. package/dist/runtime/template-source-seeds.js +319 -53
  45. package/dist/runtime/workspace-inventory.d.ts +43 -2
  46. package/dist/runtime/workspace-inventory.js +138 -5
  47. package/package.json +2 -2
@@ -5,7 +5,7 @@ export { normalizeBlockSlug, } from "./scaffold-identifiers.js";
5
5
  /**
6
6
  * Supported top-level `wp-typia add` kinds exposed by the canonical CLI.
7
7
  */
8
- export declare const ADD_KIND_IDS: readonly ["block", "variation", "pattern", "binding-source", "rest-resource", "ability", "ai-feature", "hooked-block", "editor-plugin"];
8
+ export declare const ADD_KIND_IDS: readonly ["admin-view", "block", "variation", "style", "transform", "pattern", "binding-source", "rest-resource", "ability", "ai-feature", "hooked-block", "editor-plugin"];
9
9
  export type AddKindId = (typeof ADD_KIND_IDS)[number];
10
10
  /**
11
11
  * Supported plugin-level REST resource methods accepted by
@@ -14,10 +14,18 @@ export type AddKindId = (typeof ADD_KIND_IDS)[number];
14
14
  export declare const REST_RESOURCE_METHOD_IDS: readonly ["list", "read", "create", "update", "delete"];
15
15
  export type RestResourceMethodId = (typeof REST_RESOURCE_METHOD_IDS)[number];
16
16
  /**
17
- * Supported editor-plugin shell slots accepted by `wp-typia add editor-plugin --slot`.
17
+ * Supported editor-plugin shell surfaces accepted by
18
+ * `wp-typia add editor-plugin --slot`.
18
19
  */
19
- export declare const EDITOR_PLUGIN_SLOT_IDS: readonly ["PluginSidebar"];
20
+ export declare const EDITOR_PLUGIN_SLOT_IDS: readonly ["sidebar", "document-setting-panel"];
20
21
  export type EditorPluginSlotId = (typeof EDITOR_PLUGIN_SLOT_IDS)[number];
22
+ export declare const EDITOR_PLUGIN_SLOT_ALIASES: {
23
+ readonly PluginDocumentSettingPanel: "document-setting-panel";
24
+ readonly PluginSidebar: "sidebar";
25
+ readonly "document-setting-panel": "document-setting-panel";
26
+ readonly sidebar: "sidebar";
27
+ };
28
+ export declare function resolveEditorPluginSlotAlias(slot: string): EditorPluginSlotId | undefined;
21
29
  /**
22
30
  * Supported built-in block families accepted by `wp-typia add block --template`.
23
31
  */
@@ -29,11 +37,45 @@ export interface RunAddVariationCommandOptions {
29
37
  cwd?: string;
30
38
  variationName: string;
31
39
  }
40
+ /**
41
+ * Options for `wp-typia add style`.
42
+ *
43
+ * @property blockName Existing workspace block slug that owns the style.
44
+ * @property cwd Working directory used to resolve the nearest official workspace.
45
+ * Defaults to `process.cwd()`.
46
+ * @property styleName Human-entered style name that will be normalized into the
47
+ * generated style slug.
48
+ */
49
+ export interface RunAddBlockStyleCommandOptions {
50
+ blockName: string;
51
+ cwd?: string;
52
+ styleName: string;
53
+ }
54
+ /**
55
+ * Options for `wp-typia add transform`.
56
+ *
57
+ * @property cwd Working directory used to resolve the nearest official workspace.
58
+ * Defaults to `process.cwd()`.
59
+ * @property fromBlockName Full `namespace/block` source block name accepted by
60
+ * WordPress block transform definitions.
61
+ * @property toBlockName Existing workspace block slug or full block name that
62
+ * owns the generated transform.
63
+ * @property transformName Human-entered transform name that will be normalized
64
+ * into the generated transform slug.
65
+ */
66
+ export interface RunAddBlockTransformCommandOptions {
67
+ cwd?: string;
68
+ fromBlockName: string;
69
+ toBlockName: string;
70
+ transformName: string;
71
+ }
32
72
  export interface RunAddPatternCommandOptions {
33
73
  cwd?: string;
34
74
  patternName: string;
35
75
  }
36
76
  export interface RunAddBindingSourceCommandOptions {
77
+ attributeName?: string;
78
+ blockName?: string;
37
79
  bindingSourceName: string;
38
80
  cwd?: string;
39
81
  }
@@ -43,6 +85,22 @@ export interface RunAddRestResourceCommandOptions {
43
85
  namespace?: string;
44
86
  restResourceName: string;
45
87
  }
88
+ /**
89
+ * Options for `wp-typia add admin-view`.
90
+ *
91
+ * @property adminViewName Human-entered admin screen name that will be
92
+ * normalized into the generated slug.
93
+ * @property cwd Working directory used to resolve the nearest official workspace.
94
+ * Defaults to `process.cwd()`.
95
+ * @property source Optional data source locator. The first supported source is
96
+ * `rest-resource:<slug>`, which wires the generated screen to an existing
97
+ * list-capable REST resource.
98
+ */
99
+ export interface RunAddAdminViewCommandOptions {
100
+ adminViewName: string;
101
+ cwd?: string;
102
+ source?: string;
103
+ }
46
104
  /**
47
105
  * Options for `wp-typia add ability`.
48
106
  *
@@ -73,7 +131,7 @@ export interface RunAddHookedBlockCommandOptions {
73
131
  * Defaults to `process.cwd()`.
74
132
  * @property editorPluginName Human-entered editor plugin name that will be
75
133
  * normalized into the generated slug.
76
- * @property slot Optional editor shell slot. Defaults to `PluginSidebar`.
134
+ * @property slot Optional editor shell slot. Defaults to `sidebar`.
77
135
  */
78
136
  export interface RunAddEditorPluginCommandOptions {
79
137
  cwd?: string;
@@ -143,7 +201,7 @@ export declare function assertValidHookAnchor(anchorBlockName: string): string;
143
201
  /**
144
202
  * Validate and normalize the editor plugin shell slot.
145
203
  *
146
- * @param slot Optional shell slot. Defaults to `PluginSidebar`.
204
+ * @param slot Optional shell slot. Defaults to `sidebar`.
147
205
  * @returns The canonical editor plugin slot id.
148
206
  * @throws {Error} When the slot is not supported by the workspace scaffold.
149
207
  */
@@ -157,6 +215,16 @@ export declare function assertVariationDoesNotExist(projectDir: string, blockSlu
157
215
  export declare function assertPatternDoesNotExist(projectDir: string, patternSlug: string, inventory: WorkspaceInventory): void;
158
216
  export declare function assertBindingSourceDoesNotExist(projectDir: string, bindingSourceSlug: string, inventory: WorkspaceInventory): void;
159
217
  export declare function assertRestResourceDoesNotExist(projectDir: string, restResourceSlug: string, inventory: WorkspaceInventory): void;
218
+ /**
219
+ * Ensure a DataViews admin screen scaffold does not already exist on disk or in
220
+ * the workspace inventory.
221
+ *
222
+ * @param projectDir Workspace root directory.
223
+ * @param adminViewSlug Normalized admin screen slug.
224
+ * @param inventory Parsed workspace inventory.
225
+ * @throws {Error} When the directory, PHP bootstrap, or inventory entry already exists.
226
+ */
227
+ export declare function assertAdminViewDoesNotExist(projectDir: string, adminViewSlug: string, inventory: WorkspaceInventory): void;
160
228
  /**
161
229
  * Ensure a workflow ability scaffold does not already exist on disk or in the
162
230
  * workspace inventory.
@@ -10,8 +10,11 @@ export { normalizeBlockSlug, } from "./scaffold-identifiers.js";
10
10
  * Supported top-level `wp-typia add` kinds exposed by the canonical CLI.
11
11
  */
12
12
  export const ADD_KIND_IDS = [
13
+ "admin-view",
13
14
  "block",
14
15
  "variation",
16
+ "style",
17
+ "transform",
15
18
  "pattern",
16
19
  "binding-source",
17
20
  "rest-resource",
@@ -32,9 +35,23 @@ export const REST_RESOURCE_METHOD_IDS = [
32
35
  "delete",
33
36
  ];
34
37
  /**
35
- * Supported editor-plugin shell slots accepted by `wp-typia add editor-plugin --slot`.
38
+ * Supported editor-plugin shell surfaces accepted by
39
+ * `wp-typia add editor-plugin --slot`.
36
40
  */
37
- export const EDITOR_PLUGIN_SLOT_IDS = ["PluginSidebar"];
41
+ export const EDITOR_PLUGIN_SLOT_IDS = ["sidebar", "document-setting-panel"];
42
+ export const EDITOR_PLUGIN_SLOT_ALIASES = {
43
+ PluginDocumentSettingPanel: "document-setting-panel",
44
+ PluginSidebar: "sidebar",
45
+ "document-setting-panel": "document-setting-panel",
46
+ sidebar: "sidebar",
47
+ };
48
+ export function resolveEditorPluginSlotAlias(slot) {
49
+ const trimmed = slot.trim();
50
+ if (!Object.prototype.hasOwnProperty.call(EDITOR_PLUGIN_SLOT_ALIASES, trimmed)) {
51
+ return undefined;
52
+ }
53
+ return EDITOR_PLUGIN_SLOT_ALIASES[trimmed];
54
+ }
38
55
  /**
39
56
  * Supported built-in block families accepted by `wp-typia add block --template`.
40
57
  */
@@ -175,15 +192,16 @@ export function assertValidHookAnchor(anchorBlockName) {
175
192
  /**
176
193
  * Validate and normalize the editor plugin shell slot.
177
194
  *
178
- * @param slot Optional shell slot. Defaults to `PluginSidebar`.
195
+ * @param slot Optional shell slot. Defaults to `sidebar`.
179
196
  * @returns The canonical editor plugin slot id.
180
197
  * @throws {Error} When the slot is not supported by the workspace scaffold.
181
198
  */
182
- export function assertValidEditorPluginSlot(slot = "PluginSidebar") {
183
- if (EDITOR_PLUGIN_SLOT_IDS.includes(slot)) {
184
- return slot;
199
+ export function assertValidEditorPluginSlot(slot = "sidebar") {
200
+ const alias = resolveEditorPluginSlotAlias(slot);
201
+ if (alias) {
202
+ return alias;
185
203
  }
186
- throw new Error(`Editor plugin slot must be one of: ${EDITOR_PLUGIN_SLOT_IDS.join(", ")}.`);
204
+ throw new Error(`Editor plugin slot must be one of: ${EDITOR_PLUGIN_SLOT_IDS.join(", ")}. Legacy aliases: PluginSidebar, PluginDocumentSettingPanel.`);
187
205
  }
188
206
  export function readWorkspaceBlockJson(projectDir, blockSlug) {
189
207
  const blockJsonPath = path.join(projectDir, "src", "blocks", blockSlug, "block.json");
@@ -256,6 +274,28 @@ export function assertRestResourceDoesNotExist(projectDir, restResourceSlug, inv
256
274
  throw new Error(`A REST resource inventory entry already exists for ${restResourceSlug}. Choose a different name.`);
257
275
  }
258
276
  }
277
+ /**
278
+ * Ensure a DataViews admin screen scaffold does not already exist on disk or in
279
+ * the workspace inventory.
280
+ *
281
+ * @param projectDir Workspace root directory.
282
+ * @param adminViewSlug Normalized admin screen slug.
283
+ * @param inventory Parsed workspace inventory.
284
+ * @throws {Error} When the directory, PHP bootstrap, or inventory entry already exists.
285
+ */
286
+ export function assertAdminViewDoesNotExist(projectDir, adminViewSlug, inventory) {
287
+ const adminViewDir = path.join(projectDir, "src", "admin-views", adminViewSlug);
288
+ const adminViewPhpPath = path.join(projectDir, "inc", "admin-views", `${adminViewSlug}.php`);
289
+ if (fs.existsSync(adminViewDir)) {
290
+ throw new Error(`An admin view already exists at ${path.relative(projectDir, adminViewDir)}. Choose a different name.`);
291
+ }
292
+ if (fs.existsSync(adminViewPhpPath)) {
293
+ throw new Error(`An admin view bootstrap already exists at ${path.relative(projectDir, adminViewPhpPath)}. Choose a different name.`);
294
+ }
295
+ if (inventory.adminViews.some((entry) => entry.slug === adminViewSlug)) {
296
+ throw new Error(`An admin view inventory entry already exists for ${adminViewSlug}. Choose a different name.`);
297
+ }
298
+ }
259
299
  /**
260
300
  * Ensure a workflow ability scaffold does not already exist on disk or in the
261
301
  * workspace inventory.
@@ -317,10 +357,13 @@ export function assertEditorPluginDoesNotExist(projectDir, editorPluginSlug, inv
317
357
  */
318
358
  export function formatAddHelpText() {
319
359
  return `Usage:
320
- wp-typia add block <name> --template <${ADD_BLOCK_TEMPLATE_IDS.join("|")}> [--external-layer-source <./path|github:owner/repo/path[#ref]|npm-package>] [--external-layer-id <layer-id>] [--inner-blocks-preset <freeform|ordered|horizontal|locked-structure>] [--alternate-render-targets <email,mjml,plain-text>] [--data-storage <post-meta|custom-table>] [--persistence-policy <authenticated|public>] [--dry-run]
360
+ wp-typia add admin-view <name> [--source <rest-resource:slug>] [--dry-run]
361
+ wp-typia add block <name> [--template <${ADD_BLOCK_TEMPLATE_IDS.join("|")}>] [--external-layer-source <./path|github:owner/repo/path[#ref]|npm-package>] [--external-layer-id <layer-id>] [--inner-blocks-preset <freeform|ordered|horizontal|locked-structure>] [--alternate-render-targets <email,mjml,plain-text>] [--data-storage <post-meta|custom-table>] [--persistence-policy <authenticated|public>] [--dry-run]
321
362
  wp-typia add variation <name> --block <block-slug> [--dry-run]
363
+ wp-typia add style <name> --block <block-slug> [--dry-run]
364
+ wp-typia add transform <name> --from <namespace/block> --to <block-slug|namespace/block-slug> [--dry-run]
322
365
  wp-typia add pattern <name> [--dry-run]
323
- wp-typia add binding-source <name> [--dry-run]
366
+ wp-typia add binding-source <name> [--block <block-slug|namespace/block-slug> --attribute <attribute>] [--dry-run]
324
367
  wp-typia add rest-resource <name> [--namespace <vendor/v1>] [--methods <list,read,create,update,delete>] [--dry-run]
325
368
  wp-typia add ability <name> [--dry-run]
326
369
  wp-typia add ai-feature <name> [--namespace <vendor/v1>] [--dry-run]
@@ -330,13 +373,17 @@ export function formatAddHelpText() {
330
373
  Notes:
331
374
  \`wp-typia add\` runs only inside official ${WORKSPACE_TEMPLATE_PACKAGE} workspaces scaffolded via \`wp-typia create <project-dir> --template workspace\`.
332
375
  Pass \`--dry-run\` to preview the workspace files that would change without writing them.
376
+ Interactive add flows let you choose a template when \`--template\` is omitted; non-interactive runs default to \`basic\`.
377
+ \`add admin-view\` scaffolds an opt-in DataViews-powered WordPress admin screen under \`src/admin-views/\`; pass \`--source rest-resource:<slug>\` to reuse a list-capable REST resource.
333
378
  \`query-loop\` is a create-time scaffold family. Use \`wp-typia create <project-dir> --template query-loop\` instead of \`wp-typia add block\`.
334
379
  \`add variation\` targets an existing block slug from \`scripts/block-config.ts\`.
380
+ \`add style\` registers a Block Styles option for an existing generated block.
381
+ \`add transform\` adds a block-to-block transform into an existing generated block.
335
382
  \`add pattern\` scaffolds a namespaced PHP pattern shell under \`src/patterns/\`.
336
- \`add binding-source\` scaffolds shared PHP and editor registration under \`src/bindings/\`.
383
+ \`add binding-source\` scaffolds shared PHP and editor registration under \`src/bindings/\`; pass \`--block\` and \`--attribute\` together to declare an end-to-end bindable attribute on an existing generated block.
337
384
  \`add rest-resource\` scaffolds plugin-level TypeScript REST contracts under \`src/rest/\` and PHP route glue under \`inc/rest/\`.
338
385
  \`add ability\` scaffolds typed workflow abilities under \`src/abilities/\` and server registration under \`inc/abilities/\`.
339
386
  \`add ai-feature\` scaffolds server-owned AI feature endpoints under \`src/ai-features/\` and PHP route glue under \`inc/ai-features/\`.
340
387
  \`add hooked-block\` patches an existing workspace block's \`block.json\` \`blockHooks\` metadata.
341
- \`add editor-plugin\` scaffolds a document-level editor extension under \`src/editor-plugins/\`.`;
388
+ \`add editor-plugin\` scaffolds a document-level editor extension under \`src/editor-plugins/\`; legacy aliases \`PluginSidebar\` and \`PluginDocumentSettingPanel\` resolve to \`sidebar\` and \`document-setting-panel\`.`;
342
389
  }
@@ -5,7 +5,8 @@ import { syncTypeSchemas } from "@wp-typia/block-runtime/metadata-core";
5
5
  import semver from "semver";
6
6
  import { appendWorkspaceInventoryEntries, readWorkspaceInventory, } from "./workspace-inventory.js";
7
7
  import { resolveWorkspaceProject } from "./workspace-project.js";
8
- import { toTitleCase } from "./string-case.js";
8
+ import { toPascalCase, toTitleCase } from "./string-case.js";
9
+ import { escapeRegex, findPhpFunctionRange, hasPhpFunctionDefinition, quotePhpString, replacePhpFunctionDefinition, } from "./php-utils.js";
9
10
  import { assertAbilityDoesNotExist, assertValidGeneratedSlug, getWorkspaceBootstrapPath, normalizeBlockSlug, patchFile, quoteTsString, rollbackWorkspaceMutation, snapshotWorkspaceFiles, } from "./cli-add-shared.js";
10
11
  import { REQUIRED_WORKSPACE_ABILITY_COMPATIBILITY, renderScaffoldCompatibilityConfig, resolveScaffoldCompatibilityPolicy, updatePluginHeaderCompatibility, } from "./scaffold-compatibility.js";
11
12
  const ABILITY_SERVER_GLOB = "/inc/abilities/*.php";
@@ -17,46 +18,6 @@ const WP_ABILITIES_PACKAGE_VERSION = "^0.10.0";
17
18
  const WP_CORE_ABILITIES_PACKAGE_VERSION = "^0.9.0";
18
19
  const WP_ABILITIES_SCRIPT_MODULE_ID = "@wordpress/abilities";
19
20
  const WP_CORE_ABILITIES_SCRIPT_MODULE_ID = "@wordpress/core-abilities";
20
- function escapeRegex(value) {
21
- return value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
22
- }
23
- function quotePhpString(value) {
24
- return `'${value.replace(/\\/gu, "\\\\").replace(/'/gu, "\\'")}'`;
25
- }
26
- function findPhpFunctionRange(source, functionName) {
27
- const functionPattern = new RegExp(`function\\s+${escapeRegex(functionName)}\\s*\\([^)]*\\)\\s*\\{`, "u");
28
- const match = functionPattern.exec(source);
29
- if (!match) {
30
- return null;
31
- }
32
- const openingBraceIndex = match.index + match[0].length - 1;
33
- let depth = 0;
34
- for (let index = openingBraceIndex; index < source.length; index += 1) {
35
- const character = source[index];
36
- if (character === "{") {
37
- depth += 1;
38
- }
39
- else if (character === "}") {
40
- depth -= 1;
41
- if (depth === 0) {
42
- const end = index + 1;
43
- return {
44
- end,
45
- source: source.slice(match.index, end),
46
- start: match.index,
47
- };
48
- }
49
- }
50
- }
51
- return null;
52
- }
53
- function replacePhpFunctionDefinition(source, functionName, replacement) {
54
- const functionRange = findPhpFunctionRange(source, functionName);
55
- if (!functionRange) {
56
- return source;
57
- }
58
- return `${source.slice(0, functionRange.start)}${replacement.trimStart()}${source.slice(functionRange.end)}`;
59
- }
60
21
  function resolveManagedDependencyVersion(existingVersion, requiredVersion) {
61
22
  if (!existingVersion) {
62
23
  return requiredVersion;
@@ -70,13 +31,6 @@ function resolveManagedDependencyVersion(existingVersion, requiredVersion) {
70
31
  ? existingVersion
71
32
  : requiredVersion;
72
33
  }
73
- function toPascalCaseFromAbilitySlug(abilitySlug) {
74
- return normalizeBlockSlug(abilitySlug)
75
- .split("-")
76
- .filter(Boolean)
77
- .map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1))
78
- .join("");
79
- }
80
34
  function toAbilityCategorySlug(workspaceNamespace) {
81
35
  const normalizedNamespace = workspaceNamespace
82
36
  .replace(/[^a-z0-9-]+/gu, "-")
@@ -85,7 +39,7 @@ function toAbilityCategorySlug(workspaceNamespace) {
85
39
  return `${normalizedNamespace || "workspace"}-workflows`;
86
40
  }
87
41
  function buildAbilityConfigEntry(abilitySlug, compatibilityPolicy) {
88
- const pascalCase = toPascalCaseFromAbilitySlug(abilitySlug);
42
+ const pascalCase = toPascalCase(abilitySlug);
89
43
  return [
90
44
  "\t{",
91
45
  `\t\tclientFile: ${quoteTsString(`src/abilities/${abilitySlug}/client.ts`)},`,
@@ -127,7 +81,7 @@ function buildAbilityConfigSource(abilitySlug, workspaceNamespace) {
127
81
  }, null, 2)}\n`;
128
82
  }
129
83
  function buildAbilityTypesSource(abilitySlug) {
130
- const pascalCase = toPascalCaseFromAbilitySlug(abilitySlug);
84
+ const pascalCase = toPascalCase(abilitySlug);
131
85
  return `export interface ${pascalCase}AbilityInput {
132
86
  \tcontextId: number;
133
87
  \tnote?: string;
@@ -142,7 +96,7 @@ export interface ${pascalCase}AbilityOutput {
142
96
  `;
143
97
  }
144
98
  function buildAbilityDataSource(abilitySlug) {
145
- const pascalCase = toPascalCaseFromAbilitySlug(abilitySlug);
99
+ const pascalCase = toPascalCase(abilitySlug);
146
100
  const abilityConstBase = abilitySlug
147
101
  .toUpperCase()
148
102
  .replace(/[^A-Z0-9]+/gu, "_")
@@ -242,7 +196,7 @@ export async function run${pascalCase}Ability(
242
196
  `;
243
197
  }
244
198
  function buildAbilityClientSource(abilitySlug) {
245
- const pascalCase = toPascalCaseFromAbilitySlug(abilitySlug);
199
+ const pascalCase = toPascalCase(abilitySlug);
246
200
  return `/**
247
201
  * Re-export the typed ${pascalCase} ability client helpers.
248
202
  *
@@ -651,7 +605,6 @@ function ${enqueueFunctionName}() {
651
605
  /add_action\(\s*["']init["']\s*,\s*["'][^"']+_load_textdomain["']\s*\);\s*\n/u,
652
606
  /\?>\s*$/u,
653
607
  ];
654
- const hasPhpFunctionDefinition = (functionName) => new RegExp(`function\\s+${escapeRegex(functionName)}\\s*\\(`, "u").test(nextSource);
655
608
  const insertPhpSnippet = (snippet) => {
656
609
  for (const anchor of insertionAnchors) {
657
610
  const candidate = nextSource.replace(anchor, (match) => `${snippet}\n${match}`);
@@ -670,14 +623,15 @@ function ${enqueueFunctionName}() {
670
623
  }
671
624
  nextSource = `${nextSource.trimEnd()}\n${snippet}\n`;
672
625
  };
673
- if (!hasPhpFunctionDefinition(loadFunctionName)) {
626
+ if (!hasPhpFunctionDefinition(nextSource, loadFunctionName)) {
674
627
  insertPhpSnippet(loadFunction);
675
628
  }
676
- if (!hasPhpFunctionDefinition(enqueueFunctionName)) {
629
+ if (!hasPhpFunctionDefinition(nextSource, enqueueFunctionName)) {
677
630
  insertPhpSnippet(enqueueFunction);
678
631
  }
679
632
  else if (!findPhpFunctionRange(nextSource, enqueueFunctionName)?.source.includes("wp_enqueue_script_module")) {
680
- nextSource = replacePhpFunctionDefinition(nextSource, enqueueFunctionName, enqueueFunction);
633
+ nextSource =
634
+ replacePhpFunctionDefinition(nextSource, enqueueFunctionName, enqueueFunction, { trimReplacementStart: true }) ?? nextSource;
681
635
  }
682
636
  if (!nextSource.includes(loadHook)) {
683
637
  appendPhpSnippet(loadHook);
@@ -882,7 +836,7 @@ export async function runAddAbilityCommand({ abilityName, cwd = process.cwd(), }
882
836
  await fsp.writeFile(dataFilePath, buildAbilityDataSource(abilitySlug), "utf8");
883
837
  await fsp.writeFile(clientFilePath, buildAbilityClientSource(abilitySlug), "utf8");
884
838
  await fsp.writeFile(phpFilePath, buildAbilityPhpSource(abilitySlug, workspace), "utf8");
885
- const pascalCase = toPascalCaseFromAbilitySlug(abilitySlug);
839
+ const pascalCase = toPascalCase(abilitySlug);
886
840
  await syncTypeSchemas({
887
841
  jsonSchemaFile: `src/abilities/${abilitySlug}/input.schema.json`,
888
842
  projectRoot: workspace.projectDir,
@@ -0,0 +1,23 @@
1
+ import { type RunAddAdminViewCommandOptions } from "./cli-add-shared.js";
2
+ /**
3
+ * Add one DataViews-powered WordPress admin screen scaffold to an official
4
+ * workspace project.
5
+ *
6
+ * @param options Command options for the admin-view scaffold workflow.
7
+ * @param options.adminViewName Human-entered admin screen name that will be
8
+ * normalized and validated before files are written.
9
+ * @param options.cwd Working directory used to resolve the nearest official workspace.
10
+ * Defaults to `process.cwd()`.
11
+ * @param options.source Optional data source locator. `rest-resource:<slug>`
12
+ * wires the screen to an existing list-capable REST resource.
13
+ * @returns A promise that resolves with the normalized `adminViewSlug`, optional
14
+ * `source`, and owning `projectDir` after scaffold files and inventory entries
15
+ * are written successfully.
16
+ * @throws {Error} When the command is run outside an official workspace, when
17
+ * the slug/source is invalid, or when a conflicting file or inventory entry exists.
18
+ */
19
+ export declare function runAddAdminViewCommand({ adminViewName, cwd, source, }: RunAddAdminViewCommandOptions): Promise<{
20
+ adminViewSlug: string;
21
+ projectDir: string;
22
+ source?: string;
23
+ }>;