@wp-typia/project-tools 0.22.3 → 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.
Files changed (40) hide show
  1. package/dist/runtime/cli-add-block-json.d.ts +31 -0
  2. package/dist/runtime/cli-add-block-json.js +65 -0
  3. package/dist/runtime/cli-add-collision.d.ts +129 -0
  4. package/dist/runtime/cli-add-collision.js +293 -0
  5. package/dist/runtime/cli-add-filesystem.d.ts +29 -0
  6. package/dist/runtime/cli-add-filesystem.js +77 -0
  7. package/dist/runtime/cli-add-help.d.ts +4 -0
  8. package/dist/runtime/cli-add-help.js +41 -0
  9. package/dist/runtime/cli-add-shared.d.ts +6 -304
  10. package/dist/runtime/cli-add-shared.js +6 -524
  11. package/dist/runtime/cli-add-types.d.ts +247 -0
  12. package/dist/runtime/cli-add-types.js +64 -0
  13. package/dist/runtime/cli-add-validation.d.ts +87 -0
  14. package/dist/runtime/cli-add-validation.js +147 -0
  15. package/dist/runtime/cli-add-workspace-ability-scaffold.js +46 -72
  16. package/dist/runtime/cli-add-workspace-admin-view-scaffold.js +35 -61
  17. package/dist/runtime/cli-add-workspace-ai-scaffold.js +53 -57
  18. package/dist/runtime/cli-add-workspace-ai-templates.js +2 -0
  19. package/dist/runtime/cli-add-workspace-mutation.d.ts +30 -0
  20. package/dist/runtime/cli-add-workspace-mutation.js +60 -0
  21. package/dist/runtime/cli-add-workspace.js +1 -79
  22. package/dist/runtime/cli-add.d.ts +2 -2
  23. package/dist/runtime/cli-add.js +2 -2
  24. package/dist/runtime/cli-doctor-workspace-blocks.js +1 -66
  25. package/dist/runtime/index.d.ts +2 -0
  26. package/dist/runtime/index.js +1 -0
  27. package/dist/runtime/migration-utils.d.ts +2 -1
  28. package/dist/runtime/migration-utils.js +3 -11
  29. package/dist/runtime/package-managers.d.ts +19 -0
  30. package/dist/runtime/package-managers.js +62 -0
  31. package/dist/runtime/template-source-cache.d.ts +59 -0
  32. package/dist/runtime/template-source-cache.js +160 -0
  33. package/dist/runtime/ts-source-masking.d.ts +28 -0
  34. package/dist/runtime/ts-source-masking.js +104 -0
  35. package/dist/runtime/typia-llm.d.ts +9 -1
  36. package/dist/runtime/typia-llm.js +20 -5
  37. package/dist/runtime/workspace-inventory.js +116 -59
  38. package/dist/runtime/workspace-project.d.ts +1 -1
  39. package/dist/runtime/workspace-project.js +2 -10
  40. package/package.json +2 -2
@@ -6,7 +6,8 @@ import semver from "semver";
6
6
  import { appendWorkspaceInventoryEntries, readWorkspaceInventory, } from "./workspace-inventory.js";
7
7
  import { buildAbilityClientSource, buildAbilityConfigEntry, buildAbilityConfigSource, buildAbilityDataSource, buildAbilityPhpSource, buildAbilityRegistrySource, buildAbilitySyncScriptSource, buildAbilityTypesSource, } from "./cli-add-workspace-ability-templates.js";
8
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";
9
+ import { getWorkspaceBootstrapPath, patchFile, } from "./cli-add-shared.js";
10
+ import { appendPhpSnippetBeforeClosingTag, executeWorkspaceMutationPlan, insertPhpSnippetBeforeWorkspaceAnchors, } from "./cli-add-workspace-mutation.js";
10
11
  import { updatePluginHeaderCompatibility, } from "./scaffold-compatibility.js";
11
12
  import { DEFAULT_WORDPRESS_ABILITIES_VERSION, DEFAULT_WORDPRESS_CORE_ABILITIES_VERSION, } from "./package-versions.js";
12
13
  import { escapeRegex, findPhpFunctionRange, hasPhpFunctionDefinition, replacePhpFunctionDefinition, } from "./php-utils.js";
@@ -123,46 +124,24 @@ function ${enqueueFunctionName}() {
123
124
  \t);
124
125
  }
