@wp-typia/project-tools 0.22.2 → 0.22.4
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.
- package/dist/runtime/built-in-block-code-templates/interactivity.d.ts +1 -1
- package/dist/runtime/built-in-block-code-templates/interactivity.js +4 -2
- package/dist/runtime/cli-add-block-json.d.ts +31 -0
- package/dist/runtime/cli-add-block-json.js +65 -0
- package/dist/runtime/cli-add-collision.d.ts +129 -0
- package/dist/runtime/cli-add-collision.js +293 -0
- package/dist/runtime/cli-add-filesystem.d.ts +29 -0
- package/dist/runtime/cli-add-filesystem.js +77 -0
- package/dist/runtime/cli-add-help.d.ts +4 -0
- package/dist/runtime/cli-add-help.js +41 -0
- package/dist/runtime/cli-add-shared.d.ts +6 -255
- package/dist/runtime/cli-add-shared.js +6 -391
- package/dist/runtime/cli-add-types.d.ts +247 -0
- package/dist/runtime/cli-add-types.js +64 -0
- package/dist/runtime/cli-add-validation.d.ts +87 -0
- package/dist/runtime/cli-add-validation.js +147 -0
- package/dist/runtime/cli-add-workspace-ability-scaffold.d.ts +5 -0
- package/dist/runtime/cli-add-workspace-ability-scaffold.js +366 -0
- package/dist/runtime/cli-add-workspace-ability-templates.d.ts +34 -0
- package/dist/runtime/cli-add-workspace-ability-templates.js +500 -0
- package/dist/runtime/cli-add-workspace-ability-types.d.ts +27 -0
- package/dist/runtime/cli-add-workspace-ability-types.js +14 -0
- package/dist/runtime/cli-add-workspace-ability.js +12 -852
- package/dist/runtime/cli-add-workspace-admin-view-scaffold.js +35 -61
- package/dist/runtime/cli-add-workspace-ai-scaffold.d.ts +21 -0
- package/dist/runtime/cli-add-workspace-ai-scaffold.js +87 -0
- package/dist/runtime/cli-add-workspace-ai-templates.d.ts +4 -0
- package/dist/runtime/cli-add-workspace-ai-templates.js +607 -0
- package/dist/runtime/cli-add-workspace-ai.js +15 -688
- package/dist/runtime/cli-add-workspace-assets.js +7 -4
- package/dist/runtime/cli-add-workspace-mutation.d.ts +30 -0
- package/dist/runtime/cli-add-workspace-mutation.js +60 -0
- package/dist/runtime/cli-add-workspace.js +2 -98
- package/dist/runtime/cli-add.d.ts +2 -2
- package/dist/runtime/cli-add.js +2 -2
- package/dist/runtime/cli-doctor-workspace-bindings.d.ts +11 -0
- package/dist/runtime/cli-doctor-workspace-bindings.js +134 -0
- package/dist/runtime/cli-doctor-workspace-blocks.d.ts +11 -0
- package/dist/runtime/cli-doctor-workspace-blocks.js +439 -0
- package/dist/runtime/cli-doctor-workspace-features.d.ts +11 -0
- package/dist/runtime/cli-doctor-workspace-features.js +383 -0
- package/dist/runtime/cli-doctor-workspace-package.d.ts +18 -0
- package/dist/runtime/cli-doctor-workspace-package.js +59 -0
- package/dist/runtime/cli-doctor-workspace-shared.d.ts +69 -0
- package/dist/runtime/cli-doctor-workspace-shared.js +87 -0
- package/dist/runtime/cli-doctor-workspace.js +25 -1062
- package/dist/runtime/index.d.ts +2 -0
- package/dist/runtime/index.js +1 -0
- package/dist/runtime/migration-utils.d.ts +2 -1
- package/dist/runtime/migration-utils.js +3 -11
- package/dist/runtime/package-managers.d.ts +19 -0
- package/dist/runtime/package-managers.js +62 -0
- package/dist/runtime/template-source-cache.d.ts +59 -0
- package/dist/runtime/template-source-cache.js +160 -0
- package/dist/runtime/ts-source-masking.d.ts +28 -0
- package/dist/runtime/ts-source-masking.js +104 -0
- package/dist/runtime/typia-llm.d.ts +9 -1
- package/dist/runtime/typia-llm.js +20 -5
- package/dist/runtime/workspace-inventory.js +116 -59
- package/dist/runtime/workspace-project.d.ts +1 -1
- package/dist/runtime/workspace-project.js +2 -10
- package/package.json +4 -4
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Supported top-level `wp-typia add` kinds exposed by the canonical CLI.
|
|
3
|
+
*/
|
|
4
|
+
export const ADD_KIND_IDS = [
|
|
5
|
+
"admin-view",
|
|
6
|
+
"block",
|
|
7
|
+
"variation",
|
|
8
|
+
"style",
|
|
9
|
+
"transform",
|
|
10
|
+
"pattern",
|
|
11
|
+
"binding-source",
|
|
12
|
+
"rest-resource",
|
|
13
|
+
"ability",
|
|
14
|
+
"ai-feature",
|
|
15
|
+
"hooked-block",
|
|
16
|
+
"editor-plugin",
|
|
17
|
+
];
|
|
18
|
+
/**
|
|
19
|
+
* Supported plugin-level REST resource methods accepted by
|
|
20
|
+
* `wp-typia add rest-resource --methods`.
|
|
21
|
+
*/
|
|
22
|
+
export const REST_RESOURCE_METHOD_IDS = [
|
|
23
|
+
"list",
|
|
24
|
+
"read",
|
|
25
|
+
"create",
|
|
26
|
+
"update",
|
|
27
|
+
"delete",
|
|
28
|
+
];
|
|
29
|
+
/**
|
|
30
|
+
* Canonical editor-plugin shell surface ids accepted by
|
|
31
|
+
* `wp-typia add editor-plugin --slot`.
|
|
32
|
+
*/
|
|
33
|
+
export const EDITOR_PLUGIN_SLOT_IDS = ["sidebar", "document-setting-panel"];
|
|
34
|
+
/**
|
|
35
|
+
* Legacy and canonical editor-plugin slot aliases keyed by user-facing input.
|
|
36
|
+
*/
|
|
37
|
+
export const EDITOR_PLUGIN_SLOT_ALIASES = {
|
|
38
|
+
PluginDocumentSettingPanel: "document-setting-panel",
|
|
39
|
+
PluginSidebar: "sidebar",
|
|
40
|
+
"document-setting-panel": "document-setting-panel",
|
|
41
|
+
sidebar: "sidebar",
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Resolve a user-provided editor-plugin slot alias to its canonical id.
|
|
45
|
+
*
|
|
46
|
+
* @param slot Raw slot value from CLI input.
|
|
47
|
+
* @returns The canonical slot id, or `undefined` when unsupported.
|
|
48
|
+
*/
|
|
49
|
+
export function resolveEditorPluginSlotAlias(slot) {
|
|
50
|
+
const trimmed = slot.trim();
|
|
51
|
+
if (!Object.prototype.hasOwnProperty.call(EDITOR_PLUGIN_SLOT_ALIASES, trimmed)) {
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
54
|
+
return EDITOR_PLUGIN_SLOT_ALIASES[trimmed];
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Supported built-in block families accepted by `wp-typia add block --template`.
|
|
58
|
+
*/
|
|
59
|
+
export const ADD_BLOCK_TEMPLATE_IDS = [
|
|
60
|
+
"basic",
|
|
61
|
+
"interactivity",
|
|
62
|
+
"persistence",
|
|
63
|
+
"compound",
|
|
64
|
+
];
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { type HookedBlockPositionId } from "./hooked-blocks.js";
|
|
2
|
+
import { type AddBlockTemplateId, type EditorPluginSlotId, type RestResourceMethodId } from "./cli-add-types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Namespace format accepted by plugin-level REST resources.
|
|
5
|
+
*/
|
|
6
|
+
export declare const REST_RESOURCE_NAMESPACE_PATTERN: RegExp;
|
|
7
|
+
/**
|
|
8
|
+
* Validate a normalized workspace-generated slug.
|
|
9
|
+
*
|
|
10
|
+
* @param label Human-readable field label used in error messages.
|
|
11
|
+
* @param slug Normalized slug value to validate.
|
|
12
|
+
* @param usage CLI usage hint shown when the slug is empty.
|
|
13
|
+
* @returns The validated slug.
|
|
14
|
+
* @throws {Error} When the slug is empty or contains unsupported characters.
|
|
15
|
+
*/
|
|
16
|
+
export declare function assertValidGeneratedSlug(label: string, slug: string, usage: string): string;
|
|
17
|
+
/**
|
|
18
|
+
* Validate a REST resource namespace.
|
|
19
|
+
*
|
|
20
|
+
* @param namespace Namespace candidate such as `vendor/v1`.
|
|
21
|
+
* @returns The trimmed namespace.
|
|
22
|
+
* @throws {Error} When the namespace is empty or not lowercase slash-separated.
|
|
23
|
+
*/
|
|
24
|
+
export declare function assertValidRestResourceNamespace(namespace: string): string;
|
|
25
|
+
/**
|
|
26
|
+
* Resolve the effective REST resource namespace for a workspace.
|
|
27
|
+
*
|
|
28
|
+
* @param workspaceNamespace Default workspace namespace prefix.
|
|
29
|
+
* @param namespace Optional explicit namespace from CLI input.
|
|
30
|
+
* @returns A validated namespace, defaulting to `<workspaceNamespace>/v1`.
|
|
31
|
+
* @throws {Error} When the resolved namespace is invalid.
|
|
32
|
+
*/
|
|
33
|
+
export declare function resolveRestResourceNamespace(workspaceNamespace: string, namespace?: string): string;
|
|
34
|
+
/**
|
|
35
|
+
* Parse and validate REST resource method ids from a comma-separated list.
|
|
36
|
+
*
|
|
37
|
+
* @param methods Optional comma-separated method list. Defaults to list, read, and create.
|
|
38
|
+
* @returns Deduplicated canonical REST resource method ids.
|
|
39
|
+
* @throws {Error} When any method is unsupported or the list is empty.
|
|
40
|
+
*/
|
|
41
|
+
export declare function assertValidRestResourceMethods(methods?: string): RestResourceMethodId[];
|
|
42
|
+
/**
|
|
43
|
+
* Validate a hooked block insertion position.
|
|
44
|
+
*
|
|
45
|
+
* @param position Position candidate from CLI input.
|
|
46
|
+
* @returns The canonical hooked block position id.
|
|
47
|
+
* @throws {Error} When the position is not supported.
|
|
48
|
+
*/
|
|
49
|
+
export declare function assertValidHookedBlockPosition(position: string): HookedBlockPositionId;
|
|
50
|
+
/**
|
|
51
|
+
* Build a PHP-safe workspace identifier prefix for a generated artifact.
|
|
52
|
+
*
|
|
53
|
+
* @param workspacePhpPrefix Workspace PHP prefix from project metadata.
|
|
54
|
+
* @param slug Generated artifact slug to append.
|
|
55
|
+
* @returns Snake-case PHP prefix for generated identifiers.
|
|
56
|
+
*/
|
|
57
|
+
export declare function buildWorkspacePhpPrefix(workspacePhpPrefix: string, slug: string): string;
|
|
58
|
+
/**
|
|
59
|
+
* Check whether a value is a supported built-in add block template id.
|
|
60
|
+
*
|
|
61
|
+
* @param value Candidate template id from CLI input.
|
|
62
|
+
* @returns True when the value is an `AddBlockTemplateId`.
|
|
63
|
+
*/
|
|
64
|
+
export declare function isAddBlockTemplateId(value: string): value is AddBlockTemplateId;
|
|
65
|
+
/**
|
|
66
|
+
* Quote a value for safe insertion into generated TypeScript source.
|
|
67
|
+
*
|
|
68
|
+
* @param value Raw string value.
|
|
69
|
+
* @returns JSON-escaped TypeScript string literal.
|
|
70
|
+
*/
|
|
71
|
+
export declare function quoteTsString(value: string): string;
|
|
72
|
+
/**
|
|
73
|
+
* Validate a full block name used as a hooked block anchor.
|
|
74
|
+
*
|
|
75
|
+
* @param anchorBlockName Anchor block name from CLI input.
|
|
76
|
+
* @returns The trimmed full block name.
|
|
77
|
+
* @throws {Error} When the anchor is empty or not `namespace/slug`.
|
|
78
|
+
*/
|
|
79
|
+
export declare function assertValidHookAnchor(anchorBlockName: string): string;
|
|
80
|
+
/**
|
|
81
|
+
* Validate and normalize the editor plugin shell slot.
|
|
82
|
+
*
|
|
83
|
+
* @param slot Optional shell slot. Defaults to `sidebar`.
|
|
84
|
+
* @returns The canonical editor plugin slot id.
|
|
85
|
+
* @throws {Error} When the slot is not supported by the workspace scaffold.
|
|
86
|
+
*/
|
|
87
|
+
export declare function assertValidEditorPluginSlot(slot?: string): EditorPluginSlotId;
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { HOOKED_BLOCK_ANCHOR_PATTERN, HOOKED_BLOCK_POSITION_IDS, } from "./hooked-blocks.js";
|
|
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";
|
|
4
|
+
const WORKSPACE_GENERATED_SLUG_PATTERN = /^[a-z][a-z0-9-]*$/;
|
|
5
|
+
/**
|
|
6
|
+
* Namespace format accepted by plugin-level REST resources.
|
|
7
|
+
*/
|
|
8
|
+
export const REST_RESOURCE_NAMESPACE_PATTERN = /^[a-z][a-z0-9-]*(?:\/[a-z0-9-]+)+$/u;
|
|
9
|
+
/**
|
|
10
|
+
* Validate a normalized workspace-generated slug.
|
|
11
|
+
*
|
|
12
|
+
* @param label Human-readable field label used in error messages.
|
|
13
|
+
* @param slug Normalized slug value to validate.
|
|
14
|
+
* @param usage CLI usage hint shown when the slug is empty.
|
|
15
|
+
* @returns The validated slug.
|
|
16
|
+
* @throws {Error} When the slug is empty or contains unsupported characters.
|
|
17
|
+
*/
|
|
18
|
+
export function assertValidGeneratedSlug(label, slug, usage) {
|
|
19
|
+
if (!slug) {
|
|
20
|
+
throw new Error(`${label} is required. Use \`${usage}\`.`);
|
|
21
|
+
}
|
|
22
|
+
if (!WORKSPACE_GENERATED_SLUG_PATTERN.test(slug)) {
|
|
23
|
+
throw new Error(`${label} must start with a letter and contain only lowercase letters, numbers, and hyphens.`);
|
|
24
|
+
}
|
|
25
|
+
return slug;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Validate a REST resource namespace.
|
|
29
|
+
*
|
|
30
|
+
* @param namespace Namespace candidate such as `vendor/v1`.
|
|
31
|
+
* @returns The trimmed namespace.
|
|
32
|
+
* @throws {Error} When the namespace is empty or not lowercase slash-separated.
|
|
33
|
+
*/
|
|
34
|
+
export function assertValidRestResourceNamespace(namespace) {
|
|
35
|
+
const trimmed = namespace.trim();
|
|
36
|
+
if (!trimmed) {
|
|
37
|
+
throw new Error("REST resource namespace is required. Use `--namespace <vendor/v1>` or let the workspace default apply.");
|
|
38
|
+
}
|
|
39
|
+
if (!REST_RESOURCE_NAMESPACE_PATTERN.test(trimmed)) {
|
|
40
|
+
throw new Error("REST resource namespace must use lowercase slash-separated segments like `demo-space/v1`.");
|
|
41
|
+
}
|
|
42
|
+
return trimmed;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Resolve the effective REST resource namespace for a workspace.
|
|
46
|
+
*
|
|
47
|
+
* @param workspaceNamespace Default workspace namespace prefix.
|
|
48
|
+
* @param namespace Optional explicit namespace from CLI input.
|
|
49
|
+
* @returns A validated namespace, defaulting to `<workspaceNamespace>/v1`.
|
|
50
|
+
* @throws {Error} When the resolved namespace is invalid.
|
|
51
|
+
*/
|
|
52
|
+
export function resolveRestResourceNamespace(workspaceNamespace, namespace) {
|
|
53
|
+
return assertValidRestResourceNamespace(namespace ?? `${workspaceNamespace}/v1`);
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Parse and validate REST resource method ids from a comma-separated list.
|
|
57
|
+
*
|
|
58
|
+
* @param methods Optional comma-separated method list. Defaults to list, read, and create.
|
|
59
|
+
* @returns Deduplicated canonical REST resource method ids.
|
|
60
|
+
* @throws {Error} When any method is unsupported or the list is empty.
|
|
61
|
+
*/
|
|
62
|
+
export function assertValidRestResourceMethods(methods) {
|
|
63
|
+
const rawMethods = typeof methods === "string" && methods.trim().length > 0
|
|
64
|
+
? methods.split(",").map((value) => value.trim()).filter(Boolean)
|
|
65
|
+
: ["list", "read", "create"];
|
|
66
|
+
const normalizedMethods = Array.from(new Set(rawMethods));
|
|
67
|
+
const invalidMethods = normalizedMethods.filter((method) => !REST_RESOURCE_METHOD_IDS.includes(method));
|
|
68
|
+
if (invalidMethods.length > 0) {
|
|
69
|
+
throw new Error(`REST resource methods must be a comma-separated list of: ${REST_RESOURCE_METHOD_IDS.join(", ")}.`);
|
|
70
|
+
}
|
|
71
|
+
if (normalizedMethods.length === 0) {
|
|
72
|
+
throw new Error(`REST resource methods must include at least one of: ${REST_RESOURCE_METHOD_IDS.join(", ")}.`);
|
|
73
|
+
}
|
|
74
|
+
return normalizedMethods;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Validate a hooked block insertion position.
|
|
78
|
+
*
|
|
79
|
+
* @param position Position candidate from CLI input.
|
|
80
|
+
* @returns The canonical hooked block position id.
|
|
81
|
+
* @throws {Error} When the position is not supported.
|
|
82
|
+
*/
|
|
83
|
+
export function assertValidHookedBlockPosition(position) {
|
|
84
|
+
if (HOOKED_BLOCK_POSITION_IDS.includes(position)) {
|
|
85
|
+
return position;
|
|
86
|
+
}
|
|
87
|
+
throw new Error(`Hook position must be one of: ${HOOKED_BLOCK_POSITION_IDS.join(", ")}.`);
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Build a PHP-safe workspace identifier prefix for a generated artifact.
|
|
91
|
+
*
|
|
92
|
+
* @param workspacePhpPrefix Workspace PHP prefix from project metadata.
|
|
93
|
+
* @param slug Generated artifact slug to append.
|
|
94
|
+
* @returns Snake-case PHP prefix for generated identifiers.
|
|
95
|
+
*/
|
|
96
|
+
export function buildWorkspacePhpPrefix(workspacePhpPrefix, slug) {
|
|
97
|
+
return toSnakeCase(`${workspacePhpPrefix}_${slug}`);
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Check whether a value is a supported built-in add block template id.
|
|
101
|
+
*
|
|
102
|
+
* @param value Candidate template id from CLI input.
|
|
103
|
+
* @returns True when the value is an `AddBlockTemplateId`.
|
|
104
|
+
*/
|
|
105
|
+
export function isAddBlockTemplateId(value) {
|
|
106
|
+
return ADD_BLOCK_TEMPLATE_IDS.includes(value);
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Quote a value for safe insertion into generated TypeScript source.
|
|
110
|
+
*
|
|
111
|
+
* @param value Raw string value.
|
|
112
|
+
* @returns JSON-escaped TypeScript string literal.
|
|
113
|
+
*/
|
|
114
|
+
export function quoteTsString(value) {
|
|
115
|
+
return JSON.stringify(value);
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Validate a full block name used as a hooked block anchor.
|
|
119
|
+
*
|
|
120
|
+
* @param anchorBlockName Anchor block name from CLI input.
|
|
121
|
+
* @returns The trimmed full block name.
|
|
122
|
+
* @throws {Error} When the anchor is empty or not `namespace/slug`.
|
|
123
|
+
*/
|
|
124
|
+
export function assertValidHookAnchor(anchorBlockName) {
|
|
125
|
+
const trimmed = anchorBlockName.trim();
|
|
126
|
+
if (!trimmed) {
|
|
127
|
+
throw new Error("`wp-typia add hooked-block` requires --anchor <anchor-block-name>.");
|
|
128
|
+
}
|
|
129
|
+
if (!HOOKED_BLOCK_ANCHOR_PATTERN.test(trimmed)) {
|
|
130
|
+
throw new Error("`wp-typia add hooked-block` requires --anchor <anchor-block-name> to use the full `namespace/slug` block name format.");
|
|
131
|
+
}
|
|
132
|
+
return trimmed;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Validate and normalize the editor plugin shell slot.
|
|
136
|
+
*
|
|
137
|
+
* @param slot Optional shell slot. Defaults to `sidebar`.
|
|
138
|
+
* @returns The canonical editor plugin slot id.
|
|
139
|
+
* @throws {Error} When the slot is not supported by the workspace scaffold.
|
|
140
|
+
*/
|
|
141
|
+
export function assertValidEditorPluginSlot(slot = "sidebar") {
|
|
142
|
+
const alias = resolveEditorPluginSlotAlias(slot);
|
|
143
|
+
if (alias) {
|
|
144
|
+
return alias;
|
|
145
|
+
}
|
|
146
|
+
throw new Error(`Editor plugin slot must be one of: ${EDITOR_PLUGIN_SLOT_IDS.join(", ")}. Legacy aliases: PluginSidebar, PluginDocumentSettingPanel.`);
|
|
147
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type ScaffoldAbilityWorkspaceOptions } from "./cli-add-workspace-ability-types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Write generated workflow ability sources and patch shared workspace anchors.
|
|
4
|
+
*/
|
|
5
|
+
export declare function scaffoldAbilityWorkspace({ abilitySlug, compatibilityPolicy, workspace, }: ScaffoldAbilityWorkspaceOptions): Promise<void>;
|
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import { promises as fsp } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { syncTypeSchemas } from "@wp-typia/block-runtime/metadata-core";
|
|
5
|
+
import semver from "semver";
|
|
6
|
+
import { appendWorkspaceInventoryEntries, readWorkspaceInventory, } from "./workspace-inventory.js";
|
|
7
|
+
import { buildAbilityClientSource, buildAbilityConfigEntry, buildAbilityConfigSource, buildAbilityDataSource, buildAbilityPhpSource, buildAbilityRegistrySource, buildAbilitySyncScriptSource, buildAbilityTypesSource, } from "./cli-add-workspace-ability-templates.js";
|
|
8
|
+
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
|
+
import { getWorkspaceBootstrapPath, patchFile, } from "./cli-add-shared.js";
|
|
10
|
+
import { appendPhpSnippetBeforeClosingTag, executeWorkspaceMutationPlan, insertPhpSnippetBeforeWorkspaceAnchors, } from "./cli-add-workspace-mutation.js";
|
|
11
|
+
import { updatePluginHeaderCompatibility, } from "./scaffold-compatibility.js";
|
|
12
|
+
import { DEFAULT_WORDPRESS_ABILITIES_VERSION, DEFAULT_WORDPRESS_CORE_ABILITIES_VERSION, } from "./package-versions.js";
|
|
13
|
+
import { escapeRegex, findPhpFunctionRange, hasPhpFunctionDefinition, replacePhpFunctionDefinition, } from "./php-utils.js";
|
|
14
|
+
import { toPascalCase } from "./string-case.js";
|
|
15
|
+
function resolveManagedDependencyVersion(existingVersion, requiredVersion) {
|
|
16
|
+
if (!existingVersion) {
|
|
17
|
+
return requiredVersion;
|
|
18
|
+
}
|
|
19
|
+
const existingMinimum = semver.minVersion(existingVersion);
|
|
20
|
+
const requiredMinimum = semver.minVersion(requiredVersion);
|
|
21
|
+
if (!existingMinimum || !requiredMinimum) {
|
|
22
|
+
return requiredVersion;
|
|
23
|
+
}
|
|
24
|
+
return semver.gte(existingMinimum, requiredMinimum)
|
|
25
|
+
? existingVersion
|
|
26
|
+
: requiredVersion;
|
|
27
|
+
}
|
|
28
|
+
function resolveAbilityRegistryPath(projectDir) {
|
|
29
|
+
const abilitiesDir = path.join(projectDir, "src", "abilities");
|
|
30
|
+
return [path.join(abilitiesDir, "index.ts"), path.join(abilitiesDir, "index.js")].find((candidatePath) => fs.existsSync(candidatePath)) ?? path.join(abilitiesDir, "index.ts");
|
|
31
|
+
}
|
|
32
|
+
function readAbilityRegistrySlugs(registryPath) {
|
|
33
|
+
if (!fs.existsSync(registryPath)) {
|
|
34
|
+
return [];
|
|
35
|
+
}
|
|
36
|
+
const source = fs.readFileSync(registryPath, "utf8");
|
|
37
|
+
return Array.from(source.matchAll(/^\s*export\s+\*\s+from\s+['"]\.\/([^/'"]+)\/client['"];?\s*$/gmu)).map((match) => match[1]);
|
|
38
|
+
}
|
|
39
|
+
async function writeAbilityRegistry(projectDir, abilitySlug) {
|
|
40
|
+
const abilitiesDir = path.join(projectDir, "src", "abilities");
|
|
41
|
+
const registryPath = resolveAbilityRegistryPath(projectDir);
|
|
42
|
+
await fsp.mkdir(abilitiesDir, { recursive: true });
|
|
43
|
+
const existingAbilitySlugs = readWorkspaceInventory(projectDir).abilities.map((entry) => entry.slug);
|
|
44
|
+
const existingRegistrySlugs = readAbilityRegistrySlugs(registryPath);
|
|
45
|
+
const nextAbilitySlugs = Array.from(new Set([...existingAbilitySlugs, ...existingRegistrySlugs, abilitySlug])).sort();
|
|
46
|
+
const generatedSection = buildAbilityRegistrySource(nextAbilitySlugs);
|
|
47
|
+
const existingSource = fs.existsSync(registryPath)
|
|
48
|
+
? fs.readFileSync(registryPath, "utf8")
|
|
49
|
+
: "";
|
|
50
|
+
const generatedSectionPattern = new RegExp(`${escapeRegex(ABILITY_REGISTRY_START_MARKER)}[\\s\\S]*?${escapeRegex(ABILITY_REGISTRY_END_MARKER)}\\n?`, "u");
|
|
51
|
+
const nextSource = existingSource
|
|
52
|
+
? generatedSectionPattern.test(existingSource)
|
|
53
|
+
? existingSource.replace(generatedSectionPattern, generatedSection)
|
|
54
|
+
: `${existingSource.trimEnd()}\n\n${generatedSection}`
|
|
55
|
+
: generatedSection;
|
|
56
|
+
await fsp.writeFile(registryPath, nextSource, "utf8");
|
|
57
|
+
}
|
|
58
|
+
async function ensureAbilityBootstrapAnchors(workspace) {
|
|
59
|
+
const bootstrapPath = getWorkspaceBootstrapPath(workspace);
|
|
60
|
+
await patchFile(bootstrapPath, (source) => {
|
|
61
|
+
let nextSource = source;
|
|
62
|
+
const workspaceBaseName = workspace.packageName.split("/").pop() ?? workspace.packageName;
|
|
63
|
+
const loadFunctionName = `${workspace.workspace.phpPrefix}_load_workflow_abilities`;
|
|
64
|
+
const enqueueFunctionName = `${workspace.workspace.phpPrefix}_enqueue_workflow_abilities`;
|
|
65
|
+
const loadHook = `add_action( 'plugins_loaded', '${loadFunctionName}' );`;
|
|
66
|
+
const adminEnqueueHook = `add_action( 'admin_enqueue_scripts', '${enqueueFunctionName}' );`;
|
|
67
|
+
const editorEnqueueHook = `add_action( 'enqueue_block_editor_assets', '${enqueueFunctionName}' );`;
|
|
68
|
+
const loadFunction = `
|
|
69
|
+
|
|
70
|
+
function ${loadFunctionName}() {
|
|
71
|
+
\tforeach ( glob( __DIR__ . '${ABILITY_SERVER_GLOB}' ) ?: array() as $ability_module ) {
|
|
72
|
+
\t\trequire_once $ability_module;
|
|
73
|
+
\t}
|
|
74
|
+
}
|
|
75
|
+
`;
|
|
76
|
+
const enqueueFunction = `
|
|
77
|
+
|
|
78
|
+
function ${enqueueFunctionName}() {
|
|
79
|
+
\tif ( ! class_exists( 'WP_Ability' ) ) {
|
|
80
|
+
\t\treturn;
|
|
81
|
+
\t}
|
|
82
|
+
|
|
83
|
+
\t$script_path = __DIR__ . '/${ABILITY_EDITOR_SCRIPT}';
|
|
84
|
+
\t$asset_path = __DIR__ . '/${ABILITY_EDITOR_ASSET}';
|
|
85
|
+
|
|
86
|
+
\tif ( ! file_exists( $script_path ) || ! file_exists( $asset_path ) ) {
|
|
87
|
+
\t\treturn;
|
|
88
|
+
\t}
|
|
89
|
+
|
|
90
|
+
\t$asset = require $asset_path;
|
|
91
|
+
\tif ( ! is_array( $asset ) ) {
|
|
92
|
+
\t\t$asset = array();
|
|
93
|
+
\t}
|
|
94
|
+
|
|
95
|
+
\t$dependencies = isset( $asset['dependencies'] ) && is_array( $asset['dependencies'] )
|
|
96
|
+
\t\t? $asset['dependencies']
|
|
97
|
+
\t\t: array();
|
|
98
|
+
|
|
99
|
+
\tforeach ( array( '${WP_CORE_ABILITIES_SCRIPT_MODULE_ID}', '${WP_ABILITIES_SCRIPT_MODULE_ID}' ) as $ability_dependency ) {
|
|
100
|
+
\t\t$has_dependency = false;
|
|
101
|
+
\t\tforeach ( $dependencies as $dependency ) {
|
|
102
|
+
\t\t\t$dependency_id = is_array( $dependency ) && isset( $dependency['id'] )
|
|
103
|
+
\t\t\t\t? $dependency['id']
|
|
104
|
+
\t\t\t\t: $dependency;
|
|
105
|
+
\t\t\tif ( $dependency_id === $ability_dependency ) {
|
|
106
|
+
\t\t\t\t$has_dependency = true;
|
|
107
|
+
\t\t\t\tbreak;
|
|
108
|
+
\t\t\t}
|
|
109
|
+
\t\t}
|
|
110
|
+
\t\tif ( ! $has_dependency ) {
|
|
111
|
+
\t\t\t$dependencies[] = $ability_dependency;
|
|
112
|
+
\t\t}
|
|
113
|
+
\t}
|
|
114
|
+
|
|
115
|
+
\tif ( ! function_exists( 'wp_enqueue_script_module' ) ) {
|
|
116
|
+
\t\treturn;
|
|
117
|
+
\t}
|
|
118
|
+
|
|
119
|
+
\twp_enqueue_script_module(
|
|
120
|
+
\t\t'${workspaceBaseName}-abilities',
|
|
121
|
+
\t\tplugins_url( '${ABILITY_EDITOR_SCRIPT}', __FILE__ ),
|
|
122
|
+
\t\t$dependencies,
|
|
123
|
+
\t\tisset( $asset['version'] ) ? $asset['version'] : filemtime( $script_path )
|
|
124
|
+
\t);
|
|
125
|
+
}
|
|
126
|
+
`;
|
|
127
|
+
if (!hasPhpFunctionDefinition(nextSource, loadFunctionName)) {
|
|
128
|
+
nextSource = insertPhpSnippetBeforeWorkspaceAnchors(nextSource, loadFunction);
|
|
129
|
+
}
|
|
130
|
+
if (!hasPhpFunctionDefinition(nextSource, enqueueFunctionName)) {
|
|
131
|
+
nextSource = insertPhpSnippetBeforeWorkspaceAnchors(nextSource, enqueueFunction);
|
|
132
|
+
}
|
|
133
|
+
else if (!findPhpFunctionRange(nextSource, enqueueFunctionName)?.source.includes("wp_enqueue_script_module")) {
|
|
134
|
+
nextSource =
|
|
135
|
+
replacePhpFunctionDefinition(nextSource, enqueueFunctionName, enqueueFunction, { trimReplacementStart: true }) ?? nextSource;
|
|
136
|
+
}
|
|
137
|
+
if (!nextSource.includes(loadHook)) {
|
|
138
|
+
nextSource = appendPhpSnippetBeforeClosingTag(nextSource, loadHook);
|
|
139
|
+
}
|
|
140
|
+
if (!nextSource.includes(adminEnqueueHook)) {
|
|
141
|
+
nextSource = appendPhpSnippetBeforeClosingTag(nextSource, adminEnqueueHook);
|
|
142
|
+
}
|
|
143
|
+
if (!nextSource.includes(editorEnqueueHook)) {
|
|
144
|
+
nextSource = appendPhpSnippetBeforeClosingTag(nextSource, editorEnqueueHook);
|
|
145
|
+
}
|
|
146
|
+
return nextSource;
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
async function ensureAbilityPackageScripts(workspace) {
|
|
150
|
+
const packageJsonPath = path.join(workspace.projectDir, "package.json");
|
|
151
|
+
const packageJson = JSON.parse(await fsp.readFile(packageJsonPath, "utf8"));
|
|
152
|
+
const nextScripts = {
|
|
153
|
+
...(packageJson.scripts ?? {}),
|
|
154
|
+
"sync-abilities": packageJson.scripts?.["sync-abilities"] ?? "tsx scripts/sync-abilities.ts",
|
|
155
|
+
};
|
|
156
|
+
const nextDependencies = {
|
|
157
|
+
...(packageJson.dependencies ?? {}),
|
|
158
|
+
[WP_ABILITIES_SCRIPT_MODULE_ID]: resolveManagedDependencyVersion(packageJson.dependencies?.[WP_ABILITIES_SCRIPT_MODULE_ID], DEFAULT_WORDPRESS_ABILITIES_VERSION),
|
|
159
|
+
[WP_CORE_ABILITIES_SCRIPT_MODULE_ID]: resolveManagedDependencyVersion(packageJson.dependencies?.[WP_CORE_ABILITIES_SCRIPT_MODULE_ID], DEFAULT_WORDPRESS_CORE_ABILITIES_VERSION),
|
|
160
|
+
};
|
|
161
|
+
if (JSON.stringify(nextScripts) === JSON.stringify(packageJson.scripts ?? {}) &&
|
|
162
|
+
JSON.stringify(nextDependencies) === JSON.stringify(packageJson.dependencies ?? {})) {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
packageJson.scripts = nextScripts;
|
|
166
|
+
packageJson.dependencies = nextDependencies;
|
|
167
|
+
await fsp.writeFile(packageJsonPath, `${JSON.stringify(packageJson, null, "\t")}\n`, "utf8");
|
|
168
|
+
}
|
|
169
|
+
async function ensureAbilitySyncProjectAnchors(workspace) {
|
|
170
|
+
const syncProjectScriptPath = path.join(workspace.projectDir, "scripts", "sync-project.ts");
|
|
171
|
+
await patchFile(syncProjectScriptPath, (source) => {
|
|
172
|
+
let nextSource = source;
|
|
173
|
+
const syncRestConst = "const syncRestScriptPath = path.join( 'scripts', 'sync-rest-contracts.ts' );";
|
|
174
|
+
const syncAbilitiesConst = "const syncAbilitiesScriptPath = path.join( 'scripts', 'sync-abilities.ts' );";
|
|
175
|
+
const syncRestBlockPattern = /if \( fs\.existsSync\( path\.resolve\( process\.cwd\(\), syncRestScriptPath \) \) \) \{\n\s*runSyncScript\( syncRestScriptPath, options \);\n\s*\}/u;
|
|
176
|
+
const syncAbilitiesBlock = [
|
|
177
|
+
"if ( fs.existsSync( path.resolve( process.cwd(), syncAbilitiesScriptPath ) ) ) {",
|
|
178
|
+
"\trunSyncScript( syncAbilitiesScriptPath, options );",
|
|
179
|
+
"}",
|
|
180
|
+
].join("\n");
|
|
181
|
+
if (!nextSource.includes(syncAbilitiesConst)) {
|
|
182
|
+
if (!nextSource.includes(syncRestConst)) {
|
|
183
|
+
throw new Error([
|
|
184
|
+
`ensureAbilitySyncProjectAnchors could not patch ${path.basename(syncProjectScriptPath)}.`,
|
|
185
|
+
"Missing the expected sync-rest script constant in scripts/sync-project.ts.",
|
|
186
|
+
"Restore the generated template or wire sync-abilities manually before retrying.",
|
|
187
|
+
].join(" "));
|
|
188
|
+
}
|
|
189
|
+
nextSource = nextSource.replace(syncRestConst, `${syncRestConst}\n${syncAbilitiesConst}`);
|
|
190
|
+
}
|
|
191
|
+
if (!nextSource.includes("runSyncScript( syncAbilitiesScriptPath, options );")) {
|
|
192
|
+
if (!syncRestBlockPattern.test(nextSource)) {
|
|
193
|
+
throw new Error([
|
|
194
|
+
`ensureAbilitySyncProjectAnchors could not patch ${path.basename(syncProjectScriptPath)}.`,
|
|
195
|
+
"Missing the expected sync-rest invocation block in scripts/sync-project.ts.",
|
|
196
|
+
"Restore the generated template or wire sync-abilities manually before retrying.",
|
|
197
|
+
].join(" "));
|
|
198
|
+
}
|
|
199
|
+
nextSource = nextSource.replace(syncRestBlockPattern, (match) => `${match}\n\n${syncAbilitiesBlock}`);
|
|
200
|
+
}
|
|
201
|
+
return nextSource;
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
async function ensureAbilityBuildScriptAnchors(workspace) {
|
|
205
|
+
const buildScriptPath = path.join(workspace.projectDir, "scripts", "build-workspace.mjs");
|
|
206
|
+
await patchFile(buildScriptPath, (source) => {
|
|
207
|
+
let nextSource = source;
|
|
208
|
+
if (/['"]src\/abilities\/index\.(?:ts|js)['"]/u.test(nextSource)) {
|
|
209
|
+
return nextSource;
|
|
210
|
+
}
|
|
211
|
+
const sharedEntriesPattern = /(for\s*\(\s*const\s+relativePath\s+of\s+\[)([\s\S]*?)(\]\s*\)\s*\{)/u;
|
|
212
|
+
const match = nextSource.match(sharedEntriesPattern);
|
|
213
|
+
if (!match ||
|
|
214
|
+
!/['"]src\/bindings\/index\.(?:ts|js)['"]/u.test(match[2]) ||
|
|
215
|
+
!/['"]src\/editor-plugins\/index\.(?:tsx|ts|js)['"]/u.test(match[2])) {
|
|
216
|
+
throw new Error([
|
|
217
|
+
`ensureAbilityBuildScriptAnchors could not patch ${path.basename(buildScriptPath)}.`,
|
|
218
|
+
"Missing the expected shared editor entries array in scripts/build-workspace.mjs.",
|
|
219
|
+
"Restore the generated template or wire abilities/index manually before retrying.",
|
|
220
|
+
].join(" "));
|
|
221
|
+
}
|
|
222
|
+
nextSource = nextSource.replace(sharedEntriesPattern, (fullMatch, sharedEntries) => {
|
|
223
|
+
const missingAbilityEntries = [
|
|
224
|
+
"'src/abilities/index.ts'",
|
|
225
|
+
"'src/abilities/index.js'",
|
|
226
|
+
].filter((entry) => !sharedEntries.includes(entry));
|
|
227
|
+
if (missingAbilityEntries.length === 0) {
|
|
228
|
+
return fullMatch;
|
|
229
|
+
}
|
|
230
|
+
const itemIndent = sharedEntries.match(/\n([ \t]*)['"]/u)?.[1] ?? "\t\t";
|
|
231
|
+
const trimmedEntries = sharedEntries.replace(/\s*$/u, "");
|
|
232
|
+
const trailingWhitespace = sharedEntries.slice(trimmedEntries.length);
|
|
233
|
+
const separator = trimmedEntries.trimEnd().endsWith(",") ? "" : ",";
|
|
234
|
+
const nextEntries = `${trimmedEntries}${separator}` +
|
|
235
|
+
missingAbilityEntries.map((entry) => `\n${itemIndent}${entry},`).join("") +
|
|
236
|
+
trailingWhitespace;
|
|
237
|
+
return fullMatch.replace(sharedEntries, nextEntries);
|
|
238
|
+
});
|
|
239
|
+
return nextSource;
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
async function ensureAbilityWebpackAnchors(workspace) {
|
|
243
|
+
const webpackConfigPath = path.join(workspace.projectDir, "webpack.config.js");
|
|
244
|
+
await patchFile(webpackConfigPath, (source) => {
|
|
245
|
+
if (/['"]abilities\/index['"]/u.test(source)) {
|
|
246
|
+
return source;
|
|
247
|
+
}
|
|
248
|
+
const optionalModuleReturnPattern = /(function\s+getOptionalModuleEntries\s*\(\)\s*\{[\s\S]*?)(\n\treturn Object\.fromEntries\(\s*entries\s*\);\n\})/u;
|
|
249
|
+
if (optionalModuleReturnPattern.test(source)) {
|
|
250
|
+
return source.replace(optionalModuleReturnPattern, `$1
|
|
251
|
+
|
|
252
|
+
\tfor ( const [ entryName, candidates ] of [
|
|
253
|
+
\t\t[
|
|
254
|
+
\t\t\t'abilities/index',
|
|
255
|
+
\t\t\t[ 'src/abilities/index.ts', 'src/abilities/index.js' ],
|
|
256
|
+
\t\t],
|
|
257
|
+
\t] ) {
|
|
258
|
+
\t\tfor ( const relativePath of candidates ) {
|
|
259
|
+
\t\t\tconst entryPath = path.resolve( process.cwd(), relativePath );
|
|
260
|
+
\t\t\tif ( ! fs.existsSync( entryPath ) ) {
|
|
261
|
+
\t\t\t\tcontinue;
|
|
262
|
+
\t\t\t}
|
|
263
|
+
|
|
264
|
+
\t\t\tentries.push( [ entryName, entryPath ] );
|
|
265
|
+
\t\t\tbreak;
|
|
266
|
+
\t\t}
|
|
267
|
+
\t}$2`);
|
|
268
|
+
}
|
|
269
|
+
const sharedEntriesPattern = /for\s*\(\s*const\s+\[\s*entryName\s*,\s*candidates\s*\]\s+of\s+\[([\s\S]*?)\]\s*\)\s*\{/u;
|
|
270
|
+
const match = source.match(sharedEntriesPattern);
|
|
271
|
+
if (!match ||
|
|
272
|
+
!match[1].includes("bindings/index") ||
|
|
273
|
+
!match[1].includes("editor-plugins/index")) {
|
|
274
|
+
throw new Error([
|
|
275
|
+
`ensureAbilityWebpackAnchors could not patch ${path.basename(webpackConfigPath)}.`,
|
|
276
|
+
"Missing the expected shared editor entries block in webpack.config.js.",
|
|
277
|
+
"Restore the generated template or wire abilities/index manually before retrying.",
|
|
278
|
+
].join(" "));
|
|
279
|
+
}
|
|
280
|
+
return source.replace(sharedEntriesPattern, (fullMatch, sharedEntries) => {
|
|
281
|
+
if (/['"]abilities\/index['"]/u.test(sharedEntries)) {
|
|
282
|
+
return fullMatch;
|
|
283
|
+
}
|
|
284
|
+
const tupleIndent = sharedEntries.match(/\n([ \t]*)\[/u)?.[1] ?? "\t\t";
|
|
285
|
+
const nestedIndent = `${tupleIndent}\t`;
|
|
286
|
+
const trimmedEntries = sharedEntries.replace(/\s*$/u, "");
|
|
287
|
+
const trailingWhitespace = sharedEntries.slice(trimmedEntries.length);
|
|
288
|
+
const separator = trimmedEntries.trimEnd().endsWith(",") ? "" : ",";
|
|
289
|
+
const abilityTuple = [
|
|
290
|
+
`${tupleIndent}[`,
|
|
291
|
+
`${nestedIndent}'abilities/index',`,
|
|
292
|
+
`${nestedIndent}[ 'src/abilities/index.ts', 'src/abilities/index.js' ],`,
|
|
293
|
+
`${tupleIndent}],`,
|
|
294
|
+
].join("\n");
|
|
295
|
+
const nextEntries = `${trimmedEntries}${separator}\n${abilityTuple}` + trailingWhitespace;
|
|
296
|
+
return fullMatch.replace(sharedEntries, nextEntries);
|
|
297
|
+
});
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Write generated workflow ability sources and patch shared workspace anchors.
|
|
302
|
+
*/
|
|
303
|
+
export async function scaffoldAbilityWorkspace({ abilitySlug, compatibilityPolicy, workspace, }) {
|
|
304
|
+
const blockConfigPath = path.join(workspace.projectDir, "scripts", "block-config.ts");
|
|
305
|
+
const bootstrapPath = getWorkspaceBootstrapPath(workspace);
|
|
306
|
+
const buildScriptPath = path.join(workspace.projectDir, "scripts", "build-workspace.mjs");
|
|
307
|
+
const packageJsonPath = path.join(workspace.projectDir, "package.json");
|
|
308
|
+
const syncAbilitiesScriptPath = path.join(workspace.projectDir, "scripts", "sync-abilities.ts");
|
|
309
|
+
const syncProjectScriptPath = path.join(workspace.projectDir, "scripts", "sync-project.ts");
|
|
310
|
+
const webpackConfigPath = path.join(workspace.projectDir, "webpack.config.js");
|
|
311
|
+
const abilitiesIndexPath = resolveAbilityRegistryPath(workspace.projectDir);
|
|
312
|
+
const abilityDir = path.join(workspace.projectDir, "src", "abilities", abilitySlug);
|
|
313
|
+
const configFilePath = path.join(abilityDir, "ability.config.json");
|
|
314
|
+
const typesFilePath = path.join(abilityDir, "types.ts");
|
|
315
|
+
const dataFilePath = path.join(abilityDir, "data.ts");
|
|
316
|
+
const clientFilePath = path.join(abilityDir, "client.ts");
|
|
317
|
+
const phpFilePath = path.join(workspace.projectDir, "inc", "abilities", `${abilitySlug}.php`);
|
|
318
|
+
await executeWorkspaceMutationPlan({
|
|
319
|
+
filePaths: [
|
|
320
|
+
blockConfigPath,
|
|
321
|
+
bootstrapPath,
|
|
322
|
+
buildScriptPath,
|
|
323
|
+
packageJsonPath,
|
|
324
|
+
syncAbilitiesScriptPath,
|
|
325
|
+
syncProjectScriptPath,
|
|
326
|
+
webpackConfigPath,
|
|
327
|
+
abilitiesIndexPath,
|
|
328
|
+
],
|
|
329
|
+
targetPaths: [abilityDir, phpFilePath, syncAbilitiesScriptPath],
|
|
330
|
+
run: async () => {
|
|
331
|
+
await fsp.mkdir(abilityDir, { recursive: true });
|
|
332
|
+
await fsp.mkdir(path.dirname(phpFilePath), { recursive: true });
|
|
333
|
+
await ensureAbilityBootstrapAnchors(workspace);
|
|
334
|
+
await patchFile(bootstrapPath, (source) => updatePluginHeaderCompatibility(source, compatibilityPolicy));
|
|
335
|
+
await ensureAbilityPackageScripts(workspace);
|
|
336
|
+
await ensureAbilitySyncProjectAnchors(workspace);
|
|
337
|
+
await ensureAbilityBuildScriptAnchors(workspace);
|
|
338
|
+
await ensureAbilityWebpackAnchors(workspace);
|
|
339
|
+
await fsp.writeFile(syncAbilitiesScriptPath, buildAbilitySyncScriptSource(), "utf8");
|
|
340
|
+
await fsp.writeFile(configFilePath, buildAbilityConfigSource(abilitySlug, workspace.workspace.namespace), "utf8");
|
|
341
|
+
await fsp.writeFile(typesFilePath, buildAbilityTypesSource(abilitySlug), "utf8");
|
|
342
|
+
await fsp.writeFile(dataFilePath, buildAbilityDataSource(abilitySlug), "utf8");
|
|
343
|
+
await fsp.writeFile(clientFilePath, buildAbilityClientSource(abilitySlug), "utf8");
|
|
344
|
+
await fsp.writeFile(phpFilePath, buildAbilityPhpSource(abilitySlug, workspace), "utf8");
|
|
345
|
+
const pascalCase = toPascalCase(abilitySlug);
|
|
346
|
+
await syncTypeSchemas({
|
|
347
|
+
jsonSchemaFile: `src/abilities/${abilitySlug}/input.schema.json`,
|
|
348
|
+
projectRoot: workspace.projectDir,
|
|
349
|
+
sourceTypeName: `${pascalCase}AbilityInput`,
|
|
350
|
+
typesFile: `src/abilities/${abilitySlug}/types.ts`,
|
|
351
|
+
});
|
|
352
|
+
await syncTypeSchemas({
|
|
353
|
+
jsonSchemaFile: `src/abilities/${abilitySlug}/output.schema.json`,
|
|
354
|
+
projectRoot: workspace.projectDir,
|
|
355
|
+
sourceTypeName: `${pascalCase}AbilityOutput`,
|
|
356
|
+
typesFile: `src/abilities/${abilitySlug}/types.ts`,
|
|
357
|
+
});
|
|
358
|
+
await writeAbilityRegistry(workspace.projectDir, abilitySlug);
|
|
359
|
+
await appendWorkspaceInventoryEntries(workspace.projectDir, {
|
|
360
|
+
abilityEntries: [
|
|
361
|
+
buildAbilityConfigEntry(abilitySlug, compatibilityPolicy),
|
|
362
|
+
],
|
|
363
|
+
});
|
|
364
|
+
},
|
|
365
|
+
});
|
|
366
|
+
}
|