@wp-typia/project-tools 0.22.6 → 0.22.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/dist/runtime/block-targets.d.ts +40 -0
  2. package/dist/runtime/block-targets.js +71 -0
  3. package/dist/runtime/built-in-block-artifact-types.js +2 -1
  4. package/dist/runtime/built-in-block-attribute-specs.js +2 -1
  5. package/dist/runtime/built-in-block-code-artifacts.js +2 -0
  6. package/dist/runtime/built-in-block-non-ts-family-artifacts.js +12 -9
  7. package/dist/runtime/built-in-block-non-ts-render-utils.js +2 -0
  8. package/dist/runtime/cli-add-block-config.js +2 -1
  9. package/dist/runtime/cli-add-block-json.d.ts +2 -2
  10. package/dist/runtime/cli-add-block-json.js +5 -4
  11. package/dist/runtime/cli-add-block.js +4 -3
  12. package/dist/runtime/cli-add-workspace-ability-scaffold.js +21 -15
  13. package/dist/runtime/cli-add-workspace-ability.js +2 -2
  14. package/dist/runtime/cli-add-workspace-admin-view-scaffold.js +17 -13
  15. package/dist/runtime/cli-add-workspace-admin-view.js +2 -2
  16. package/dist/runtime/cli-add-workspace-ai.js +2 -2
  17. package/dist/runtime/cli-add-workspace-assets.js +42 -48
  18. package/dist/runtime/cli-add-workspace-rest.js +2 -2
  19. package/dist/runtime/cli-add-workspace.js +14 -49
  20. package/dist/runtime/cli-diagnostics.js +6 -0
  21. package/dist/runtime/cli-init-plan-presentation.d.ts +16 -0
  22. package/dist/runtime/cli-init-plan-presentation.js +74 -0
  23. package/dist/runtime/cli-init-plan.js +5 -77
  24. package/dist/runtime/cli-scaffold.js +2 -1
  25. package/dist/runtime/create-template-validation.d.ts +10 -0
  26. package/dist/runtime/create-template-validation.js +121 -0
  27. package/dist/runtime/package-versions.d.ts +1 -1
  28. package/dist/runtime/package-versions.js +16 -3
  29. package/dist/runtime/php-utils.js +151 -148
  30. package/dist/runtime/scaffold-answer-resolution.js +5 -108
  31. package/dist/runtime/scaffold-apply-utils.js +3 -2
  32. package/dist/runtime/scaffold-identifiers.js +4 -3
  33. package/dist/runtime/scaffold-template-assertions.d.ts +6 -0
  34. package/dist/runtime/scaffold-template-assertions.js +33 -0
  35. package/dist/runtime/scaffold-template-variable-groups.d.ts +2 -0
  36. package/dist/runtime/scaffold-template-variable-groups.js +7 -0
  37. package/dist/runtime/string-case.d.ts +2 -4
  38. package/dist/runtime/string-case.js +15 -4
  39. package/dist/runtime/template-source-cache-policy.d.ts +53 -0
  40. package/dist/runtime/template-source-cache-policy.js +135 -0
  41. package/dist/runtime/template-source-cache.d.ts +1 -45
  42. package/dist/runtime/template-source-cache.js +9 -152
  43. package/dist/runtime/ts-property-names.d.ts +11 -0
  44. package/dist/runtime/ts-property-names.js +16 -0
  45. package/dist/runtime/workspace-inventory.d.ts +13 -1
  46. package/dist/runtime/workspace-inventory.js +35 -9
  47. package/package.json +6 -1
@@ -1,15 +1,17 @@
1
- import fs from "node:fs";
2
1
  import { promises as fsp } from "node:fs";
3
2
  import path from "node:path";
4
3
  import { syncBlockMetadata, } from "@wp-typia/block-runtime/metadata-core";
5
4
  import ts from "typescript";
6
5
  import { resolveWorkspaceProject, } from "./workspace-project.js";
7
- import { readWorkspaceInventory, appendWorkspaceInventoryEntries, } from "./workspace-inventory.js";
6
+ import { appendWorkspaceInventoryEntries, readWorkspaceInventoryAsync, } from "./workspace-inventory.js";
8
7
  import { toPascalCase, toTitleCase } from "./string-case.js";
9
8
  import { findPhpFunctionRange, hasPhpFunctionDefinition, quotePhpString, replacePhpFunctionDefinition, } from "./php-utils.js";
10
9
  import { assertBindingSourceDoesNotExist, assertEditorPluginDoesNotExist, assertPatternDoesNotExist, assertValidEditorPluginSlot, assertValidGeneratedSlug, getWorkspaceBootstrapPath, normalizeBlockSlug, patchFile, quoteTsString, resolveWorkspaceBlock, rollbackWorkspaceMutation, snapshotWorkspaceFiles, } from "./cli-add-shared.js";
11
10
  import { appendPhpSnippetBeforeClosingTag, insertPhpSnippetBeforeWorkspaceAnchors, } from "./cli-add-workspace-mutation.js";
11
+ import { resolveWorkspaceBlockTargetName } from "./block-targets.js";
12
+ import { pathExists, readOptionalUtf8File } from "./fs-async.js";
12
13
  import { normalizeOptionalCliString } from "./cli-validation.js";
14
+ import { getPropertyNameText } from "./ts-property-names.js";
13
15
  const PATTERN_BOOTSTRAP_CATEGORY = "register_block_pattern_category";
14
16
  const BINDING_SOURCE_SERVER_GLOB = "/src/bindings/*/server.php";
15
17
  const BINDING_SOURCE_EDITOR_SCRIPT = "build/bindings/index.js";
@@ -48,26 +50,6 @@ function assertValidBindingAttributeName(attributeName) {
48
50
  }
49
51
  return trimmed;
50
52
  }