125
126
  `;
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
127
  if (!hasPhpFunctionDefinition(nextSource, loadFunctionName)) {
149
- insertPhpSnippet(loadFunction);
128
+ nextSource = insertPhpSnippetBeforeWorkspaceAnchors(nextSource, loadFunction);
150
129
  }
151
130
  if (!hasPhpFunctionDefinition(nextSource, enqueueFunctionName)) {
152
- insertPhpSnippet(enqueueFunction);
131
+ nextSource = insertPhpSnippetBeforeWorkspaceAnchors(nextSource, enqueueFunction);
153
132
  }
154
133
  else if (!findPhpFunctionRange(nextSource, enqueueFunctionName)?.source.includes("wp_enqueue_script_module")) {
155
134
  nextSource =
156
135
  replacePhpFunctionDefinition(nextSource, enqueueFunctionName, enqueueFunction, { trimReplacementStart: true }) ?? nextSource;
157
136
  }
158
137
  if (!nextSource.includes(loadHook)) {
159
- appendPhpSnippet(loadHook);
138
+ nextSource = appendPhpSnippetBeforeClosingTag(nextSource, loadHook);
160
139
  }
161
140
  if (!nextSource.includes(adminEnqueueHook)) {
162
- appendPhpSnippet(adminEnqueueHook);
141
+ nextSource = appendPhpSnippetBeforeClosingTag(nextSource, adminEnqueueHook);
163
142
  }
164
143
  if (!nextSource.includes(editorEnqueueHook)) {
165
- appendPhpSnippet(editorEnqueueHook);
144
+ nextSource = appendPhpSnippetBeforeClosingTag(nextSource, editorEnqueueHook);
166
145
  }
167
146
  return nextSource;
168
147
  });
@@ -336,8 +315,8 @@ export async function scaffoldAbilityWorkspace({ abilitySlug, compatibilityPolic
336
315
  const dataFilePath = path.join(abilityDir, "data.ts");
337
316
  const clientFilePath = path.join(abilityDir, "client.ts");
338
317
  const phpFilePath = path.join(workspace.projectDir, "inc", "abilities", `${abilitySlug}.php`);
339
- const mutationSnapshot = {
340
- fileSources: await snapshotWorkspaceFiles([
318
+ await executeWorkspaceMutationPlan({
319
+ filePaths: [
341
320
  blockConfigPath,
342
321
  bootstrapPath,
343
322
  buildScriptPath,
@@ -346,47 +325,42 @@ export async function scaffoldAbilityWorkspace({ abilitySlug, compatibilityPolic
346
325
  syncProjectScriptPath,
347
326
  webpackConfigPath,
348
327
  abilitiesIndexPath,
349
- ]),
350
- snapshotDirs: [],
328
+ ],
351
329
  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
- }
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
+ });
392
366
  }
@@ -4,7 +4,8 @@ import path from 'node:path';
4
4
  import { appendWorkspaceInventoryEntries, readWorkspaceInventory, } from './workspace-inventory.js';
5
5
  import { buildAdminViewConfigEntry, buildAdminViewConfigSource, buildAdminViewEntrySource, buildAdminViewPhpSource, buildAdminViewRegistrySource, buildAdminViewScreenSource, buildAdminViewStyleSource, buildAdminViewTypesSource, buildCoreDataAdminViewDataSource, buildCoreDataAdminViewScreenSource, buildDefaultAdminViewDataSource, buildRestAdminViewDataSource, } from './cli-add-workspace-admin-view-templates.js';
6
6
  import { ADMIN_VIEWS_PHP_GLOB, isAdminViewCoreDataSource, } from './cli-add-workspace-admin-view-types.js';
7
- import { getWorkspaceBootstrapPath, patchFile, rollbackWorkspaceMutation, snapshotWorkspaceFiles, } from './cli-add-shared.js';
7
+ import { getWorkspaceBootstrapPath, patchFile, } from './cli-add-shared.js';
8
+ import { appendPhpSnippetBeforeClosingTag, executeWorkspaceMutationPlan, insertPhpSnippetBeforeWorkspaceAnchors, } from './cli-add-workspace-mutation.js';
8
9
  import { DEFAULT_WORDPRESS_CORE_DATA_VERSION, DEFAULT_WORDPRESS_DATA_VERSION, DEFAULT_WORDPRESS_DATAVIEWS_VERSION, DEFAULT_WP_TYPIA_DATAVIEWS_VERSION, resolveManagedPackageVersionRange, } from './package-versions.js';
9
10
  import { findPhpFunctionRange, hasPhpFunctionDefinition, replacePhpFunctionDefinition, } from './php-utils.js';
10
11
  function detectJsonIndent(source) {
@@ -77,30 +78,8 @@ function ${loadFunctionName}() {
77
78
  \t}
78
79
  }
79
80
  `;
