@wp-typia/project-tools 0.22.2 → 0.22.3
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-shared.d.ts +49 -0
- package/dist/runtime/cli-add-shared.js +204 -71
- package/dist/runtime/cli-add-workspace-ability-scaffold.d.ts +5 -0
- package/dist/runtime/cli-add-workspace-ability-scaffold.js +392 -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-ai-scaffold.d.ts +21 -0
- package/dist/runtime/cli-add-workspace-ai-scaffold.js +91 -0
- package/dist/runtime/cli-add-workspace-ai-templates.d.ts +4 -0
- package/dist/runtime/cli-add-workspace-ai-templates.js +605 -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.js +1 -19
- 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 +504 -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/package.json +3 -3
|
@@ -0,0 +1,392 @@
|
|
|
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, rollbackWorkspaceMutation, snapshotWorkspaceFiles, } from "./cli-add-shared.js";
|
|
10
|
+
import { updatePluginHeaderCompatibility, } from "./scaffold-compatibility.js";
|
|
11
|
+
import { DEFAULT_WORDPRESS_ABILITIES_VERSION, DEFAULT_WORDPRESS_CORE_ABILITIES_VERSION, } from "./package-versions.js";
|
|
12
|
+
import { escapeRegex, findPhpFunctionRange, hasPhpFunctionDefinition, replacePhpFunctionDefinition, } from "./php-utils.js";
|
|
13
|
+
import { toPascalCase } from "./string-case.js";
|
|
14
|
+
function resolveManagedDependencyVersion(existingVersion, requiredVersion) {
|
|
15
|
+
if (!existingVersion) {
|
|
16
|
+
return requiredVersion;
|
|
17
|
+
}
|
|
18
|
+
const existingMinimum = semver.minVersion(existingVersion);
|
|
19
|
+
const requiredMinimum = semver.minVersion(requiredVersion);
|
|
20
|
+
if (!existingMinimum || !requiredMinimum) {
|
|
21
|
+
return requiredVersion;
|
|
22
|
+
}
|
|
23
|
+
return semver.gte(existingMinimum, requiredMinimum)
|
|
24
|
+
? existingVersion
|
|
25
|
+
: requiredVersion;
|
|
26
|
+
}
|
|
27
|
+
function resolveAbilityRegistryPath(projectDir) {
|
|
28
|
+
const abilitiesDir = path.join(projectDir, "src", "abilities");
|
|
29
|
+
return [path.join(abilitiesDir, "index.ts"), path.join(abilitiesDir, "index.js")].find((candidatePath) => fs.existsSync(candidatePath)) ?? path.join(abilitiesDir, "index.ts");
|
|
30
|
+
}
|
|
31
|
+
function readAbilityRegistrySlugs(registryPath) {
|
|
32
|
+
if (!fs.existsSync(registryPath)) {
|
|
33
|
+
return [];
|
|
34
|
+
}
|
|
35
|
+
const source = fs.readFileSync(registryPath, "utf8");
|
|
36
|
+
return Array.from(source.matchAll(/^\s*export\s+\*\s+from\s+['"]\.\/([^/'"]+)\/client['"];?\s*$/gmu)).map((match) => match[1]);
|
|
37
|
+
}
|
|
38
|
+
async function writeAbilityRegistry(projectDir, abilitySlug) {
|
|
39
|
+
const abilitiesDir = path.join(projectDir, "src", "abilities");
|
|
40
|
+
const registryPath = resolveAbilityRegistryPath(projectDir);
|
|
41
|
+
await fsp.mkdir(abilitiesDir, { recursive: true });
|
|
42
|
+
const existingAbilitySlugs = readWorkspaceInventory(projectDir).abilities.map((entry) => entry.slug);
|
|
43
|
+
const existingRegistrySlugs = readAbilityRegistrySlugs(registryPath);
|
|
44
|
+
const nextAbilitySlugs = Array.from(new Set([...existingAbilitySlugs, ...existingRegistrySlugs, abilitySlug])).sort();
|
|
45
|
+
const generatedSection = buildAbilityRegistrySource(nextAbilitySlugs);
|
|
46
|
+
const existingSource = fs.existsSync(registryPath)
|
|
47
|
+
? fs.readFileSync(registryPath, "utf8")
|
|
48
|
+
: "";
|
|
49
|
+
const generatedSectionPattern = new RegExp(`${escapeRegex(ABILITY_REGISTRY_START_MARKER)}[\\s\\S]*?${escapeRegex(ABILITY_REGISTRY_END_MARKER)}\\n?`, "u");
|
|
50
|
+
const nextSource = existingSource
|
|
51
|
+
? generatedSectionPattern.test(existingSource)
|
|
52
|
+
? existingSource.replace(generatedSectionPattern, generatedSection)
|
|
53
|
+
: `${existingSource.trimEnd()}\n\n${generatedSection}`
|
|
54
|
+
: generatedSection;
|
|
55
|
+
await fsp.writeFile(registryPath, nextSource, "utf8");
|
|
56
|
+
}
|
|
57
|
+
async function ensureAbilityBootstrapAnchors(workspace) {
|
|
58
|
+
const bootstrapPath = getWorkspaceBootstrapPath(workspace);
|
|
59
|
+
await patchFile(bootstrapPath, (source) => {
|
|
60
|
+
let nextSource = source;
|
|
61
|
+
const workspaceBaseName = workspace.packageName.split("/").pop() ?? workspace.packageName;
|
|
62
|
+
const loadFunctionName = `${workspace.workspace.phpPrefix}_load_workflow_abilities`;
|
|
63
|
+
const enqueueFunctionName = `${workspace.workspace.phpPrefix}_enqueue_workflow_abilities`;
|
|
64
|
+
const loadHook = `add_action( 'plugins_loaded', '${loadFunctionName}' );`;
|
|
65
|
+
const adminEnqueueHook = `add_action( 'admin_enqueue_scripts', '${enqueueFunctionName}' );`;
|
|
66
|
+
const editorEnqueueHook = `add_action( 'enqueue_block_editor_assets', '${enqueueFunctionName}' );`;
|
|
67
|
+
const loadFunction = `
|
|
68
|
+
|
|
69
|
+
function ${loadFunctionName}() {
|
|
70
|
+
\tforeach ( glob( __DIR__ . '${ABILITY_SERVER_GLOB}' ) ?: array() as $ability_module ) {
|
|
71
|
+
\t\trequire_once $ability_module;
|
|
72
|
+
\t}
|
|
73
|
+
}
|
|
74
|
+
`;
|
|
75
|
+
const enqueueFunction = `
|
|
76
|
+
|
|
77
|
+
function ${enqueueFunctionName}() {
|
|
78
|
+
\tif ( ! class_exists( 'WP_Ability' ) ) {
|
|
79
|
+
\t\treturn;
|
|
80
|
+
\t}
|
|
81
|
+
|
|
82
|
+
\t$script_path = __DIR__ . '/${ABILITY_EDITOR_SCRIPT}';
|
|
83
|
+
\t$asset_path = __DIR__ . '/${ABILITY_EDITOR_ASSET}';
|
|
84
|
+
|
|
85
|
+
\tif ( ! file_exists( $script_path ) || ! file_exists( $asset_path ) ) {
|
|
86
|
+
\t\treturn;
|
|
87
|
+
\t}
|
|
88
|
+
|
|
89
|
+
\t$asset = require $asset_path;
|
|
90
|
+
\tif ( ! is_array( $asset ) ) {
|
|
91
|
+
\t\t$asset = array();
|
|
92
|
+
\t}
|
|
93
|
+
|
|
94
|
+
\t$dependencies = isset( $asset['dependencies'] ) && is_array( $asset['dependencies'] )
|
|
95
|
+
\t\t? $asset['dependencies']
|
|
96
|
+
\t\t: array();
|
|
97
|
+
|
|
98
|
+
\tforeach ( array( '${WP_CORE_ABILITIES_SCRIPT_MODULE_ID}', '${WP_ABILITIES_SCRIPT_MODULE_ID}' ) as $ability_dependency ) {
|
|
99
|
+
\t\t$has_dependency = false;
|
|
100
|
+
\t\tforeach ( $dependencies as $dependency ) {
|
|
101
|
+
\t\t\t$dependency_id = is_array( $dependency ) && isset( $dependency['id'] )
|
|
102
|
+
\t\t\t\t? $dependency['id']
|
|
103
|
+
\t\t\t\t: $dependency;
|
|
104
|
+
\t\t\tif ( $dependency_id === $ability_dependency ) {
|
|
105
|
+
\t\t\t\t$has_dependency = true;
|
|
106
|
+
\t\t\t\tbreak;
|
|
107
|
+
\t\t\t}
|
|
108
|
+
\t\t}
|
|
109
|
+
\t\tif ( ! $has_dependency ) {
|
|
110
|
+
\t\t\t$dependencies[] = $ability_dependency;
|
|
111
|
+
\t\t}
|
|
112
|
+
\t}
|
|
113
|
+
|
|
114
|
+
\tif ( ! function_exists( 'wp_enqueue_script_module' ) ) {
|
|
115
|
+
\t\treturn;
|
|
116
|
+
\t}
|
|
117
|
+
|
|
118
|
+
\twp_enqueue_script_module(
|
|
119
|
+
\t\t'${workspaceBaseName}-abilities',
|
|
120
|
+
\t\tplugins_url( '${ABILITY_EDITOR_SCRIPT}', __FILE__ ),
|
|
121
|
+
\t\t$dependencies,
|
|
122
|
+
\t\tisset( $asset['version'] ) ? $asset['version'] : filemtime( $script_path )
|
|
123
|
+
\t);
|
|
124
|
+
}
|
|
125
|
+
`;
|
|
126
|
+
const insertionAnchors = [
|
|
127
|
+
/add_action\(\s*["']init["']\s*,\s*["'][^"']+_load_textdomain["']\s*\);\s*\n/u,
|
|
128
|
+
/\?>\s*$/u,
|
|
129
|
+
];
|
|
130
|
+
const insertPhpSnippet = (snippet) => {
|
|
131
|
+
for (const anchor of insertionAnchors) {
|
|
132
|
+
const candidate = nextSource.replace(anchor, (match) => `${snippet}\n${match}`);
|
|
133
|
+
if (candidate !== nextSource) {
|
|
134
|
+
nextSource = candidate;
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
nextSource = `${nextSource.trimEnd()}\n${snippet}\n`;
|
|
139
|
+
};
|
|
140
|
+
const appendPhpSnippet = (snippet) => {
|
|
141
|
+
const closingTagPattern = /\?>\s*$/u;
|
|
142
|
+
if (closingTagPattern.test(nextSource)) {
|
|
143
|
+
nextSource = nextSource.replace(closingTagPattern, `${snippet}\n?>`);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
nextSource = `${nextSource.trimEnd()}\n${snippet}\n`;
|
|
147
|
+
};
|
|
148
|
+
if (!hasPhpFunctionDefinition(nextSource, loadFunctionName)) {
|
|
149
|
+
insertPhpSnippet(loadFunction);
|
|
150
|
+
}
|
|
151
|
+
if (!hasPhpFunctionDefinition(nextSource, enqueueFunctionName)) {
|
|
152
|
+
insertPhpSnippet(enqueueFunction);
|
|
153
|
+
}
|
|
154
|
+
else if (!findPhpFunctionRange(nextSource, enqueueFunctionName)?.source.includes("wp_enqueue_script_module")) {
|
|
155
|
+
nextSource =
|
|
156
|
+
replacePhpFunctionDefinition(nextSource, enqueueFunctionName, enqueueFunction, { trimReplacementStart: true }) ?? nextSource;
|
|
157
|
+
}
|
|
158
|
+
if (!nextSource.includes(loadHook)) {
|
|
159
|
+
appendPhpSnippet(loadHook);
|
|
160
|
+
}
|
|
161
|
+
if (!nextSource.includes(adminEnqueueHook)) {
|
|
162
|
+
appendPhpSnippet(adminEnqueueHook);
|
|
163
|
+
}
|
|
164
|
+
if (!nextSource.includes(editorEnqueueHook)) {
|
|
165
|
+
appendPhpSnippet(editorEnqueueHook);
|
|
166
|
+
}
|
|
167
|
+
return nextSource;
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
async function ensureAbilityPackageScripts(workspace) {
|
|
171
|
+
const packageJsonPath = path.join(workspace.projectDir, "package.json");
|
|
172
|
+
const packageJson = JSON.parse(await fsp.readFile(packageJsonPath, "utf8"));
|
|
173
|
+
const nextScripts = {
|
|
174
|
+
...(packageJson.scripts ?? {}),
|
|
175
|
+
"sync-abilities": packageJson.scripts?.["sync-abilities"] ?? "tsx scripts/sync-abilities.ts",
|
|
176
|
+
};
|
|
177
|
+
const nextDependencies = {
|
|
178
|
+
...(packageJson.dependencies ?? {}),
|
|
179
|
+
[WP_ABILITIES_SCRIPT_MODULE_ID]: resolveManagedDependencyVersion(packageJson.dependencies?.[WP_ABILITIES_SCRIPT_MODULE_ID], DEFAULT_WORDPRESS_ABILITIES_VERSION),
|
|
180
|
+
[WP_CORE_ABILITIES_SCRIPT_MODULE_ID]: resolveManagedDependencyVersion(packageJson.dependencies?.[WP_CORE_ABILITIES_SCRIPT_MODULE_ID], DEFAULT_WORDPRESS_CORE_ABILITIES_VERSION),
|
|
181
|
+
};
|
|
182
|
+
if (JSON.stringify(nextScripts) === JSON.stringify(packageJson.scripts ?? {}) &&
|
|
183
|
+
JSON.stringify(nextDependencies) === JSON.stringify(packageJson.dependencies ?? {})) {
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
packageJson.scripts = nextScripts;
|
|
187
|
+
packageJson.dependencies = nextDependencies;
|
|
188
|
+
await fsp.writeFile(packageJsonPath, `${JSON.stringify(packageJson, null, "\t")}\n`, "utf8");
|
|
189
|
+
}
|
|
190
|
+
async function ensureAbilitySyncProjectAnchors(workspace) {
|
|
191
|
+
const syncProjectScriptPath = path.join(workspace.projectDir, "scripts", "sync-project.ts");
|
|
192
|
+
await patchFile(syncProjectScriptPath, (source) => {
|
|
193
|
+
let nextSource = source;
|
|
194
|
+
const syncRestConst = "const syncRestScriptPath = path.join( 'scripts', 'sync-rest-contracts.ts' );";
|
|
195
|
+
const syncAbilitiesConst = "const syncAbilitiesScriptPath = path.join( 'scripts', 'sync-abilities.ts' );";
|
|
196
|
+
const syncRestBlockPattern = /if \( fs\.existsSync\( path\.resolve\( process\.cwd\(\), syncRestScriptPath \) \) \) \{\n\s*runSyncScript\( syncRestScriptPath, options \);\n\s*\}/u;
|
|
197
|
+
const syncAbilitiesBlock = [
|
|
198
|
+
"if ( fs.existsSync( path.resolve( process.cwd(), syncAbilitiesScriptPath ) ) ) {",
|
|
199
|
+
"\trunSyncScript( syncAbilitiesScriptPath, options );",
|
|
200
|
+
"}",
|
|
201
|
+
].join("\n");
|
|
202
|
+
if (!nextSource.includes(syncAbilitiesConst)) {
|
|
203
|
+
if (!nextSource.includes(syncRestConst)) {
|
|
204
|
+
throw new Error([
|
|
205
|
+
`ensureAbilitySyncProjectAnchors could not patch ${path.basename(syncProjectScriptPath)}.`,
|
|
206
|
+
"Missing the expected sync-rest script constant in scripts/sync-project.ts.",
|
|
207
|
+
"Restore the generated template or wire sync-abilities manually before retrying.",
|
|
208
|
+
].join(" "));
|
|
209
|
+
}
|
|
210
|
+
nextSource = nextSource.replace(syncRestConst, `${syncRestConst}\n${syncAbilitiesConst}`);
|
|
211
|
+
}
|
|
212
|
+
if (!nextSource.includes("runSyncScript( syncAbilitiesScriptPath, options );")) {
|
|
213
|
+
if (!syncRestBlockPattern.test(nextSource)) {
|
|
214
|
+
throw new Error([
|
|
215
|
+
`ensureAbilitySyncProjectAnchors could not patch ${path.basename(syncProjectScriptPath)}.`,
|
|
216
|
+
"Missing the expected sync-rest invocation block in scripts/sync-project.ts.",
|
|
217
|
+
"Restore the generated template or wire sync-abilities manually before retrying.",
|
|
218
|
+
].join(" "));
|
|
219
|
+
}
|
|
220
|
+
nextSource = nextSource.replace(syncRestBlockPattern, (match) => `${match}\n\n${syncAbilitiesBlock}`);
|
|
221
|
+
}
|
|
222
|
+
return nextSource;
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
async function ensureAbilityBuildScriptAnchors(workspace) {
|
|
226
|
+
const buildScriptPath = path.join(workspace.projectDir, "scripts", "build-workspace.mjs");
|
|
227
|
+
await patchFile(buildScriptPath, (source) => {
|
|
228
|
+
let nextSource = source;
|
|
229
|
+
if (/['"]src\/abilities\/index\.(?:ts|js)['"]/u.test(nextSource)) {
|
|
230
|
+
return nextSource;
|
|
231
|
+
}
|
|
232
|
+
const sharedEntriesPattern = /(for\s*\(\s*const\s+relativePath\s+of\s+\[)([\s\S]*?)(\]\s*\)\s*\{)/u;
|
|
233
|
+
const match = nextSource.match(sharedEntriesPattern);
|
|
234
|
+
if (!match ||
|
|
235
|
+
!/['"]src\/bindings\/index\.(?:ts|js)['"]/u.test(match[2]) ||
|
|
236
|
+
!/['"]src\/editor-plugins\/index\.(?:tsx|ts|js)['"]/u.test(match[2])) {
|
|
237
|
+
throw new Error([
|
|
238
|
+
`ensureAbilityBuildScriptAnchors could not patch ${path.basename(buildScriptPath)}.`,
|
|
239
|
+
"Missing the expected shared editor entries array in scripts/build-workspace.mjs.",
|
|
240
|
+
"Restore the generated template or wire abilities/index manually before retrying.",
|
|
241
|
+
].join(" "));
|
|
242
|
+
}
|
|
243
|
+
nextSource = nextSource.replace(sharedEntriesPattern, (fullMatch, sharedEntries) => {
|
|
244
|
+
const missingAbilityEntries = [
|
|
245
|
+
"'src/abilities/index.ts'",
|
|
246
|
+
"'src/abilities/index.js'",
|
|
247
|
+
].filter((entry) => !sharedEntries.includes(entry));
|
|
248
|
+
if (missingAbilityEntries.length === 0) {
|
|
249
|
+
return fullMatch;
|
|
250
|
+
}
|
|
251
|
+
const itemIndent = sharedEntries.match(/\n([ \t]*)['"]/u)?.[1] ?? "\t\t";
|
|
252
|
+
const trimmedEntries = sharedEntries.replace(/\s*$/u, "");
|
|
253
|
+
const trailingWhitespace = sharedEntries.slice(trimmedEntries.length);
|
|
254
|
+
const separator = trimmedEntries.trimEnd().endsWith(",") ? "" : ",";
|
|
255
|
+
const nextEntries = `${trimmedEntries}${separator}` +
|
|
256
|
+
missingAbilityEntries.map((entry) => `\n${itemIndent}${entry},`).join("") +
|
|
257
|
+
trailingWhitespace;
|
|
258
|
+
return fullMatch.replace(sharedEntries, nextEntries);
|
|
259
|
+
});
|
|
260
|
+
return nextSource;
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
async function ensureAbilityWebpackAnchors(workspace) {
|
|
264
|
+
const webpackConfigPath = path.join(workspace.projectDir, "webpack.config.js");
|
|
265
|
+
await patchFile(webpackConfigPath, (source) => {
|
|
266
|
+
if (/['"]abilities\/index['"]/u.test(source)) {
|
|
267
|
+
return source;
|
|
268
|
+
}
|
|
269
|
+
const optionalModuleReturnPattern = /(function\s+getOptionalModuleEntries\s*\(\)\s*\{[\s\S]*?)(\n\treturn Object\.fromEntries\(\s*entries\s*\);\n\})/u;
|
|
270
|
+
if (optionalModuleReturnPattern.test(source)) {
|
|
271
|
+
return source.replace(optionalModuleReturnPattern, `$1
|
|
272
|
+
|
|
273
|
+
\tfor ( const [ entryName, candidates ] of [
|
|
274
|
+
\t\t[
|
|
275
|
+
\t\t\t'abilities/index',
|
|
276
|
+
\t\t\t[ 'src/abilities/index.ts', 'src/abilities/index.js' ],
|
|
277
|
+
\t\t],
|
|
278
|
+
\t] ) {
|
|
279
|
+
\t\tfor ( const relativePath of candidates ) {
|
|
280
|
+
\t\t\tconst entryPath = path.resolve( process.cwd(), relativePath );
|
|
281
|
+
\t\t\tif ( ! fs.existsSync( entryPath ) ) {
|
|
282
|
+
\t\t\t\tcontinue;
|
|
283
|
+
\t\t\t}
|
|
284
|
+
|
|
285
|
+
\t\t\tentries.push( [ entryName, entryPath ] );
|
|
286
|
+
\t\t\tbreak;
|
|
287
|
+
\t\t}
|
|
288
|
+
\t}$2`);
|
|
289
|
+
}
|
|
290
|
+
const sharedEntriesPattern = /for\s*\(\s*const\s+\[\s*entryName\s*,\s*candidates\s*\]\s+of\s+\[([\s\S]*?)\]\s*\)\s*\{/u;
|
|
291
|
+
const match = source.match(sharedEntriesPattern);
|
|
292
|
+
if (!match ||
|
|
293
|
+
!match[1].includes("bindings/index") ||
|
|
294
|
+
!match[1].includes("editor-plugins/index")) {
|
|
295
|
+
throw new Error([
|
|
296
|
+
`ensureAbilityWebpackAnchors could not patch ${path.basename(webpackConfigPath)}.`,
|
|
297
|
+
"Missing the expected shared editor entries block in webpack.config.js.",
|
|
298
|
+
"Restore the generated template or wire abilities/index manually before retrying.",
|
|
299
|
+
].join(" "));
|
|
300
|
+
}
|
|
301
|
+
return source.replace(sharedEntriesPattern, (fullMatch, sharedEntries) => {
|
|
302
|
+
if (/['"]abilities\/index['"]/u.test(sharedEntries)) {
|
|
303
|
+
return fullMatch;
|
|
304
|
+
}
|
|
305
|
+
const tupleIndent = sharedEntries.match(/\n([ \t]*)\[/u)?.[1] ?? "\t\t";
|
|
306
|
+
const nestedIndent = `${tupleIndent}\t`;
|
|
307
|
+
const trimmedEntries = sharedEntries.replace(/\s*$/u, "");
|
|
308
|
+
const trailingWhitespace = sharedEntries.slice(trimmedEntries.length);
|
|
309
|
+
const separator = trimmedEntries.trimEnd().endsWith(",") ? "" : ",";
|
|
310
|
+
const abilityTuple = [
|
|
311
|
+
`${tupleIndent}[`,
|
|
312
|
+
`${nestedIndent}'abilities/index',`,
|
|
313
|
+
`${nestedIndent}[ 'src/abilities/index.ts', 'src/abilities/index.js' ],`,
|
|
314
|
+
`${tupleIndent}],`,
|
|
315
|
+
].join("\n");
|
|
316
|
+
const nextEntries = `${trimmedEntries}${separator}\n${abilityTuple}` + trailingWhitespace;
|
|
317
|
+
return fullMatch.replace(sharedEntries, nextEntries);
|
|
318
|
+
});
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Write generated workflow ability sources and patch shared workspace anchors.
|
|
323
|
+
*/
|
|
324
|
+
export async function scaffoldAbilityWorkspace({ abilitySlug, compatibilityPolicy, workspace, }) {
|
|
325
|
+
const blockConfigPath = path.join(workspace.projectDir, "scripts", "block-config.ts");
|
|
326
|
+
const bootstrapPath = getWorkspaceBootstrapPath(workspace);
|
|
327
|
+
const buildScriptPath = path.join(workspace.projectDir, "scripts", "build-workspace.mjs");
|
|
328
|
+
const packageJsonPath = path.join(workspace.projectDir, "package.json");
|
|
329
|
+
const syncAbilitiesScriptPath = path.join(workspace.projectDir, "scripts", "sync-abilities.ts");
|
|
330
|
+
const syncProjectScriptPath = path.join(workspace.projectDir, "scripts", "sync-project.ts");
|
|
331
|
+
const webpackConfigPath = path.join(workspace.projectDir, "webpack.config.js");
|
|
332
|
+
const abilitiesIndexPath = resolveAbilityRegistryPath(workspace.projectDir);
|
|
333
|
+
const abilityDir = path.join(workspace.projectDir, "src", "abilities", abilitySlug);
|
|
334
|
+
const configFilePath = path.join(abilityDir, "ability.config.json");
|
|
335
|
+
const typesFilePath = path.join(abilityDir, "types.ts");
|
|
336
|
+
const dataFilePath = path.join(abilityDir, "data.ts");
|
|
337
|
+
const clientFilePath = path.join(abilityDir, "client.ts");
|
|
338
|
+
const phpFilePath = path.join(workspace.projectDir, "inc", "abilities", `${abilitySlug}.php`);
|
|
339
|
+
const mutationSnapshot = {
|
|
340
|
+
fileSources: await snapshotWorkspaceFiles([
|
|
341
|
+
blockConfigPath,
|
|
342
|
+
bootstrapPath,
|
|
343
|
+
buildScriptPath,
|
|
344
|
+
packageJsonPath,
|
|
345
|
+
syncAbilitiesScriptPath,
|
|
346
|
+
syncProjectScriptPath,
|
|
347
|
+
webpackConfigPath,
|
|
348
|
+
abilitiesIndexPath,
|
|
349
|
+
]),
|
|
350
|
+
snapshotDirs: [],
|
|
351
|
+
targetPaths: [abilityDir, phpFilePath, syncAbilitiesScriptPath],
|
|
352
|
+
};
|
|
353
|
+
try {
|
|
354
|
+
await fsp.mkdir(abilityDir, { recursive: true });
|
|
355
|
+
await fsp.mkdir(path.dirname(phpFilePath), { recursive: true });
|
|
356
|
+
await ensureAbilityBootstrapAnchors(workspace);
|
|
357
|
+
await patchFile(bootstrapPath, (source) => updatePluginHeaderCompatibility(source, compatibilityPolicy));
|
|
358
|
+
await ensureAbilityPackageScripts(workspace);
|
|
359
|
+
await ensureAbilitySyncProjectAnchors(workspace);
|
|
360
|
+
await ensureAbilityBuildScriptAnchors(workspace);
|
|
361
|
+
await ensureAbilityWebpackAnchors(workspace);
|
|
362
|
+
await fsp.writeFile(syncAbilitiesScriptPath, buildAbilitySyncScriptSource(), "utf8");
|
|
363
|
+
await fsp.writeFile(configFilePath, buildAbilityConfigSource(abilitySlug, workspace.workspace.namespace), "utf8");
|
|
364
|
+
await fsp.writeFile(typesFilePath, buildAbilityTypesSource(abilitySlug), "utf8");
|
|
365
|
+
await fsp.writeFile(dataFilePath, buildAbilityDataSource(abilitySlug), "utf8");
|
|
366
|
+
await fsp.writeFile(clientFilePath, buildAbilityClientSource(abilitySlug), "utf8");
|
|
367
|
+
await fsp.writeFile(phpFilePath, buildAbilityPhpSource(abilitySlug, workspace), "utf8");
|
|
368
|
+
const pascalCase = toPascalCase(abilitySlug);
|
|
369
|
+
await syncTypeSchemas({
|
|
370
|
+
jsonSchemaFile: `src/abilities/${abilitySlug}/input.schema.json`,
|
|
371
|
+
projectRoot: workspace.projectDir,
|
|
372
|
+
sourceTypeName: `${pascalCase}AbilityInput`,
|
|
373
|
+
typesFile: `src/abilities/${abilitySlug}/types.ts`,
|
|
374
|
+
});
|
|
375
|
+
await syncTypeSchemas({
|
|
376
|
+
jsonSchemaFile: `src/abilities/${abilitySlug}/output.schema.json`,
|
|
377
|
+
projectRoot: workspace.projectDir,
|
|
378
|
+
sourceTypeName: `${pascalCase}AbilityOutput`,
|
|
379
|
+
typesFile: `src/abilities/${abilitySlug}/types.ts`,
|
|
380
|
+
});
|
|
381
|
+
await writeAbilityRegistry(workspace.projectDir, abilitySlug);
|
|
382
|
+
await appendWorkspaceInventoryEntries(workspace.projectDir, {
|
|
383
|
+
abilityEntries: [
|
|
384
|
+
buildAbilityConfigEntry(abilitySlug, compatibilityPolicy),
|
|
385
|
+
],
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
catch (error) {
|
|
389
|
+
await rollbackWorkspaceMutation(mutationSnapshot);
|
|
390
|
+
throw error;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { ScaffoldCompatibilityPolicy } from "./scaffold-compatibility.js";
|
|
2
|
+
import type { WorkspaceProject } from "./workspace-project.js";
|
|
3
|
+
/**
|
|
4
|
+
* Build the `ABILITIES` inventory entry for a generated workflow ability.
|
|
5
|
+
*/
|
|
6
|
+
export declare function buildAbilityConfigEntry(abilitySlug: string, compatibilityPolicy: ScaffoldCompatibilityPolicy): string;
|
|
7
|
+
/**
|
|
8
|
+
* Build the JSON config document that powers server-side ability registration.
|
|
9
|
+
*/
|
|
10
|
+
export declare function buildAbilityConfigSource(abilitySlug: string, workspaceNamespace: string): string;
|
|
11
|
+
/**
|
|
12
|
+
* Build the starter TypeScript input and output contracts for an ability.
|
|
13
|
+
*/
|
|
14
|
+
export declare function buildAbilityTypesSource(abilitySlug: string): string;
|
|
15
|
+
/**
|
|
16
|
+
* Build the typed client helper module that wraps the WordPress Abilities API.
|
|
17
|
+
*/
|
|
18
|
+
export declare function buildAbilityDataSource(abilitySlug: string): string;
|
|
19
|
+
/**
|
|
20
|
+
* Build the re-export shim for the generated ability client helpers.
|
|
21
|
+
*/
|
|
22
|
+
export declare function buildAbilityClientSource(abilitySlug: string): string;
|
|
23
|
+
/**
|
|
24
|
+
* Build the schema sync script that keeps generated ability JSON artifacts current.
|
|
25
|
+
*/
|
|
26
|
+
export declare function buildAbilitySyncScriptSource(): string;
|
|
27
|
+
/**
|
|
28
|
+
* Build the PHP ability registration module for a generated workflow ability.
|
|
29
|
+
*/
|
|
30
|
+
export declare function buildAbilityPhpSource(abilitySlug: string, workspace: WorkspaceProject): string;
|
|
31
|
+
/**
|
|
32
|
+
* Build the generated abilities index section managed by `wp-typia add ability`.
|
|
33
|
+
*/
|
|
34
|
+
export declare function buildAbilityRegistrySource(abilitySlugs: string[]): string;
|