51
- function resolveBindingTargetBlockSlug(blockName, namespace) {
52
- const trimmed = blockName.trim();
53
- if (!trimmed) {
54
- throw new Error("`wp-typia add binding-source` requires --block <block-slug|namespace/block-slug> to include a value when --attribute is provided.");
55
- }
56
- const blockNameSegments = trimmed.split("/");
57
- if (blockNameSegments.length > 2) {
58
- throw new Error(`Binding target block "${trimmed}" must use <block-slug> or <namespace/block-slug> format.`);
59
- }
60
- if (blockNameSegments.some((segment) => segment.trim() === "")) {
61
- throw new Error(`Binding target block "${trimmed}" must use <block-slug> or <namespace/block-slug> format without empty path segments.`);
62
- }
63
- const [maybeNamespace, maybeSlug] = blockNameSegments.length === 2
64
- ? blockNameSegments
65
- : [undefined, blockNameSegments[0]];
66
- if (maybeNamespace && maybeNamespace !== namespace) {
67
- throw new Error(`Binding target block "${trimmed}" uses namespace "${maybeNamespace}". Expected "${namespace}".`);
68
- }
69
- return normalizeBlockSlug(maybeSlug ?? "");
70
- }
71
53
  function buildEditorPluginConfigEntry(editorPluginSlug, slot) {
72
54
  return [
73
55
  "\t{",
@@ -237,9 +219,15 @@ function resolveBindingTarget(options, namespace) {
237
219
  if (!hasBlock || !hasAttribute) {
238
220
  throw new Error("`wp-typia add binding-source` requires --block and --attribute to be provided together.");
239
221
  }
222
+ const targetBlock = resolveWorkspaceBlockTargetName(blockName ?? "", namespace, {
223
+ empty: () => "`wp-typia add binding-source` requires --block <block-slug|namespace/block-slug> to include a value when --attribute is provided.",
224
+ emptySegment: (input) => `Binding target block "${input}" must use <block-slug> or <namespace/block-slug> format without empty path segments.`,
225
+ invalidFormat: (input) => `Binding target block "${input}" must use <block-slug> or <namespace/block-slug> format.`,
226
+ namespaceMismatch: (input, actualNamespace, expectedNamespace) => `Binding target block "${input}" uses namespace "${actualNamespace}". Expected "${expectedNamespace}".`,
227
+ });
240
228
  return {
241
229
  attributeName: assertValidBindingAttributeName(attributeName ?? ""),
242
- blockSlug: resolveBindingTargetBlockSlug(blockName ?? "", namespace),
230
+ blockSlug: targetBlock.blockSlug,
243
231
  };
244
232
  }
245
233
  function formatBindingAttributeTypeMember(attributeName) {
@@ -266,12 +254,6 @@ function getInterfaceDeclaration(source, interfaceName) {
266
254
  visit(sourceFile);
267
255
  return declaration ? { declaration, sourceFile } : undefined;
268
256
  }
269
- function getPropertyNameText(name) {
270
- if (ts.isIdentifier(name) || ts.isStringLiteral(name) || ts.isNumericLiteral(name)) {
271
- return name.text;
272
- }
273
- return undefined;
274
- }
275
257
  function interfaceHasAttributeMember(declaration, attributeName) {
276
258
  return declaration.members.some((member) => ts.isPropertySignature(member) &&
277
259
  member.name !== undefined &&
@@ -722,41 +704,53 @@ async function ensureEditorPluginWebpackAnchors(workspace) {
722
704
  return nextSource;
723
705
  });
724
706
  }
725
- function resolveBindingSourceRegistryPath(projectDir) {
707
+ async function resolveBindingSourceRegistryPath(projectDir) {
726
708
  const bindingsDir = path.join(projectDir, "src", "bindings");
727
- return [path.join(bindingsDir, "index.ts"), path.join(bindingsDir, "index.js")].find((candidatePath) => fs.existsSync(candidatePath)) ?? path.join(bindingsDir, "index.ts");
709
+ for (const candidatePath of [
710
+ path.join(bindingsDir, "index.ts"),
711
+ path.join(bindingsDir, "index.js"),
712
+ ]) {
713
+ if (await pathExists(candidatePath)) {
714
+ return candidatePath;
715
+ }
716
+ }
717
+ return path.join(bindingsDir, "index.ts");
728
718
  }
729
719
  async function writeBindingSourceRegistry(projectDir, bindingSourceSlug) {
730
720
  const bindingsDir = path.join(projectDir, "src", "bindings");
731
- const bindingsIndexPath = resolveBindingSourceRegistryPath(projectDir);
721
+ const bindingsIndexPath = await resolveBindingSourceRegistryPath(projectDir);
732
722
  await fsp.mkdir(bindingsDir, { recursive: true });
733
- const existingBindingSourceSlugs = fs
734
- .readdirSync(bindingsDir, { withFileTypes: true })
723
+ const existingBindingSourceSlugs = (await fsp.readdir(bindingsDir, { withFileTypes: true }))
735
724
  .filter((entry) => entry.isDirectory())
736
725
  .map((entry) => entry.name);
737
726
  const nextBindingSourceSlugs = Array.from(new Set([...existingBindingSourceSlugs, bindingSourceSlug])).sort();
738
727
  await fsp.writeFile(bindingsIndexPath, buildBindingSourceIndexSource(nextBindingSourceSlugs), "utf8");
739
728
  }
740
- function resolveEditorPluginRegistryPath(projectDir) {
729
+ async function resolveEditorPluginRegistryPath(projectDir) {
741
730
  const editorPluginsDir = path.join(projectDir, "src", "editor-plugins");
742
- return [
731
+ for (const candidatePath of [
743
732
  path.join(editorPluginsDir, "index.ts"),
744
733
  path.join(editorPluginsDir, "index.js"),
745
- ].find((candidatePath) => fs.existsSync(candidatePath)) ?? path.join(editorPluginsDir, "index.ts");
734
+ ]) {
735
+ if (await pathExists(candidatePath)) {
736
+ return candidatePath;
737
+ }
738
+ }
739
+ return path.join(editorPluginsDir, "index.ts");
746
740
  }
747
- function readEditorPluginRegistrySlugs(registryPath) {
748
- if (!fs.existsSync(registryPath)) {
741
+ async function readEditorPluginRegistrySlugs(registryPath) {
742
+ const source = await readOptionalUtf8File(registryPath);
743
+ if (source === null) {
749
744
  return [];
750
745
  }
751
- const source = fs.readFileSync(registryPath, "utf8");
752
746
  return Array.from(source.matchAll(/^\s*import\s+['"]\.\/([^/'"]+)(?:\/index(?:\.[cm]?[jt]sx?)?)?['"];?\s*$/gmu)).map((match) => match[1]);
753
747
  }
754
748
  async function writeEditorPluginRegistry(projectDir, editorPluginSlug) {
755
749
  const editorPluginsDir = path.join(projectDir, "src", "editor-plugins");
756
- const registryPath = resolveEditorPluginRegistryPath(projectDir);
750
+ const registryPath = await resolveEditorPluginRegistryPath(projectDir);
757
751
  await fsp.mkdir(editorPluginsDir, { recursive: true });
758
- const existingEditorPluginSlugs = readWorkspaceInventory(projectDir).editorPlugins.map((entry) => entry.slug);
759
- const existingRegistrySlugs = readEditorPluginRegistrySlugs(registryPath);
752
+ const existingEditorPluginSlugs = (await readWorkspaceInventoryAsync(projectDir)).editorPlugins.map((entry) => entry.slug);
753
+ const existingRegistrySlugs = await readEditorPluginRegistrySlugs(registryPath);
760
754
  const nextEditorPluginSlugs = Array.from(new Set([...existingEditorPluginSlugs, ...existingRegistrySlugs, editorPluginSlug])).sort();
761
755
  await fsp.writeFile(registryPath, buildEditorPluginRegistrySource(nextEditorPluginSlugs), "utf8");
762
756
  }
@@ -779,12 +773,12 @@ export async function runAddEditorPluginCommand({ cwd = process.cwd(), editorPlu
779
773
  const workspace = resolveWorkspaceProject(cwd);
780
774
  const editorPluginSlug = assertValidGeneratedSlug("Editor plugin name", normalizeBlockSlug(editorPluginName), "wp-typia add editor-plugin <name> [--slot <sidebar|document-setting-panel>]");
781
775
  const resolvedSlot = assertValidEditorPluginSlot(slot);
782
- const inventory = readWorkspaceInventory(workspace.projectDir);
776
+ const inventory = await readWorkspaceInventoryAsync(workspace.projectDir);
783
777
  assertEditorPluginDoesNotExist(workspace.projectDir, editorPluginSlug, inventory);
784
778
  const blockConfigPath = path.join(workspace.projectDir, "scripts", "block-config.ts");
785
779
  const bootstrapPath = getWorkspaceBootstrapPath(workspace);
786
780
  const buildScriptPath = path.join(workspace.projectDir, "scripts", "build-workspace.mjs");
787
- const editorPluginsIndexPath = resolveEditorPluginRegistryPath(workspace.projectDir);
781
+ const editorPluginsIndexPath = await resolveEditorPluginRegistryPath(workspace.projectDir);
788
782
  const webpackConfigPath = path.join(workspace.projectDir, "webpack.config.js");
789
783
  const editorPluginDir = path.join(workspace.projectDir, "src", "editor-plugins", editorPluginSlug);
790
784
  const entryFilePath = path.join(editorPluginDir, "index.tsx");
@@ -848,7 +842,7 @@ export async function runAddEditorPluginCommand({ cwd = process.cwd(), editorPlu
848
842
  export async function runAddPatternCommand({ cwd = process.cwd(), patternName, }) {
849
843
  const workspace = resolveWorkspaceProject(cwd);
850
844
  const patternSlug = assertValidGeneratedSlug("Pattern name", normalizeBlockSlug(patternName), "wp-typia add pattern <name>");
851
- const inventory = readWorkspaceInventory(workspace.projectDir);
845
+ const inventory = await readWorkspaceInventoryAsync(workspace.projectDir);
852
846
  assertPatternDoesNotExist(workspace.projectDir, patternSlug, inventory);
853
847
  const blockConfigPath = path.join(workspace.projectDir, "scripts", "block-config.ts");
854
848
  const bootstrapPath = getWorkspaceBootstrapPath(workspace);
@@ -898,7 +892,7 @@ export async function runAddPatternCommand({ cwd = process.cwd(), patternName, }
898
892
  export async function runAddBindingSourceCommand({ attributeName, bindingSourceName, blockName, cwd = process.cwd(), }) {
899
893
  const workspace = resolveWorkspaceProject(cwd);
900
894
  const bindingSourceSlug = assertValidGeneratedSlug("Binding source name", normalizeBlockSlug(bindingSourceName), "wp-typia add binding-source <name> [--block <block-slug|namespace/block-slug> --attribute <attribute>]");
901
- const inventory = readWorkspaceInventory(workspace.projectDir);
895
+ const inventory = await readWorkspaceInventoryAsync(workspace.projectDir);
902
896
  assertBindingSourceDoesNotExist(workspace.projectDir, bindingSourceSlug, inventory);
903
897
  const target = resolveBindingTarget({
904
898
  attributeName,
@@ -907,7 +901,7 @@ export async function runAddBindingSourceCommand({ attributeName, bindingSourceN
907
901
  const targetBlock = target ? resolveWorkspaceBlock(inventory, target.blockSlug) : undefined;
908
902
  const blockConfigPath = path.join(workspace.projectDir, "scripts", "block-config.ts");
909
903
  const bootstrapPath = getWorkspaceBootstrapPath(workspace);
910
- const bindingsIndexPath = resolveBindingSourceRegistryPath(workspace.projectDir);
904
+ const bindingsIndexPath = await resolveBindingSourceRegistryPath(workspace.projectDir);
911
905
  const bindingSourceDir = path.join(workspace.projectDir, "src", "bindings", bindingSourceSlug);
912
906
  const serverFilePath = path.join(bindingSourceDir, "server.php");
913
907
  const editorFilePath = path.join(bindingSourceDir, "editor.ts");
@@ -7,7 +7,7 @@ import { buildRestResourceApiSource, buildRestResourceConfigEntry, buildRestReso
7
7
  import { quotePhpString } from "./php-utils.js";
8
8
  import { syncRestResourceArtifacts } from "./rest-resource-artifacts.js";
9
9
  import { toPascalCase, toTitleCase } from "./string-case.js";
10
- import { appendWorkspaceInventoryEntries, readWorkspaceInventory, } from "./workspace-inventory.js";
10
+ import { appendWorkspaceInventoryEntries, readWorkspaceInventoryAsync, } from "./workspace-inventory.js";
11
11
  import { resolveWorkspaceProject } from "./workspace-project.js";
12
12
  function buildRestResourceRouteRegistrations(restResourceSlug, methods, functions) {
13
13
  const collectionRoutes = [];
@@ -434,7 +434,7 @@ export async function runAddRestResourceCommand({ cwd = process.cwd(), methods,
434
434
  const restResourceSlug = assertValidGeneratedSlug("REST resource name", normalizeBlockSlug(restResourceName), "wp-typia add rest-resource <name> [--namespace <vendor/v1>] [--methods <list,read,create>]");
435
435
  const resolvedMethods = assertValidRestResourceMethods(methods);
436
436
  const resolvedNamespace = resolveRestResourceNamespace(workspace.workspace.namespace, namespace);
437
- const inventory = readWorkspaceInventory(workspace.projectDir);
437
+ const inventory = await readWorkspaceInventoryAsync(workspace.projectDir);
438
438
  assertRestResourceDoesNotExist(workspace.projectDir, restResourceSlug, inventory);
439
439
  const blockConfigPath = path.join(workspace.projectDir, "scripts", "block-config.ts");
440
440
  const bootstrapPath = getWorkspaceBootstrapPath(workspace);
@@ -1,8 +1,9 @@
1
- import fs from "node:fs";
2
1
  import { promises as fsp } from "node:fs";
3
2
  import path from "node:path";
3
+ import { pathExists } from "./fs-async.js";
4
+ import { assertFullBlockName, resolveWorkspaceTargetBlockName, } from "./block-targets.js";
4
5
  import { resolveWorkspaceProject } from "./workspace-project.js";
5
- import { appendWorkspaceInventoryEntries, readWorkspaceInventory } from "./workspace-inventory.js";
6
+ import { appendWorkspaceInventoryEntries, readWorkspaceInventoryAsync, } from "./workspace-inventory.js";
6
7
  import { toKebabCase, toSnakeCase, toTitleCase } from "./string-case.js";
7
8
  import { assertBlockStyleDoesNotExist, assertBlockTransformDoesNotExist, assertValidGeneratedSlug, assertValidHookAnchor, assertValidHookedBlockPosition, assertVariationDoesNotExist, getMutableBlockHooks, normalizeBlockSlug, patchFile, quoteTsString, readWorkspaceBlockJson, resolveWorkspaceBlock, rollbackWorkspaceMutation, snapshotWorkspaceFiles, } from "./cli-add-shared.js";
8
9
  import { findExecutablePatternMatch, hasExecutablePattern, hasUncommentedPattern, maskTypeScriptCommentsAndLiterals, } from "./ts-source-masking.js";
@@ -19,7 +20,6 @@ const BLOCK_TRANSFORMS_IMPORT_PATTERN = /^\s*import\s*\{\s*applyWorkspaceBlockTr
19
20
  const BLOCK_TRANSFORMS_CALL_LINE = "applyWorkspaceBlockTransforms(registration.settings);";
20
21
  const BLOCK_TRANSFORMS_CALL_PATTERN = /applyWorkspaceBlockTransforms\s*\(\s*registration\s*\.\s*settings\s*\)\s*;?/u;
21
22
  const SCAFFOLD_REGISTRATION_SETTINGS_CALL_PATTERN = /registerScaffoldBlockType\s*\(\s*registration\s*\.\s*name\s*,\s*registration\s*\.\s*settings\s*\)\s*;?/u;
22
- const FULL_BLOCK_NAME_PATTERN = /^[a-z0-9-]+\/[a-z0-9-]+$/u;
23
23
  function isIdentifierBoundary(source, index) {
24
24
  if (index < 0 || index >= source.length) {
25
25
  return true;
@@ -381,8 +381,7 @@ async function writeVariationRegistry(projectDir, blockSlug, variationSlug) {
381
381
  const variationsDir = path.join(projectDir, "src", "blocks", blockSlug, "variations");
382
382
  const variationsIndexPath = path.join(variationsDir, "index.ts");
383
383
  await fsp.mkdir(variationsDir, { recursive: true });
384
- const existingVariationSlugs = fs
385
- .readdirSync(variationsDir)
384
+ const existingVariationSlugs = (await fsp.readdir(variationsDir))
386
385
  .filter((entry) => entry.endsWith(".ts") && entry !== "index.ts")
387
386
  .map((entry) => entry.replace(/\.ts$/u, ""));
388
387
  const nextVariationSlugs = Array.from(new Set([...existingVariationSlugs, variationSlug])).sort();
@@ -392,8 +391,7 @@ async function writeBlockStyleRegistry(projectDir, blockSlug, styleSlug) {
392
391
  const stylesDir = path.join(projectDir, "src", "blocks", blockSlug, "styles");
393
392
  const stylesIndexPath = path.join(stylesDir, "index.ts");
394
393
  await fsp.mkdir(stylesDir, { recursive: true });
395
- const existingStyleSlugs = fs
396
- .readdirSync(stylesDir)
394
+ const existingStyleSlugs = (await fsp.readdir(stylesDir))
397
395
  .filter((entry) => entry.endsWith(".ts") && entry !== "index.ts")
398
396
  .map((entry) => entry.replace(/\.ts$/u, ""));
399
397
  const nextStyleSlugs = Array.from(new Set([...existingStyleSlugs, styleSlug])).sort();
@@ -403,45 +401,12 @@ async function writeBlockTransformRegistry(projectDir, blockSlug, transformSlug)
403
401
  const transformsDir = path.join(projectDir, "src", "blocks", blockSlug, "transforms");
404
402
  const transformsIndexPath = path.join(transformsDir, "index.ts");
405
403
  await fsp.mkdir(transformsDir, { recursive: true });
406
- const existingTransformSlugs = fs
407
- .readdirSync(transformsDir)
404
+ const existingTransformSlugs = (await fsp.readdir(transformsDir))
408
405
  .filter((entry) => entry.endsWith(".ts") && entry !== "index.ts")
409
406
  .map((entry) => entry.replace(/\.ts$/u, ""));
410
407
  const nextTransformSlugs = Array.from(new Set([...existingTransformSlugs, transformSlug])).sort();
411
408
  await fsp.writeFile(transformsIndexPath, buildBlockTransformIndexSource(nextTransformSlugs), "utf8");
412
409
  }
413
- function assertFullBlockName(blockName, flagName) {
414
- const trimmed = blockName.trim();
415
- if (!trimmed) {
416
- throw new Error(`\`${flagName}\` requires a block name.`);
417
- }
418
- if (!FULL_BLOCK_NAME_PATTERN.test(trimmed)) {
419
- throw new Error(`\`${flagName}\` must use <namespace/block-slug> format.`);
420
- }
421
- return trimmed;
422
- }
423
- function resolveWorkspaceTargetBlockName(blockName, namespace, flagName) {
424
- const trimmed = blockName.trim();
425
- if (!trimmed) {
426
- throw new Error(`\`${flagName}\` requires <block-slug|namespace/block-slug>.`);
427
- }
428
- const blockNameSegments = trimmed.split("/");
429
- if (blockNameSegments.length > 2 ||
430
- blockNameSegments.some((segment) => segment.trim() === "")) {
431
- throw new Error(`\`${flagName}\` must use <block-slug|namespace/block-slug> format.`);
432
- }
433
- const [maybeNamespace, maybeSlug] = blockNameSegments.length === 2
434
- ? blockNameSegments
435
- : [undefined, blockNameSegments[0]];
436
- if (maybeNamespace && maybeNamespace !== namespace) {
437
- throw new Error(`\`${flagName}\` references namespace "${maybeNamespace}". Expected "${namespace}".`);
438
- }
439
- const blockSlug = normalizeBlockSlug(maybeSlug ?? "");
440
- return {
441
- blockName: `${namespace}/${blockSlug}`,
442
- blockSlug,
443
- };
444
- }
445
410
  /**
446
411
  * Re-export the DataViews admin screen scaffold workflow from the focused
447
412
  * admin-view runtime helper module.
@@ -487,7 +452,7 @@ export async function runAddVariationCommand({ blockName, cwd = process.cwd(), v
487
452
  const workspace = resolveWorkspaceProject(cwd);
488
453
  const blockSlug = normalizeBlockSlug(blockName);
489
454
  const variationSlug = assertValidGeneratedSlug("Variation name", normalizeBlockSlug(variationName), "wp-typia add variation <name> --block <block-slug>");
490
- const inventory = readWorkspaceInventory(workspace.projectDir);
455
+ const inventory = await readWorkspaceInventoryAsync(workspace.projectDir);
491
456
  resolveWorkspaceBlock(inventory, blockSlug);
492
457
  assertVariationDoesNotExist(workspace.projectDir, blockSlug, variationSlug, inventory);
493
458
  const blockConfigPath = path.join(workspace.projectDir, "scripts", "block-config.ts");
@@ -495,7 +460,7 @@ export async function runAddVariationCommand({ blockName, cwd = process.cwd(), v
495
460
  const variationsDir = path.join(workspace.projectDir, "src", "blocks", blockSlug, "variations");
496
461
  const variationFilePath = path.join(variationsDir, `${variationSlug}.ts`);
497
462
  const variationsIndexPath = path.join(variationsDir, "index.ts");
498
- const shouldRemoveVariationsDirOnRollback = !fs.existsSync(variationsDir);
463
+ const shouldRemoveVariationsDirOnRollback = !(await pathExists(variationsDir));
499
464
  const mutationSnapshot = {
500
465
  fileSources: await snapshotWorkspaceFiles([
501
466
  blockConfigPath,
@@ -547,7 +512,7 @@ export async function runAddBlockStyleCommand({ blockName, cwd = process.cwd(),
547
512
  const workspace = resolveWorkspaceProject(cwd);
548
513
  const blockSlug = normalizeBlockSlug(blockName);
549
514
  const styleSlug = assertValidGeneratedSlug("Style name", normalizeBlockSlug(styleName), "wp-typia add style <name> --block <block-slug>");
550
- const inventory = readWorkspaceInventory(workspace.projectDir);
515
+ const inventory = await readWorkspaceInventoryAsync(workspace.projectDir);
551
516
  resolveWorkspaceBlock(inventory, blockSlug);
552
517
  assertBlockStyleDoesNotExist(workspace.projectDir, blockSlug, styleSlug, inventory);
553
518
  const blockConfigPath = path.join(workspace.projectDir, "scripts", "block-config.ts");
@@ -555,7 +520,7 @@ export async function runAddBlockStyleCommand({ blockName, cwd = process.cwd(),
555
520
  const stylesDir = path.join(workspace.projectDir, "src", "blocks", blockSlug, "styles");
556
521
  const styleFilePath = path.join(stylesDir, `${styleSlug}.ts`);
557
522
  const stylesIndexPath = path.join(stylesDir, "index.ts");
558
- const shouldRemoveStylesDirOnRollback = !fs.existsSync(stylesDir);
523
+ const shouldRemoveStylesDirOnRollback = !(await pathExists(stylesDir));
559
524
  const mutationSnapshot = {
560
525
  fileSources: await snapshotWorkspaceFiles([
561
526
  blockConfigPath,
@@ -616,7 +581,7 @@ export async function runAddBlockTransformCommand({ cwd = process.cwd(), fromBlo
616
581
  const transformSlug = assertValidGeneratedSlug("Transform name", normalizeBlockSlug(transformName), "wp-typia add transform <name> --from <namespace/block> --to <block-slug|namespace/block-slug>");
617
582
  const resolvedFromBlockName = assertFullBlockName(fromBlockName, "--from");
618
583
  const target = resolveWorkspaceTargetBlockName(toBlockName, workspace.workspace.namespace, "--to");
619
- const inventory = readWorkspaceInventory(workspace.projectDir);
584
+ const inventory = await readWorkspaceInventoryAsync(workspace.projectDir);
620
585
  resolveWorkspaceBlock(inventory, target.blockSlug);
621
586
  assertBlockTransformDoesNotExist(workspace.projectDir, target.blockSlug, transformSlug, inventory);
622
587
  const blockConfigPath = path.join(workspace.projectDir, "scripts", "block-config.ts");
@@ -624,7 +589,7 @@ export async function runAddBlockTransformCommand({ cwd = process.cwd(), fromBlo
624
589
  const transformsDir = path.join(workspace.projectDir, "src", "blocks", target.blockSlug, "transforms");
625
590
  const transformFilePath = path.join(transformsDir, `${transformSlug}.ts`);
626
591
  const transformsIndexPath = path.join(transformsDir, "index.ts");
627
- const shouldRemoveTransformsDirOnRollback = !fs.existsSync(transformsDir);
592
+ const shouldRemoveTransformsDirOnRollback = !(await pathExists(transformsDir));
628
593
  const mutationSnapshot = {
629
594
  fileSources: await snapshotWorkspaceFiles([
630
595
  blockConfigPath,
@@ -687,7 +652,7 @@ export async function runAddBlockTransformCommand({ cwd = process.cwd(), fromBlo
687
652
  export async function runAddHookedBlockCommand({ anchorBlockName, blockName, cwd = process.cwd(), position, }) {
688
653
  const workspace = resolveWorkspaceProject(cwd);
689
654
  const blockSlug = normalizeBlockSlug(blockName);
690
- const inventory = readWorkspaceInventory(workspace.projectDir);
655
+ const inventory = await readWorkspaceInventoryAsync(workspace.projectDir);
691
656
  resolveWorkspaceBlock(inventory, blockSlug);
692
657
  const resolvedAnchorBlockName = assertValidHookAnchor(anchorBlockName);
693
658
  const resolvedPosition = assertValidHookedBlockPosition(position);
@@ -695,7 +660,7 @@ export async function runAddHookedBlockCommand({ anchorBlockName, blockName, cwd
695
660
  if (resolvedAnchorBlockName === selfHookAnchor) {
696
661
  throw new Error("`wp-typia add hooked-block` cannot hook a block relative to its own block name.");
697
662
  }
698
- const { blockJson, blockJsonPath } = readWorkspaceBlockJson(workspace.projectDir, blockSlug);
663
+ const { blockJson, blockJsonPath } = await readWorkspaceBlockJson(workspace.projectDir, blockSlug);
699
664
  const blockJsonRelativePath = path.relative(workspace.projectDir, blockJsonPath);
700
665
  const blockHooks = getMutableBlockHooks(blockJson, blockJsonRelativePath);
701
666
  if (Object.prototype.hasOwnProperty.call(blockHooks, resolvedAnchorBlockName)) {
@@ -226,6 +226,12 @@ export function createCliDiagnosticCodeError(code, message, options) {
226
226
  error.code = code;
227
227
  return error;
228
228
  }
229
+ /**
230
+ * Compatibility-only fallback for legacy or third-party errors that have not
231
+ * yet been tagged by their throw site. New user-facing failures should pass an
232
+ * explicit code through `createCliDiagnosticCodeError()` or
233
+ * `createCliCommandError({ code })` instead of relying on message matching.
234
+ */
229
235
  function inferCliDiagnosticCode(options) {
230
236
  const inheritedCode = readCliDiagnosticCode(options.error);
231
237
  if (inheritedCode) {
@@ -0,0 +1,16 @@
1
+ import { type PackageManagerId } from "./package-managers.js";
2
+ import type { InitCommandMode, InitPlanLayoutKind, InitPlanStatus, RetrofitInitPlan } from "./cli-init-types.js";
3
+ export declare function buildInitPlanChangeSummary(changes: Pick<RetrofitInitPlan, "generatedArtifacts" | "packageChanges" | "plannedFiles">, options: {
4
+ includeGeneratedArtifacts: boolean;
5
+ }): string[];
6
+ export declare function buildInitPlanNextSteps(options: {
7
+ commandMode: InitCommandMode;
8
+ dependencyChangeCount: number;
9
+ hasPlannedChanges: boolean;
10
+ layoutKind: InitPlanLayoutKind;
11
+ packageManager: PackageManagerId;
12
+ }): string[];
13
+ export declare function buildRetrofitPlanSummary(options: {
14
+ commandMode: InitCommandMode;
15
+ status: InitPlanStatus;
16
+ }): string;
@@ -0,0 +1,74 @@
1
+ import { formatAddDevDependenciesCommand, formatPackageExecCommand, formatRunScript, } from "./package-managers.js";
2
+ import { buildRequiredDevDependencyMapEntries, getWpTypiaCliSpecifier, } from "./cli-init-package-json.js";
3
+ export function buildInitPlanChangeSummary(changes, options) {
4
+ const lines = [];
5
+ for (const dependencyChange of changes.packageChanges.addDevDependencies) {
6
+ lines.push(`devDependency ${dependencyChange.action} ${dependencyChange.name} -> ${dependencyChange.requiredValue}`);
7
+ }
8
+ if (changes.packageChanges.packageManagerField) {
9
+ lines.push(`packageManager ${changes.packageChanges.packageManagerField.action} -> ${changes.packageChanges.packageManagerField.requiredValue}`);
10
+ }
11
+ for (const scriptChange of changes.packageChanges.scripts) {
12
+ lines.push(`script ${scriptChange.action} ${scriptChange.name} -> ${scriptChange.requiredValue}`);
13
+ }
14
+ for (const filePlan of changes.plannedFiles) {
15
+ lines.push(`file ${filePlan.action} ${filePlan.path} (${filePlan.purpose})`);
16
+ }
17
+ if (options.includeGeneratedArtifacts) {
18
+ for (const artifactPath of changes.generatedArtifacts) {
19
+ lines.push(`generated artifact ${artifactPath}`);
20
+ }
21
+ }
22
+ return lines;
23
+ }
24
+ export function buildInitPlanNextSteps(options) {
25
+ const cliSpecifier = getWpTypiaCliSpecifier();
26
+ const syncTypesRun = formatRunScript(options.packageManager, "sync-types");
27
+ const syncRun = formatRunScript(options.packageManager, "sync");
28
+ const doctorRun = formatPackageExecCommand(options.packageManager, cliSpecifier, "doctor");
29
+ const migrationInitRun = formatPackageExecCommand(options.packageManager, cliSpecifier, "migrate init --current-migration-version v1");
30
+ const dependencyInstallCommand = formatAddDevDependenciesCommand(options.packageManager, buildRequiredDevDependencyMapEntries());
31
+ if (options.layoutKind === "unsupported") {
32
+ return [
33
+ "Align the project to one of the supported retrofit layouts listed below, then rerun `wp-typia init`.",
34
+ dependencyInstallCommand,
35
+ syncTypesRun,
36
+ doctorRun,
37
+ ];
38
+ }
39
+ if (options.commandMode === "apply") {
40
+ return [
41
+ ...(options.dependencyChangeCount > 0
42
+ ? [
43
+ "Install or reinstall project dependencies so the retrofit sync scripts and metadata generators are available locally.",
44
+ dependencyInstallCommand,
45
+ ]
46
+ : []),
47
+ syncRun,
48
+ doctorRun,
49
+ `Optional migration bootstrap: ${migrationInitRun}`,
50
+ ];
51
+ }
52
+ return [
53
+ ...(options.hasPlannedChanges
54
+ ? [
55
+ "Re-run `wp-typia init --apply` to write the planned package.json changes and helper files automatically.",
56
+ ...(options.dependencyChangeCount > 0 ? [dependencyInstallCommand] : []),
57
+ ]
58
+ : []),
59
+ syncRun,
60
+ doctorRun,
61
+ `Optional migration bootstrap: ${migrationInitRun}`,
62
+ ];
63
+ }
64
+ export function buildRetrofitPlanSummary(options) {
65
+ if (options.status === "already-initialized") {
66
+ return options.commandMode === "apply"
67
+ ? "This project already exposes the minimum wp-typia retrofit surface. No files were changed."
68
+ : "This project already exposes the minimum wp-typia retrofit surface.";
69
+ }
70
+ if (options.commandMode === "apply") {
71
+ return "Applied the minimum wp-typia retrofit surface so package.json and helper scripts are ready for the next install and sync run.";
72
+ }
73
+ return "This command previews the minimum wp-typia adoption layer for the current project without rewriting it into a full scaffold.";
74
+ }
@@ -3,9 +3,10 @@ import path from "node:path";
3
3
  import { analyzeSourceTypes } from "@wp-typia/block-runtime/metadata-parser";
4
4
  import ts from "typescript";
5
5
  import { discoverMigrationInitLayout } from "./migration-project.js";
6
- import { formatAddDevDependenciesCommand, formatPackageExecCommand, formatRunScript, } from "./package-managers.js";
6
+ import { formatPackageExecCommand, formatRunScript } from "./package-managers.js";
7
7
  import { toPascalCase } from "./string-case.js";
8
- import { buildDependencyChanges, buildPackageManagerFieldChange, buildRequiredDevDependencyMapEntries, buildScriptChanges, getWpTypiaCliSpecifier, hasExistingWpTypiaProjectSurface, readProjectPackageJson, resolveInitPackageManager, } from "./cli-init-package-json.js";
8
+ import { buildDependencyChanges, buildPackageManagerFieldChange, buildScriptChanges, getWpTypiaCliSpecifier, hasExistingWpTypiaProjectSurface, readProjectPackageJson, resolveInitPackageManager, } from "./cli-init-package-json.js";
9
+ import { buildInitPlanChangeSummary, buildInitPlanNextSteps, buildRetrofitPlanSummary, } from "./cli-init-plan-presentation.js";
9
10
  import { RETROFIT_APPLY_PREVIEW_NOTE, SUPPORTED_RETROFIT_LAYOUT_NOTE, } from "./cli-init-types.js";
10
11
  import { tryResolveWorkspaceProject } from "./workspace-project.js";
11
12
  function normalizeRelativePath(value) {
@@ -161,82 +162,9 @@ function buildPlannedFiles(projectDir, layoutKind) {
161
162
  },
162
163
  ];
163
164
  }
164
- function buildChangeSummary(changes, options) {
165
- const lines = [];
166
- for (const dependencyChange of changes.packageChanges.addDevDependencies) {
167
- lines.push(`devDependency ${dependencyChange.action} ${dependencyChange.name} -> ${dependencyChange.requiredValue}`);
168
- }
169
- if (changes.packageChanges.packageManagerField) {
170
- lines.push(`packageManager ${changes.packageChanges.packageManagerField.action} -> ${changes.packageChanges.packageManagerField.requiredValue}`);
171
- }
172
- for (const scriptChange of changes.packageChanges.scripts) {
173
- lines.push(`script ${scriptChange.action} ${scriptChange.name} -> ${scriptChange.requiredValue}`);
174
- }
175
- for (const filePlan of changes.plannedFiles) {
176
- lines.push(`file ${filePlan.action} ${filePlan.path} (${filePlan.purpose})`);
177
- }
178
- if (options.includeGeneratedArtifacts) {
179
- for (const artifactPath of changes.generatedArtifacts) {
180
- lines.push(`generated artifact ${artifactPath}`);
181
- }
182
- }
183
- return lines;
184
- }
185
- function buildNextSteps(options) {
186
- const cliSpecifier = getWpTypiaCliSpecifier();
187
- const syncTypesRun = formatRunScript(options.packageManager, "sync-types");
188
- const syncRun = formatRunScript(options.packageManager, "sync");
189
- const doctorRun = formatPackageExecCommand(options.packageManager, cliSpecifier, "doctor");
190
- const migrationInitRun = formatPackageExecCommand(options.packageManager, cliSpecifier, "migrate init --current-migration-version v1");
191
- const dependencyInstallCommand = formatAddDevDependenciesCommand(options.packageManager, buildRequiredDevDependencyMapEntries());
192
- if (options.layoutKind === "unsupported") {
193
- return [
194
- "Align the project to one of the supported retrofit layouts listed below, then rerun `wp-typia init`.",
195
- dependencyInstallCommand,
196
- syncTypesRun,
197
- doctorRun,
198
- ];
199
- }
200
- if (options.commandMode === "apply") {
201
- return [
202
- ...(options.dependencyChangeCount > 0
203
- ? [
204
- "Install or reinstall project dependencies so the retrofit sync scripts and metadata generators are available locally.",
205
- dependencyInstallCommand,
206
- ]
207
- : []),
208
- syncRun,
209
- doctorRun,
210
- `Optional migration bootstrap: ${migrationInitRun}`,
211
- ];
212
- }
213
- const steps = [
214
- ...(options.hasPlannedChanges
215
- ? [
216
- "Re-run `wp-typia init --apply` to write the planned package.json changes and helper files automatically.",
217
- ...(options.dependencyChangeCount > 0 ? [dependencyInstallCommand] : []),
218
- ]
219
- : []),
220
- syncRun,
221
- doctorRun,
222
- `Optional migration bootstrap: ${migrationInitRun}`,
223
- ];
224
- return steps;
225
- }
226
- function buildRetrofitPlanSummary(options) {
227
- if (options.status === "already-initialized") {
228
- return options.commandMode === "apply"
229
- ? "This project already exposes the minimum wp-typia retrofit surface. No files were changed."
230
- : "This project already exposes the minimum wp-typia retrofit surface.";
231
- }
232
- if (options.commandMode === "apply") {
233
- return "Applied the minimum wp-typia retrofit surface so package.json and helper scripts are ready for the next install and sync run.";
234
- }
235
- return "This command previews the minimum wp-typia adoption layer for the current project without rewriting it into a full scaffold.";
236
- }
237
165
  export function createRetrofitPlan(options) {
238
166
  const includeGeneratedArtifacts = options.commandMode === "preview-only";
239
- const plannedChanges = buildChangeSummary({
167
+ const plannedChanges = buildInitPlanChangeSummary({
240
168
  generatedArtifacts: options.generatedArtifacts,
241
169
  packageChanges: options.packageChanges,
242
170
  plannedFiles: options.plannedFiles,
@@ -249,7 +177,7 @@ export function createRetrofitPlan(options) {
249
177
  detectedLayout: options.detectedLayout,
250
178
  generatedArtifacts: options.generatedArtifacts,
251
179
  nextSteps: options.nextSteps ??
252
- buildNextSteps({
180
+ buildInitPlanNextSteps({
253
181
  commandMode: options.commandMode,
254
182
  dependencyChangeCount: options.packageChanges.addDevDependencies.length,
255
183
  hasPlannedChanges: plannedChanges.length > 0,
@@ -4,6 +4,7 @@ import path from "node:path";
4
4
  import { collectScaffoldAnswers, DATA_STORAGE_MODES, PERSISTENCE_POLICIES, isDataStorageMode, isPersistencePolicy, resolvePackageManagerId, resolveTemplateId, scaffoldProject, } from "./scaffold.js";
5
5
  import { parseAlternateRenderTargets } from "./alternate-render-targets.js";
6
6
  import { parseCompoundInnerBlocksPreset } from "./compound-inner-blocks.js";
7
+ import { isCompoundPersistenceEnabled } from "./scaffold-template-variable-groups.js";
7
8
  import { formatInstallCommand, formatRunScript, } from "./package-managers.js";
8
9
  import { getPrimaryDevelopmentScript } from "./local-dev-presets.js";
9
10
  import { createManagedTempRoot } from "./temp-roots.js";
@@ -444,7 +445,7 @@ export async function runScaffoldFlow({ projectInput, cwd = process.cwd(), templ
444
445
  availableScripts,
445
446
  packageManager: resolvedPackageManager,
446
447
  templateId: resolvedTemplateId,
447
- compoundPersistenceEnabled: resolvedResult.result.variables.compoundPersistenceEnabled === "true",
448
+ compoundPersistenceEnabled: isCompoundPersistenceEnabled(resolvedResult.result.variables),
448
449
  }),
449
450
  plan: resolvedResult.plan,
450
451
  projectDir,
@@ -0,0 +1,10 @@
1
+ export declare const CREATE_TEMPLATE_SELECTION_HINT: string;
2
+ /**
3
+ * Validate an explicitly supplied create template id before entering the full
4
+ * scaffold flow.
5
+ *
6
+ * Built-in template ids and the workspace alias resolve immediately, common
7
+ * built-in typos keep suggestion diagnostics, and explicit external template
8
+ * locators remain deferred to the external template resolver.
9
+ */
10
+ export declare function validateExplicitCreateTemplateId(templateId: string): string;