80
- const insertionAnchors = [
81
- /add_action\(\s*["']init["']\s*,\s*["'][^"']+_load_textdomain["']\s*\);\s*\n/u,
82
- /\?>\s*$/u,
83
- ];
84
- const insertPhpSnippet = (snippet) => {
85
- for (const anchor of insertionAnchors) {
86
- const candidate = nextSource.replace(anchor, (match) => `${snippet}\n${match}`);
87
- if (candidate !== nextSource) {
88
- nextSource = candidate;
89
- return;
90
- }
91
- }
92
- nextSource = `${nextSource.trimEnd()}\n${snippet}\n`;
93
- };
94
- const appendPhpSnippet = (snippet) => {
95
- const closingTagPattern = /\?>\s*$/u;
96
- if (closingTagPattern.test(nextSource)) {
97
- nextSource = nextSource.replace(closingTagPattern, `${snippet}\n?>`);
98
- return;
99
- }
100
- nextSource = `${nextSource.trimEnd()}\n${snippet}\n`;
101
- };
102
81
  if (!hasPhpFunctionDefinition(nextSource, loadFunctionName)) {
103
- insertPhpSnippet(loadFunction);
82
+ nextSource = insertPhpSnippetBeforeWorkspaceAnchors(nextSource, loadFunction);
104
83
  }
105
84
  else {
106
85
  const functionRange = findPhpFunctionRange(nextSource, loadFunctionName);
@@ -116,7 +95,7 @@ function ${loadFunctionName}() {
116
95
  }
117
96
  }
118
97
  if (!loadHookPattern.test(nextSource)) {
119
- appendPhpSnippet(loadHook);
98
+ nextSource = appendPhpSnippetBeforeClosingTag(nextSource, loadHook);
120
99
  }
121
100
  return nextSource;
122
101
  });
@@ -211,47 +190,42 @@ export async function scaffoldAdminViewWorkspace(options) {
211
190
  const adminViewsIndexPath = resolveAdminViewRegistryPath(workspace.projectDir);
212
191
  const adminViewDir = path.join(workspace.projectDir, 'src', 'admin-views', adminViewSlug);
213
192
  const adminViewPhpPath = path.join(workspace.projectDir, 'inc', 'admin-views', `${adminViewSlug}.php`);
214
- const mutationSnapshot = {
215
- fileSources: await snapshotWorkspaceFiles([
193
+ await executeWorkspaceMutationPlan({
194
+ filePaths: [
216
195
  adminViewsIndexPath,
217
196
  blockConfigPath,
218
197
  bootstrapPath,
219
198
  buildScriptPath,
220
199
  packageJsonPath,
221
200
  webpackConfigPath,
222
- ]),
223
- snapshotDirs: [],
201
+ ],
224
202
  targetPaths: [adminViewDir, adminViewPhpPath],
225
- };
226
- try {
227
- await fsp.mkdir(adminViewDir, { recursive: true });
228
- await fsp.mkdir(path.dirname(adminViewPhpPath), { recursive: true });
229
- await ensureAdminViewPackageDependencies(workspace, parsedSource);
230
- await ensureAdminViewBootstrapAnchors(workspace);
231
- await ensureAdminViewBuildScriptAnchors(workspace);
232
- await ensureAdminViewWebpackAnchors(workspace);
233
- await fsp.writeFile(path.join(adminViewDir, 'types.ts'), buildAdminViewTypesSource(adminViewSlug, restResource, coreDataSource), 'utf8');
234
- await fsp.writeFile(path.join(adminViewDir, 'config.ts'), buildAdminViewConfigSource(adminViewSlug, workspace.workspace.textDomain, parsedSource, restResource), 'utf8');
235
- await fsp.writeFile(path.join(adminViewDir, 'data.ts'), coreDataSource
236
- ? buildCoreDataAdminViewDataSource(adminViewSlug, coreDataSource)
237
- : restResource
238
- ? buildRestAdminViewDataSource(adminViewSlug, restResource)
239
- : buildDefaultAdminViewDataSource(adminViewSlug), 'utf8');
240
- await fsp.writeFile(path.join(adminViewDir, 'Screen.tsx'), coreDataSource
241
- ? buildCoreDataAdminViewScreenSource(adminViewSlug, workspace.workspace.textDomain)
242
- : buildAdminViewScreenSource(adminViewSlug, workspace.workspace.textDomain), 'utf8');
243
- await fsp.writeFile(path.join(adminViewDir, 'index.tsx'), buildAdminViewEntrySource(adminViewSlug), 'utf8');
244
- await fsp.writeFile(path.join(adminViewDir, 'style.scss'), buildAdminViewStyleSource(), 'utf8');
245
- await fsp.writeFile(adminViewPhpPath, buildAdminViewPhpSource(adminViewSlug, workspace), 'utf8');
246
- await writeAdminViewRegistry(workspace.projectDir, adminViewSlug);
247
- await appendWorkspaceInventoryEntries(workspace.projectDir, {
248
- adminViewEntries: [
249
- buildAdminViewConfigEntry(adminViewSlug, parsedSource),
250
- ],
251
- });
252
- }
253
- catch (error) {
254
- await rollbackWorkspaceMutation(mutationSnapshot);
255
- throw error;
256
- }
203
+ run: async () => {
204
+ await fsp.mkdir(adminViewDir, { recursive: true });
205
+ await fsp.mkdir(path.dirname(adminViewPhpPath), { recursive: true });
206
+ await ensureAdminViewPackageDependencies(workspace, parsedSource);
207
+ await ensureAdminViewBootstrapAnchors(workspace);
208
+ await ensureAdminViewBuildScriptAnchors(workspace);
209
+ await ensureAdminViewWebpackAnchors(workspace);
210
+ await fsp.writeFile(path.join(adminViewDir, 'types.ts'), buildAdminViewTypesSource(adminViewSlug, restResource, coreDataSource), 'utf8');
211
+ await fsp.writeFile(path.join(adminViewDir, 'config.ts'), buildAdminViewConfigSource(adminViewSlug, workspace.workspace.textDomain, parsedSource, restResource), 'utf8');
212
+ await fsp.writeFile(path.join(adminViewDir, 'data.ts'), coreDataSource
213
+ ? buildCoreDataAdminViewDataSource(adminViewSlug, coreDataSource)
214
+ : restResource
215
+ ? buildRestAdminViewDataSource(adminViewSlug, restResource)
216
+ : buildDefaultAdminViewDataSource(adminViewSlug), 'utf8');
217
+ await fsp.writeFile(path.join(adminViewDir, 'Screen.tsx'), coreDataSource
218
+ ? buildCoreDataAdminViewScreenSource(adminViewSlug, workspace.workspace.textDomain)
219
+ : buildAdminViewScreenSource(adminViewSlug, workspace.workspace.textDomain), 'utf8');
220
+ await fsp.writeFile(path.join(adminViewDir, 'index.tsx'), buildAdminViewEntrySource(adminViewSlug), 'utf8');
221
+ await fsp.writeFile(path.join(adminViewDir, 'style.scss'), buildAdminViewStyleSource(), 'utf8');
222
+ await fsp.writeFile(adminViewPhpPath, buildAdminViewPhpSource(adminViewSlug, workspace), 'utf8');
223
+ await writeAdminViewRegistry(workspace.projectDir, adminViewSlug);
224
+ await appendWorkspaceInventoryEntries(workspace.projectDir, {
225
+ adminViewEntries: [
226
+ buildAdminViewConfigEntry(adminViewSlug, parsedSource),
227
+ ],
228
+ });
229
+ },
230
+ });
257
231
  }
@@ -5,7 +5,8 @@ import { buildAiFeatureConfigEntry, buildAiFeatureDataSource, buildAiFeatureSync
5
5
  import { ensureAiFeatureBootstrapAnchors, ensureAiFeaturePackageScripts, ensureAiFeatureSyncProjectAnchors, ensureAiFeatureSyncRestAnchors, } from "./cli-add-workspace-ai-anchors.js";
6
6
  import { buildAiFeaturePhpSource } from "./cli-add-workspace-ai-templates.js";
7
7
  import { appendWorkspaceInventoryEntries } from "./workspace-inventory.js";
8
- import { getWorkspaceBootstrapPath, patchFile, rollbackWorkspaceMutation, snapshotWorkspaceFiles, } from "./cli-add-shared.js";
8
+ import { getWorkspaceBootstrapPath, patchFile, } from "./cli-add-shared.js";
9
+ import { executeWorkspaceMutationPlan } from "./cli-add-workspace-mutation.js";
9
10
  import { updatePluginHeaderCompatibility } from "./scaffold-compatibility.js";
10
11
  import { toPascalCase, toTitleCase } from "./string-case.js";
11
12
  import { syncAiFeatureRestArtifacts, syncAiFeatureSchemaArtifact, } from "./ai-feature-artifacts.js";
@@ -25,67 +26,62 @@ export async function scaffoldAiFeatureWorkspace({ aiFeatureSlug, compatibilityP
25
26
  const apiFilePath = path.join(aiFeatureDir, "api.ts");
26
27
  const dataFilePath = path.join(aiFeatureDir, "data.ts");
27
28
  const phpFilePath = path.join(workspace.projectDir, "inc", "ai-features", `${aiFeatureSlug}.php`);
28
- const mutationSnapshot = {
29
- fileSources: await snapshotWorkspaceFiles([
29
+ return executeWorkspaceMutationPlan({
30
+ filePaths: [
30
31
  blockConfigPath,
31
32
  bootstrapPath,
32
33
  packageJsonPath,
33
34
  syncAiScriptPath,
34
35
  syncProjectScriptPath,
35
36
  syncRestScriptPath,
36
- ]),
37
- snapshotDirs: [],
37
+ ],
38
38
  targetPaths: [aiFeatureDir, phpFilePath, syncAiScriptPath],
39
- };
40
- try {
41
- await fsp.mkdir(aiFeatureDir, { recursive: true });
42
- await fsp.mkdir(path.dirname(phpFilePath), { recursive: true });
43
- await ensureAiFeatureBootstrapAnchors(workspace);
44
- await patchFile(bootstrapPath, (source) => updatePluginHeaderCompatibility(source, compatibilityPolicy));
45
- const packageScriptChanges = await ensureAiFeaturePackageScripts(workspace);
46
- await ensureAiFeatureSyncProjectAnchors(workspace);
47
- await ensureAiFeatureSyncRestAnchors(workspace);
48
- await fsp.writeFile(syncAiScriptPath, buildAiFeatureSyncScriptSource(), "utf8");
49
- await fsp.writeFile(typesFilePath, buildAiFeatureTypesSource(aiFeatureSlug), "utf8");
50
- await fsp.writeFile(validatorsFilePath, buildAiFeatureValidatorsSource(aiFeatureSlug), "utf8");
51
- await fsp.writeFile(apiFilePath, buildAiFeatureApiSource(aiFeatureSlug), "utf8");
52
- await fsp.writeFile(dataFilePath, buildAiFeatureDataSource(aiFeatureSlug), "utf8");
53
- await fsp.writeFile(phpFilePath, buildAiFeaturePhpSource(aiFeatureSlug, namespace, workspace.workspace.phpPrefix, workspace.workspace.textDomain), "utf8");
54
- const pascalCase = toPascalCase(aiFeatureSlug);
55
- await syncAiFeatureRestArtifacts({
56
- clientFile: `src/ai-features/${aiFeatureSlug}/api-client.ts`,
57
- outputDir: path.join("src", "ai-features", aiFeatureSlug),
58
- projectDir: workspace.projectDir,
59
- typesFile: `src/ai-features/${aiFeatureSlug}/api-types.ts`,
60
- validatorsFile: `src/ai-features/${aiFeatureSlug}/api-validators.ts`,
61
- variables: {
62
- namespace,
63
- pascalCase,
64
- slugKebabCase: aiFeatureSlug,
65
- title: toTitleCase(aiFeatureSlug),
66
- },
67
- });
68
- await syncAiFeatureSchemaArtifact({
69
- aiSchemaFile: `src/ai-features/${aiFeatureSlug}/ai-schemas/feature-result.ai.schema.json`,
70
- outputDir: path.join("src", "ai-features", aiFeatureSlug),
71
- projectDir: workspace.projectDir,
72
- });
73
- await appendWorkspaceInventoryEntries(workspace.projectDir, {
74
- aiFeatureEntries: [
75
- buildAiFeatureConfigEntry(aiFeatureSlug, namespace),
76
- ],
77
- transformSource: ensureBlockConfigCanAddRestManifests,
78
- });
79
- return {
80
- warnings: packageScriptChanges.addedProjectToolsDependency
81
- ? [
82
- "Added `@wp-typia/project-tools` to devDependencies for `sync-ai`. If this workspace was already installed, rerun your package manager install command before the first `wp-typia sync ai`.",
83
- ]
84
- : [],
85
- };
86
- }
87
- catch (error) {
88
- await rollbackWorkspaceMutation(mutationSnapshot);
89
- throw error;
90
- }
39
+ run: async () => {
40
+ await fsp.mkdir(aiFeatureDir, { recursive: true });
41
+ await fsp.mkdir(path.dirname(phpFilePath), { recursive: true });
42
+ await ensureAiFeatureBootstrapAnchors(workspace);
43
+ await patchFile(bootstrapPath, (source) => updatePluginHeaderCompatibility(source, compatibilityPolicy));
44
+ const packageScriptChanges = await ensureAiFeaturePackageScripts(workspace);
45
+ await ensureAiFeatureSyncProjectAnchors(workspace);
46
+ await ensureAiFeatureSyncRestAnchors(workspace);
47
+ await fsp.writeFile(syncAiScriptPath, buildAiFeatureSyncScriptSource(), "utf8");
48
+ await fsp.writeFile(typesFilePath, buildAiFeatureTypesSource(aiFeatureSlug), "utf8");
49
+ await fsp.writeFile(validatorsFilePath, buildAiFeatureValidatorsSource(aiFeatureSlug), "utf8");
50
+ await fsp.writeFile(apiFilePath, buildAiFeatureApiSource(aiFeatureSlug), "utf8");
51
+ await fsp.writeFile(dataFilePath, buildAiFeatureDataSource(aiFeatureSlug), "utf8");
52
+ await fsp.writeFile(phpFilePath, buildAiFeaturePhpSource(aiFeatureSlug, namespace, workspace.workspace.phpPrefix, workspace.workspace.textDomain), "utf8");
53
+ const pascalCase = toPascalCase(aiFeatureSlug);
54
+ await syncAiFeatureRestArtifacts({
55
+ clientFile: `src/ai-features/${aiFeatureSlug}/api-client.ts`,
56
+ outputDir: path.join("src", "ai-features", aiFeatureSlug),
57
+ projectDir: workspace.projectDir,
58
+ typesFile: `src/ai-features/${aiFeatureSlug}/api-types.ts`,
59
+ validatorsFile: `src/ai-features/${aiFeatureSlug}/api-validators.ts`,
60
+ variables: {
61
+ namespace,
62
+ pascalCase,
63
+ slugKebabCase: aiFeatureSlug,
64
+ title: toTitleCase(aiFeatureSlug),
65
+ },
66
+ });
67
+ await syncAiFeatureSchemaArtifact({
68
+ aiSchemaFile: `src/ai-features/${aiFeatureSlug}/ai-schemas/feature-result.ai.schema.json`,
69
+ outputDir: path.join("src", "ai-features", aiFeatureSlug),
70
+ projectDir: workspace.projectDir,
71
+ });
72
+ await appendWorkspaceInventoryEntries(workspace.projectDir, {
73
+ aiFeatureEntries: [
74
+ buildAiFeatureConfigEntry(aiFeatureSlug, namespace),
75
+ ],
76
+ transformSource: ensureBlockConfigCanAddRestManifests,
77
+ });
78
+ return {
79
+ warnings: packageScriptChanges.addedProjectToolsDependency
80
+ ? [
81
+ "Added `@wp-typia/project-tools` to devDependencies for `sync-ai`. If this workspace was already installed, rerun your package manager install command before the first `wp-typia sync ai`.",
82
+ ]
83
+ : [],
84
+ };
85
+ },
86
+ });
91
87
  }
@@ -43,6 +43,8 @@ if ( ! defined( 'ABSPATH' ) ) {
43
43
  * - ${quotePhpString(adminNoticeMessageFilterHook)} filters the wp-admin notice shown when AI support is unavailable.
44
44
  * - ${quotePhpString(unavailableMessageFilterHook)} filters REST-facing unavailable messages by reason code.
45
45
  * - ${quotePhpString(telemetryFilterHook)} filters the response telemetry array before schema validation. Return a schema-compatible array.
46
+ *
47
+ * Compatibility note: this server-only endpoint avoids WordPress script-module enqueue APIs so older sites can load the generated feature file safely.
46
48
  */
47
49
 
48
50
  if ( ! function_exists( '${loadSchemaFunctionName}' ) ) {
@@ -0,0 +1,30 @@
1
+ export interface WorkspaceMutationPlan<TResult> {
2
+ /** Files to capture before the mutation starts. Missing files are restored as absent. */
3
+ filePaths: string[];
4
+ /** Snapshot directories created by the mutation, usually migration fixtures. */
5
+ snapshotDirs?: string[];
6
+ /** Created files or directories to remove if the mutation fails. */
7
+ targetPaths?: string[];
8
+ /** Mutating work to execute after the snapshot is captured. */
9
+ run: () => Promise<TResult>;
10
+ }
11
+ /**
12
+ * Error thrown when the mutation and its rollback both fail.
13
+ */
14
+ export declare class WorkspaceMutationRollbackError extends Error {
15
+ readonly mutationError: unknown;
16
+ readonly rollbackError: unknown;
17
+ constructor(mutationError: unknown, rollbackError: unknown);
18
+ }
19
+ /**
20
+ * Execute a workspace add mutation with rollback on any failure.
21
+ */
22
+ export declare function executeWorkspaceMutationPlan<TResult>({ filePaths, run, snapshotDirs, targetPaths, }: WorkspaceMutationPlan<TResult>): Promise<TResult>;
23
+ /**
24
+ * Insert a PHP snippet before the workspace textdomain hook or closing tag.
25
+ */
26
+ export declare function insertPhpSnippetBeforeWorkspaceAnchors(source: string, snippet: string): string;
27
+ /**
28
+ * Append a PHP snippet before the closing tag when one is present.
29
+ */
30
+ export declare function appendPhpSnippetBeforeClosingTag(source: string, snippet: string): string;
@@ -0,0 +1,60 @@
1
+ import { rollbackWorkspaceMutation, snapshotWorkspaceFiles, } from "./cli-add-shared.js";
2
+ const DEFAULT_PHP_SNIPPET_INSERTION_ANCHORS = [
3
+ /add_action\(\s*["']init["']\s*,\s*["'][^"']+_load_textdomain["']\s*\);\s*\n/u,
4
+ /\?>\s*$/u,
5
+ ];
6
+ /**
7
+ * Error thrown when the mutation and its rollback both fail.
8
+ */
9
+ export class WorkspaceMutationRollbackError extends Error {
10
+ constructor(mutationError, rollbackError) {
11
+ super("Workspace mutation failed and rollback also failed.");
12
+ this.name = "WorkspaceMutationRollbackError";
13
+ this.mutationError = mutationError;
14
+ this.rollbackError = rollbackError;
15
+ }
16
+ }
17
+ /**
18
+ * Execute a workspace add mutation with rollback on any failure.
19
+ */
20
+ export async function executeWorkspaceMutationPlan({ filePaths, run, snapshotDirs = [], targetPaths = [], }) {
21
+ const mutationSnapshot = {
22
+ fileSources: await snapshotWorkspaceFiles(filePaths),
23
+ snapshotDirs: [...snapshotDirs],
24
+ targetPaths: [...targetPaths],
25
+ };
26
+ try {
27
+ return await run();
28
+ }
29
+ catch (error) {
30
+ try {
31
+ await rollbackWorkspaceMutation(mutationSnapshot);
32
+ }
33
+ catch (rollbackError) {
34
+ throw new WorkspaceMutationRollbackError(error, rollbackError);
35
+ }
36
+ throw error;
37
+ }
38
+ }
39
+ /**
40
+ * Insert a PHP snippet before the workspace textdomain hook or closing tag.
41
+ */
42
+ export function insertPhpSnippetBeforeWorkspaceAnchors(source, snippet) {
43
+ for (const anchor of DEFAULT_PHP_SNIPPET_INSERTION_ANCHORS) {
44
+ const candidate = source.replace(anchor, (match) => `${snippet}\n${match}`);
45
+ if (candidate !== source) {
46
+ return candidate;
47
+ }
48
+ }
49
+ return `${source.trimEnd()}\n${snippet}\n`;
50
+ }
51
+ /**
52
+ * Append a PHP snippet before the closing tag when one is present.
53
+ */
54
+ export function appendPhpSnippetBeforeClosingTag(source, snippet) {
55
+ const closingTagPattern = /\?>\s*$/u;
56
+ if (closingTagPattern.test(source)) {
57
+ return source.replace(closingTagPattern, `${snippet}\n?>`);
58
+ }
59
+ return `${source.trimEnd()}\n${snippet}\n`;
60
+ }
@@ -5,6 +5,7 @@ import { resolveWorkspaceProject } from "./workspace-project.js";
5
5
  import { appendWorkspaceInventoryEntries, readWorkspaceInventory } from "./workspace-inventory.js";
6
6
  import { toKebabCase, toSnakeCase, toTitleCase } from "./string-case.js";
7
7
  import { assertBlockStyleDoesNotExist, assertBlockTransformDoesNotExist, assertValidGeneratedSlug, assertValidHookAnchor, assertValidHookedBlockPosition, assertVariationDoesNotExist, getMutableBlockHooks, normalizeBlockSlug, patchFile, quoteTsString, readWorkspaceBlockJson, resolveWorkspaceBlock, rollbackWorkspaceMutation, snapshotWorkspaceFiles, } from "./cli-add-shared.js";
8
+ import { findExecutablePatternMatch, hasExecutablePattern, hasUncommentedPattern, maskTypeScriptCommentsAndLiterals, } from "./ts-source-masking.js";
8
9
  const VARIATIONS_IMPORT_LINE = "import { registerWorkspaceVariations } from './variations';";
9
10
  const VARIATIONS_IMPORT_PATTERN = /^\s*import\s*\{\s*registerWorkspaceVariations\s*\}\s*from\s*["']\.\/variations["']\s*;?\s*$/mu;
10
11
  const VARIATIONS_CALL_LINE = "registerWorkspaceVariations();";
@@ -19,85 +20,6 @@ const BLOCK_TRANSFORMS_CALL_LINE = "applyWorkspaceBlockTransforms(registration.s
19
20
  const BLOCK_TRANSFORMS_CALL_PATTERN = /applyWorkspaceBlockTransforms\s*\(\s*registration\s*\.\s*settings\s*\)\s*;?/u;
20
21
  const SCAFFOLD_REGISTRATION_SETTINGS_CALL_PATTERN = /registerScaffoldBlockType\s*\(\s*registration\s*\.\s*name\s*,\s*registration\s*\.\s*settings\s*\)\s*;?/u;
21
22
  const FULL_BLOCK_NAME_PATTERN = /^[a-z0-9-]+\/[a-z0-9-]+$/u;
22
- function maskSourceSegment(segment) {
23
- return segment.replace(/[^\n\r]/gu, " ");
24
- }
25
- function maskTypeScriptComments(source) {
26
- return source
27
- .replace(/\/\*[\s\S]*?\*\//gu, maskSourceSegment)
28
- .replace(/\/\/[^\n\r]*/gu, maskSourceSegment);
29
- }
30
- // Preserve source offsets so executable-code match indexes still map to the original file.
31
- function maskTypeScriptCommentsAndLiterals(source) {
32
- let maskedSource = "";
33
- let index = 0;
34
- while (index < source.length) {
35
- const current = source[index];
36
- const next = source[index + 1];
37
- if (current === "/" && next === "/") {
38
- const start = index;
39
- index += 2;
40
- while (index < source.length &&
41
- source[index] !== "\n" &&
42
- source[index] !== "\r") {
43
- index += 1;
44
- }
45
- maskedSource += maskSourceSegment(source.slice(start, index));
46
- continue;
47
- }
48
- if (current === "/" && next === "*") {
49
- const start = index;
50
- index += 2;
51
- while (index < source.length &&
52
- !(source[index] === "*" && source[index + 1] === "/")) {
53
- index += 1;
54
- }
55
- index = Math.min(index + 2, source.length);
56
- maskedSource += maskSourceSegment(source.slice(start, index));
57
- continue;
58
- }
59
- if (current === "'" || current === '"' || current === "`") {
60
- const start = index;
61
- const quote = current;
62
- index += 1;
63
- while (index < source.length) {
64
- const char = source[index];
65
- if (char === "\\") {
66
- index += 2;
67
- continue;
68
- }
69
- index += 1;
70
- if (char === quote) {
71
- break;
72
- }
73
- }
74
- maskedSource += maskSourceSegment(source.slice(start, index));
75
- continue;
76
- }
77
- maskedSource += current;
78
- index += 1;
79
- }
80
- return maskedSource;
81
- }
82
- function hasExecutablePattern(source, pattern) {
83
- return pattern.test(maskTypeScriptCommentsAndLiterals(source));
84
- }
85
- function hasUncommentedPattern(source, pattern) {
86
- return pattern.test(maskTypeScriptComments(source));
87
- }
88
- function findExecutablePatternMatch(source, patterns) {
89
- const maskedSource = maskTypeScriptCommentsAndLiterals(source);
90
- for (const pattern of patterns) {
91
- const match = pattern.exec(maskedSource);
92
- if (match && match.index !== undefined) {
93
- return {
94
- end: match.index + match[0].length,
95
- start: match.index,
96
- };
97
- }
98
- }
99
- return undefined;
100
- }
101
23
  function isIdentifierBoundary(source, index) {
102
24
  if (index < 0 || index >= source.length) {
103
25
  return true;
@@ -3,11 +3,11 @@
3
3
  *
4
4
  * The canonical CLI surface stays stable here while the implementation lives
5
5
  * in focused internal modules:
6
- * - `cli-add-shared` for shared validation/help/rollback helpers
6
+ * - `cli-add-shared` as a compatibility barrel around focused add helpers
7
7
  * - `cli-add-block` for built-in block scaffolding
8
8
  * - `cli-add-workspace` for workspace mutation commands
9
9
  */
10
- export { ADD_BLOCK_TEMPLATE_IDS, ADD_KIND_IDS, EDITOR_PLUGIN_SLOT_IDS, formatAddHelpText, } from "./cli-add-shared.js";
10
+ export { ADD_BLOCK_TEMPLATE_IDS, ADD_KIND_IDS, EDITOR_PLUGIN_SLOT_IDS, formatAddHelpText, isAddBlockTemplateId, } from "./cli-add-shared.js";
11
11
  export type { AddBlockTemplateId, AddKindId, EditorPluginSlotId, } from "./cli-add-shared.js";
12
12
  export { runAddBlockCommand, seedWorkspaceMigrationProject, } from "./cli-add-block.js";
13
13
  export { runAddAdminViewCommand, runAddAbilityCommand, runAddAiFeatureCommand, runAddBindingSourceCommand, runAddBlockStyleCommand, runAddBlockTransformCommand, runAddEditorPluginCommand, runAddHookedBlockCommand, runAddPatternCommand, runAddRestResourceCommand, runAddVariationCommand, } from "./cli-add-workspace.js";