@wp-typia/project-tools 0.22.7 → 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 (41) 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.js +4 -3
  10. package/dist/runtime/cli-add-workspace-ability-scaffold.js +21 -15
  11. package/dist/runtime/cli-add-workspace-ability.js +2 -2
  12. package/dist/runtime/cli-add-workspace-admin-view-scaffold.js +17 -13
  13. package/dist/runtime/cli-add-workspace-admin-view.js +2 -2
  14. package/dist/runtime/cli-add-workspace-ai.js +2 -2
  15. package/dist/runtime/cli-add-workspace-assets.js +42 -48
  16. package/dist/runtime/cli-add-workspace-rest.js +2 -2
  17. package/dist/runtime/cli-add-workspace.js +6 -38
  18. package/dist/runtime/cli-diagnostics.js +6 -0
  19. package/dist/runtime/cli-scaffold.js +2 -1
  20. package/dist/runtime/create-template-validation.d.ts +10 -0
  21. package/dist/runtime/create-template-validation.js +121 -0
  22. package/dist/runtime/package-versions.js +15 -2
  23. package/dist/runtime/php-utils.js +66 -0
  24. package/dist/runtime/scaffold-answer-resolution.js +5 -108
  25. package/dist/runtime/scaffold-apply-utils.js +3 -2
  26. package/dist/runtime/scaffold-identifiers.js +4 -3
  27. package/dist/runtime/scaffold-template-assertions.d.ts +6 -0
  28. package/dist/runtime/scaffold-template-assertions.js +33 -0
  29. package/dist/runtime/scaffold-template-variable-groups.d.ts +2 -0
  30. package/dist/runtime/scaffold-template-variable-groups.js +7 -0
  31. package/dist/runtime/string-case.d.ts +2 -4
  32. package/dist/runtime/string-case.js +13 -4
  33. package/dist/runtime/template-source-cache-policy.d.ts +53 -0
  34. package/dist/runtime/template-source-cache-policy.js +135 -0
  35. package/dist/runtime/template-source-cache.d.ts +1 -45
  36. package/dist/runtime/template-source-cache.js +9 -152
  37. package/dist/runtime/ts-property-names.d.ts +11 -0
  38. package/dist/runtime/ts-property-names.js +16 -0
  39. package/dist/runtime/workspace-inventory.d.ts +13 -1
  40. package/dist/runtime/workspace-inventory.js +35 -9
  41. package/package.json +6 -1
@@ -0,0 +1,40 @@
1
+ export interface WorkspaceBlockTargetName {
2
+ blockName: string;
3
+ blockSlug: string;
4
+ }
5
+ export interface WorkspaceBlockTargetDiagnostics {
6
+ empty: () => string;
7
+ emptySegment: (input: string) => string;
8
+ invalidFormat: (input: string) => string;
9
+ namespaceMismatch: (input: string, actualNamespace: string, expectedNamespace: string) => string;
10
+ }
11
+ /**
12
+ * Validate a full `namespace/block-slug` block name.
13
+ *
14
+ * @param blockName Candidate block name.
15
+ * @param flagName CLI flag name used in diagnostics.
16
+ * @returns The trimmed full block name.
17
+ * @throws {Error} When the block name is empty or not a full block name.
18
+ */
19
+ export declare function assertFullBlockName(blockName: string, flagName: string): string;
20
+ /**
21
+ * Resolve a workspace block target from either `block-slug` or
22
+ * `namespace/block-slug` input while preserving caller-owned diagnostics.
23
+ *
24
+ * @param blockName Candidate block target.
25
+ * @param namespace Expected workspace namespace.
26
+ * @param diagnostics Error message builders for the caller's UX context.
27
+ * @returns The normalized workspace block target.
28
+ * @throws {Error} When the target is empty, malformed, or references another namespace.
29
+ */
30
+ export declare function resolveWorkspaceBlockTargetName(blockName: string, namespace: string, diagnostics: WorkspaceBlockTargetDiagnostics): WorkspaceBlockTargetName;
31
+ /**
32
+ * Resolve the standard `--to` style workspace block target used by add flows.
33
+ *
34
+ * @param blockName Candidate block target.
35
+ * @param namespace Expected workspace namespace.
36
+ * @param flagName CLI flag name used in diagnostics.
37
+ * @returns The normalized workspace block target.
38
+ * @throws {Error} When the target is empty, malformed, or references another namespace.
39
+ */
40
+ export declare function resolveWorkspaceTargetBlockName(blockName: string, namespace: string, flagName: string): WorkspaceBlockTargetName;
@@ -0,0 +1,71 @@
1
+ import { normalizeBlockSlug } from "./scaffold-identifiers.js";
2
+ const FULL_BLOCK_NAME_PATTERN = /^[a-z0-9-]+\/[a-z0-9-]+$/u;
3
+ /**
4
+ * Validate a full `namespace/block-slug` block name.
5
+ *
6
+ * @param blockName Candidate block name.
7
+ * @param flagName CLI flag name used in diagnostics.
8
+ * @returns The trimmed full block name.
9
+ * @throws {Error} When the block name is empty or not a full block name.
10
+ */
11
+ export function assertFullBlockName(blockName, flagName) {
12
+ const trimmed = blockName.trim();
13
+ if (!trimmed) {
14
+ throw new Error(`\`${flagName}\` requires a block name.`);
15
+ }
16
+ if (!FULL_BLOCK_NAME_PATTERN.test(trimmed)) {
17
+ throw new Error(`\`${flagName}\` must use <namespace/block-slug> format.`);
18
+ }
19
+ return trimmed;
20
+ }
21
+ /**
22
+ * Resolve a workspace block target from either `block-slug` or
23
+ * `namespace/block-slug` input while preserving caller-owned diagnostics.
24
+ *
25
+ * @param blockName Candidate block target.
26
+ * @param namespace Expected workspace namespace.
27
+ * @param diagnostics Error message builders for the caller's UX context.
28
+ * @returns The normalized workspace block target.
29
+ * @throws {Error} When the target is empty, malformed, or references another namespace.
30
+ */
31
+ export function resolveWorkspaceBlockTargetName(blockName, namespace, diagnostics) {
32
+ const trimmed = blockName.trim();
33
+ if (!trimmed) {
34
+ throw new Error(diagnostics.empty());
35
+ }
36
+ const blockNameSegments = trimmed.split("/");
37
+ if (blockNameSegments.length > 2) {
38
+ throw new Error(diagnostics.invalidFormat(trimmed));
39
+ }
40
+ if (blockNameSegments.some((segment) => segment.trim() === "")) {
41
+ throw new Error(diagnostics.emptySegment(trimmed));
42
+ }
43
+ const [maybeNamespace, maybeSlug] = blockNameSegments.length === 2
44
+ ? blockNameSegments
45
+ : [undefined, blockNameSegments[0]];
46
+ if (maybeNamespace && maybeNamespace !== namespace) {
47
+ throw new Error(diagnostics.namespaceMismatch(trimmed, maybeNamespace, namespace));
48
+ }
49
+ const blockSlug = normalizeBlockSlug(maybeSlug ?? "");
50
+ return {
51
+ blockName: `${namespace}/${blockSlug}`,
52
+ blockSlug,
53
+ };
54
+ }
55
+ /**
56
+ * Resolve the standard `--to` style workspace block target used by add flows.
57
+ *
58
+ * @param blockName Candidate block target.
59
+ * @param namespace Expected workspace namespace.
60
+ * @param flagName CLI flag name used in diagnostics.
61
+ * @returns The normalized workspace block target.
62
+ * @throws {Error} When the target is empty, malformed, or references another namespace.
63
+ */
64
+ export function resolveWorkspaceTargetBlockName(blockName, namespace, flagName) {
65
+ return resolveWorkspaceBlockTargetName(blockName, namespace, {
66
+ empty: () => `\`${flagName}\` requires <block-slug|namespace/block-slug>.`,
67
+ emptySegment: () => `\`${flagName}\` must use <block-slug|namespace/block-slug> format.`,
68
+ invalidFormat: () => `\`${flagName}\` must use <block-slug|namespace/block-slug> format.`,
69
+ namespaceMismatch: (_input, actualNamespace, expectedNamespace) => `\`${flagName}\` references namespace "${actualNamespace}". Expected "${expectedNamespace}".`,
70
+ });
71
+ }
@@ -1,3 +1,4 @@
1
+ import { isCompoundPersistenceEnabled } from "./scaffold-template-variable-groups.js";
1
2
  const STANDARD_PREAMBLE_LINES = [
2
3
  'import type { TextAlignment } from "@wp-typia/block-types/block-editor/alignment";',
3
4
  "import type {",
@@ -216,7 +217,7 @@ export function buildPersistenceTypesSource(variables, attributes) {
216
217
  * @returns TypeScript source for the compound parent runtime types.
217
218
  */
218
219
  export function buildCompoundTypesSource(variables, attributes) {
219
- const persistenceEnabled = variables.compoundPersistenceEnabled === "true";
220
+ const persistenceEnabled = isCompoundPersistenceEnabled(variables);
220
221
  return emitTypesModule({
221
222
  preambleLines: persistenceEnabled
222
223
  ? [...STANDARD_PREAMBLE_LINES]
@@ -1,3 +1,4 @@
1
+ import { isCompoundPersistenceEnabled } from "./scaffold-template-variable-groups.js";
1
2
  import { DEFAULT_COMPOUND_CHILD_BODY_PLACEHOLDER, buildAttributesFromSpecs, describe, } from "./built-in-block-attribute-emitters.js";
2
3
  const ALIGNMENT_VALUES = ["left", "center", "right"];
3
4
  const BASIC_ALIGNMENT_VALUES = ["left", "center", "right", "justify"];
@@ -334,7 +335,7 @@ export function buildPersistenceAttributes(variables) {
334
335
  * @returns Emitted attribute definitions for the compound parent artifact.
335
336
  */
336
337
  export function buildCompoundParentAttributes(variables) {
337
- return buildAttributesFromSpecs(variables.compoundPersistenceEnabled === "true"
338
+ return buildAttributesFromSpecs(isCompoundPersistenceEnabled(variables)
338
339
  ? [
339
340
  ...COMPOUND_PARENT_BASE_ATTRIBUTE_SPECS,
340
341
  ...COMPOUND_PARENT_PERSISTENCE_ATTRIBUTE_SPECS,
@@ -1,8 +1,10 @@
1
1
  import { buildBuiltInNonTsArtifacts } from "./built-in-block-non-ts-artifacts.js";
2
2
  import { BASIC_EDIT_TEMPLATE, BASIC_INDEX_TEMPLATE, BASIC_SAVE_TEMPLATE, BASIC_VALIDATORS_TEMPLATE, BLOCK_METADATA_WRAPPER_TEMPLATE, COMPOUND_CHILD_EDIT_TEMPLATE, COMPOUND_CHILD_INDEX_TEMPLATE, COMPOUND_CHILD_SAVE_TEMPLATE, COMPOUND_CHILD_VALIDATORS_TEMPLATE, COMPOUND_CHILDREN_TEMPLATE, COMPOUND_LOCAL_HOOKS_TEMPLATE, COMPOUND_PARENT_EDIT_TEMPLATE, COMPOUND_PARENT_INDEX_TEMPLATE, COMPOUND_PARENT_SAVE_TEMPLATE, COMPOUND_PARENT_VALIDATORS_TEMPLATE, COMPOUND_PERSISTENCE_PARENT_EDIT_TEMPLATE, COMPOUND_PERSISTENCE_PARENT_INTERACTIVITY_TEMPLATE, COMPOUND_PERSISTENCE_PARENT_SAVE_TEMPLATE, COMPOUND_PERSISTENCE_PARENT_VALIDATORS_TEMPLATE, INTERACTIVITY_EDIT_TEMPLATE, INTERACTIVITY_INDEX_TEMPLATE, INTERACTIVITY_SAVE_TEMPLATE, INTERACTIVITY_SCRIPT_TEMPLATE, INTERACTIVITY_STORE_TEMPLATE, INTERACTIVITY_VALIDATORS_TEMPLATE, MANIFEST_DEFAULTS_DOCUMENT_WRAPPER_TEMPLATE, MANIFEST_DOCUMENT_WRAPPER_TEMPLATE, PERSISTENCE_EDIT_TEMPLATE, PERSISTENCE_INDEX_TEMPLATE, PERSISTENCE_INTERACTIVITY_TEMPLATE, PERSISTENCE_SAVE_TEMPLATE, PERSISTENCE_VALIDATORS_TEMPLATE, QUERY_LOOP_INDEX_TEMPLATE, SHARED_HOOKS_TEMPLATE, } from "./built-in-block-code-templates.js";
3
3
  import { getScaffoldTemplateVariableGroups } from "./scaffold-template-variable-groups.js";
4
+ import { assertScaffoldTemplateCodeIdentifiers } from "./scaffold-template-assertions.js";
4
5
  import { renderMustacheTemplateString } from "./template-render.js";
5
6
  function renderCodeTemplate(template, variables) {
7
+ assertScaffoldTemplateCodeIdentifiers(variables);
6
8
  const rendered = renderMustacheTemplateString(template, variables);
7
9
  return rendered.endsWith("\n") ? rendered : `${rendered}\n`;
8
10
  }
@@ -1,4 +1,5 @@
1
1
  import { buildAlternateRenderEntryArtifact, renderArtifact, toPhpSingleQuotedString, } from "./built-in-block-non-ts-render-utils.js";
2
+ import { getScaffoldAlternateRenderTargets, isCompoundPersistenceEnabled, } from "./scaffold-template-variable-groups.js";
2
3
  const BASIC_STYLE_TEMPLATE = `/**
3
4
  * {{title}} Block Styles
4
5
  */
@@ -826,7 +827,8 @@ export function buildInteractivityArtifacts(variables) {
826
827
  * @returns The persistence SCSS and PHP artifacts for the selected render targets.
827
828
  */
828
829
  export function buildPersistenceArtifacts(variables) {
829
- if (variables.hasAlternateRenderTargets !== "true") {
830
+ const alternateRenderTargets = getScaffoldAlternateRenderTargets(variables);
831
+ if (!alternateRenderTargets.enabled) {
830
832
  return [
831
833
  renderArtifact("src/style.scss", PERSISTENCE_STYLE_TEMPLATE, variables),
832
834
  renderArtifact("src/render.php", PERSISTENCE_RENDER_TEMPLATE, variables),
@@ -837,13 +839,13 @@ export function buildPersistenceArtifacts(variables) {
837
839
  renderArtifact("src/render-targets.php", PERSISTENCE_RENDER_TARGETS_TEMPLATE, variables),
838
840
  buildAlternateRenderEntryArtifact("src/render.php", "web", variables),
839
841
  ];
840
- if (variables.hasAlternateEmailRenderTarget === "true") {
842
+ if (alternateRenderTargets.hasEmail) {
841
843
  artifacts.push(buildAlternateRenderEntryArtifact("src/render-email.php", "email", variables));
842
844
  }
843
- if (variables.hasAlternateMjmlRenderTarget === "true") {
845
+ if (alternateRenderTargets.hasMjml) {
844
846
  artifacts.push(buildAlternateRenderEntryArtifact("src/render-mjml.php", "mjml", variables));
845
847
  }
846
- if (variables.hasAlternatePlainTextRenderTarget === "true") {
848
+ if (alternateRenderTargets.hasPlainText) {
847
849
  artifacts.push(buildAlternateRenderEntryArtifact("src/render-text.php", "plain-text", variables));
848
850
  }
849
851
  return artifacts;
@@ -855,23 +857,24 @@ export function buildPersistenceArtifacts(variables) {
855
857
  * @returns The compound SCSS and PHP artifacts, including persistence variants when enabled.
856
858
  */
857
859
  export function buildCompoundArtifacts(variables) {
860
+ const alternateRenderTargets = getScaffoldAlternateRenderTargets(variables);
858
861
  const artifacts = [
859
862
  renderArtifact(`src/blocks/${variables.slugKebabCase}/style.scss`, COMPOUND_STYLE_TEMPLATE, variables),
860
863
  ];
861
- if (variables.compoundPersistenceEnabled === "true") {
864
+ if (isCompoundPersistenceEnabled(variables)) {
862
865
  const renderView = {
863
866
  ...variables,
864
867
  titlePhpLiteral: toPhpSingleQuotedString(variables.title),
865
868
  };
866
- if (variables.hasAlternateRenderTargets === "true") {
869
+ if (alternateRenderTargets.enabled) {
867
870
  artifacts.push(renderArtifact(`src/blocks/${variables.slugKebabCase}/render-targets.php`, COMPOUND_PERSISTENCE_RENDER_TARGETS_TEMPLATE, renderView), buildAlternateRenderEntryArtifact(`src/blocks/${variables.slugKebabCase}/render.php`, "web", variables));
868
- if (variables.hasAlternateEmailRenderTarget === "true") {
871
+ if (alternateRenderTargets.hasEmail) {
869
872
  artifacts.push(buildAlternateRenderEntryArtifact(`src/blocks/${variables.slugKebabCase}/render-email.php`, "email", variables));
870
873
  }
871
- if (variables.hasAlternateMjmlRenderTarget === "true") {
874
+ if (alternateRenderTargets.hasMjml) {
872
875
  artifacts.push(buildAlternateRenderEntryArtifact(`src/blocks/${variables.slugKebabCase}/render-mjml.php`, "mjml", variables));
873
876
  }
874
- if (variables.hasAlternatePlainTextRenderTarget === "true") {
877
+ if (alternateRenderTargets.hasPlainText) {
875
878
  artifacts.push(buildAlternateRenderEntryArtifact(`src/blocks/${variables.slugKebabCase}/render-text.php`, "plain-text", variables));
876
879
  }
877
880
  return artifacts;
@@ -1,3 +1,4 @@
1
+ import { assertScaffoldTemplateCodeIdentifiers } from "./scaffold-template-assertions.js";
1
2
  import { renderMustacheTemplateString } from "./template-render.js";
2
3
  /**
3
4
  * Renders a non-TypeScript artifact and normalizes the output to a trailing newline.
@@ -8,6 +9,7 @@ import { renderMustacheTemplateString } from "./template-render.js";
8
9
  * @returns A built-in code artifact with normalized source text.
9
10
  */
10
11
  export function renderArtifact(relativePath, template, view) {
12
+ assertScaffoldTemplateCodeIdentifiers(view);
11
13
  const source = renderMustacheTemplateString(template, view);
12
14
  return {
13
15
  relativePath,
@@ -1,5 +1,6 @@
1
1
  import path from "node:path";
2
2
  import { SHARED_WORKSPACE_TEMPLATE_ROOT, } from "./template-registry.js";
3
+ import { isCompoundPersistenceEnabled } from "./scaffold-template-variable-groups.js";
3
4
  import { quoteTsString, } from "./cli-add-shared.js";
4
5
  export function buildServerTemplateRoot(persistencePolicy) {
5
6
  return path.join(SHARED_WORKSPACE_TEMPLATE_ROOT, persistencePolicy === "public" ? "persistence-public" : "persistence-auth");
@@ -98,7 +99,7 @@ export function buildConfigEntries(templateId, variables) {
98
99
  if (templateId === "persistence") {
99
100
  return [buildPersistenceBlockConfigEntry(variables)];
100
101
  }
101
- if (variables.compoundPersistenceEnabled === "true") {
102
+ if (isCompoundPersistenceEnabled(variables)) {
102
103
  return [
103
104
  buildPersistenceBlockConfigEntry(variables),
104
105
  buildCompoundChildConfigEntry(variables),
@@ -17,6 +17,7 @@ import { parseTemplateLocator, resolveTemplateSeed, } from "./template-source.js
17
17
  import { resolveExternalTemplateLayers, } from "./template-layers.js";
18
18
  import { formatInstallCommand, } from "./package-managers.js";
19
19
  import { parseCompoundInnerBlocksPreset } from "./compound-inner-blocks.js";
20
+ import { isCompoundPersistenceEnabled } from "./scaffold-template-variable-groups.js";
20
21
  import { resolveOptionalInteractiveExternalLayerId, } from "./external-layer-selection.js";
21
22
  import { parseAlternateRenderTargets } from "./alternate-render-targets.js";
22
23
  import { assertExternalLayerCompositionOptions, normalizeOptionalCliString, resolveLocalCliPathOption, } from "./cli-validation.js";
@@ -84,7 +85,7 @@ async function copyScaffoldedBlockSlice(projectDir, templateId, tempProjectDir,
84
85
  await ensureCompoundWorkspaceSupportFiles(projectDir, tempProjectDir, legacyValidatorPaths);
85
86
  await copyTempDirectory(path.join(tempProjectDir, "src", "blocks", variables.slugKebabCase), path.join(projectDir, "src", "blocks", variables.slugKebabCase));
86
87
  await copyTempDirectory(path.join(tempProjectDir, "src", "blocks", `${variables.slugKebabCase}-item`), path.join(projectDir, "src", "blocks", `${variables.slugKebabCase}-item`));
87
- if (variables.compoundPersistenceEnabled === "true") {
88
+ if (isCompoundPersistenceEnabled(variables)) {
88
89
  await renderWorkspacePersistenceServerModule(projectDir, variables);
89
90
  }
90
91
  return;
@@ -196,7 +197,7 @@ async function syncWorkspaceAddedBlockArtifacts(projectDir, templateId, variable
196
197
  await syncWorkspaceBlockMetadata(projectDir, `${variables.slugKebabCase}-item`, `${variables.pascalCase}ItemAttributes`, path.join("src", "blocks", `${variables.slugKebabCase}-item`, "types.ts"));
197
198
  }
198
199
  if (templateId === "persistence" ||
199
- (templateId === "compound" && variables.compoundPersistenceEnabled === "true")) {
200
+ (templateId === "compound" && isCompoundPersistenceEnabled(variables))) {
200
201
  await syncWorkspacePersistenceArtifacts(projectDir, variables);
201
202
  }
202
203
  }
@@ -390,7 +391,7 @@ export async function runAddBlockCommand({ alternateRenderTargets, blockName, cw
390
391
  await addCollectionImportsForTemplate(workspace.projectDir, resolvedTemplateId, result.variables);
391
392
  await appendBlockConfigEntries(workspace.projectDir, buildConfigEntries(resolvedTemplateId, result.variables), resolvedTemplateId === "persistence" ||
392
393
  (resolvedTemplateId === "compound" &&
393
- result.variables.compoundPersistenceEnabled === "true"));
394
+ isCompoundPersistenceEnabled(result.variables)));
394
395
  await syncWorkspaceAddedBlockArtifacts(workspace.projectDir, resolvedTemplateId, result.variables);
395
396
  await updateWorkspaceMigrationConfigIfPresent(workspace.projectDir, buildMigrationBlocks(resolvedTemplateId, result.variables));
396
397
  return {
@@ -1,9 +1,9 @@
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 { syncTypeSchemas } from "@wp-typia/block-runtime/metadata-core";
5
4
  import semver from "semver";
6
- import { appendWorkspaceInventoryEntries, readWorkspaceInventory, } from "./workspace-inventory.js";
5
+ import { appendWorkspaceInventoryEntries, readWorkspaceInventoryAsync, } from "./workspace-inventory.js";
6
+ import { pathExists, readOptionalUtf8File } from "./fs-async.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
9
  import { getWorkspaceBootstrapPath, patchFile, } from "./cli-add-shared.js";
@@ -25,28 +25,34 @@ function resolveManagedDependencyVersion(existingVersion, requiredVersion) {
25
25
  ? existingVersion
26
26
  : requiredVersion;
27
27
  }
28
- function resolveAbilityRegistryPath(projectDir) {
28
+ async function resolveAbilityRegistryPath(projectDir) {
29
29
  const abilitiesDir = path.join(projectDir, "src", "abilities");
30
- return [path.join(abilitiesDir, "index.ts"), path.join(abilitiesDir, "index.js")].find((candidatePath) => fs.existsSync(candidatePath)) ?? path.join(abilitiesDir, "index.ts");
30
+ for (const candidatePath of [
31
+ path.join(abilitiesDir, "index.ts"),
32
+ path.join(abilitiesDir, "index.js"),
33
+ ]) {
34
+ if (await pathExists(candidatePath)) {
35
+ return candidatePath;
36
+ }
37
+ }
38
+ return path.join(abilitiesDir, "index.ts");
31
39
  }
32
- function readAbilityRegistrySlugs(registryPath) {
33
- if (!fs.existsSync(registryPath)) {
40
+ async function readAbilityRegistrySlugs(registryPath) {
41
+ const source = await readOptionalUtf8File(registryPath);
42
+ if (source === null) {
34
43
  return [];
35
44
  }
36
- const source = fs.readFileSync(registryPath, "utf8");
37
- return Array.from(source.matchAll(/^\s*export\s+\*\s+from\s+['"]\.\/([^/'"]+)\/client['"];?\s*$/gmu)).map((match) => match[1]);
45
+ return Array.from(source.matchAll(/^\s*export\s+\*\s+from\s+['"]\.\/([^/'"]+)\/client(?:\.[cm]?[jt]sx?)?['"];?\s*$/gmu)).map((match) => match[1]);
38
46
  }
39
47
  async function writeAbilityRegistry(projectDir, abilitySlug) {
40
48
  const abilitiesDir = path.join(projectDir, "src", "abilities");
41
- const registryPath = resolveAbilityRegistryPath(projectDir);
49
+ const registryPath = await resolveAbilityRegistryPath(projectDir);
42
50
  await fsp.mkdir(abilitiesDir, { recursive: true });
43
- const existingAbilitySlugs = readWorkspaceInventory(projectDir).abilities.map((entry) => entry.slug);
44
- const existingRegistrySlugs = readAbilityRegistrySlugs(registryPath);
51
+ const existingAbilitySlugs = (await readWorkspaceInventoryAsync(projectDir)).abilities.map((entry) => entry.slug);
52
+ const existingRegistrySlugs = await readAbilityRegistrySlugs(registryPath);
45
53
  const nextAbilitySlugs = Array.from(new Set([...existingAbilitySlugs, ...existingRegistrySlugs, abilitySlug])).sort();
46
54
  const generatedSection = buildAbilityRegistrySource(nextAbilitySlugs);
47
- const existingSource = fs.existsSync(registryPath)
48
- ? fs.readFileSync(registryPath, "utf8")
49
- : "";
55
+ const existingSource = (await readOptionalUtf8File(registryPath)) ?? "";
50
56
  const generatedSectionPattern = new RegExp(`${escapeRegex(ABILITY_REGISTRY_START_MARKER)}[\\s\\S]*?${escapeRegex(ABILITY_REGISTRY_END_MARKER)}\\n?`, "u");
51
57
  const nextSource = existingSource
52
58
  ? generatedSectionPattern.test(existingSource)
@@ -318,7 +324,7 @@ export async function scaffoldAbilityWorkspace({ abilitySlug, compatibilityPolic
318
324
  const syncAbilitiesScriptPath = path.join(workspace.projectDir, "scripts", "sync-abilities.ts");
319
325
  const syncProjectScriptPath = path.join(workspace.projectDir, "scripts", "sync-project.ts");
320
326
  const webpackConfigPath = path.join(workspace.projectDir, "webpack.config.js");
321
- const abilitiesIndexPath = resolveAbilityRegistryPath(workspace.projectDir);
327
+ const abilitiesIndexPath = await resolveAbilityRegistryPath(workspace.projectDir);
322
328
  const abilityDir = path.join(workspace.projectDir, "src", "abilities", abilitySlug);
323
329
  const configFilePath = path.join(abilityDir, "ability.config.json");
324
330
  const typesFilePath = path.join(abilityDir, "types.ts");
@@ -1,6 +1,6 @@
1
1
  import { assertAbilityDoesNotExist, assertValidGeneratedSlug, normalizeBlockSlug, } from "./cli-add-shared.js";
2
2
  import { scaffoldAbilityWorkspace } from "./cli-add-workspace-ability-scaffold.js";
3
- import { readWorkspaceInventory } from "./workspace-inventory.js";
3
+ import { readWorkspaceInventoryAsync } from "./workspace-inventory.js";
4
4
  import { resolveWorkspaceProject } from "./workspace-project.js";
5
5
  import { REQUIRED_WORKSPACE_ABILITY_COMPATIBILITY, resolveScaffoldCompatibilityPolicy, } from "./scaffold-compatibility.js";
6
6
  /**
@@ -12,7 +12,7 @@ import { REQUIRED_WORKSPACE_ABILITY_COMPATIBILITY, resolveScaffoldCompatibilityP
12
12
  export async function runAddAbilityCommand({ abilityName, cwd = process.cwd(), }) {
13
13
  const workspace = resolveWorkspaceProject(cwd);
14
14
  const abilitySlug = assertValidGeneratedSlug("Ability name", normalizeBlockSlug(abilityName), "wp-typia add ability <name>");
15
- const inventory = readWorkspaceInventory(workspace.projectDir);
15
+ const inventory = await readWorkspaceInventoryAsync(workspace.projectDir);
16
16
  assertAbilityDoesNotExist(workspace.projectDir, abilitySlug, inventory);
17
17
  const compatibilityPolicy = resolveScaffoldCompatibilityPolicy(REQUIRED_WORKSPACE_ABILITY_COMPATIBILITY);
18
18
  const scaffoldResult = await scaffoldAbilityWorkspace({
@@ -1,7 +1,7 @@
1
- import fs from 'node:fs';
2
1
  import { promises as fsp } from 'node:fs';
3
2
  import path from 'node:path';
4
- import { appendWorkspaceInventoryEntries, readWorkspaceInventory, } from './workspace-inventory.js';
3
+ import { pathExists, readOptionalUtf8File } from './fs-async.js';
4
+ import { appendWorkspaceInventoryEntries, readWorkspaceInventoryAsync, } 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
7
  import { getWorkspaceBootstrapPath, patchFile, } from './cli-add-shared.js';
@@ -152,27 +152,31 @@ async function ensureAdminViewWebpackAnchors(workspace) {
152
152
  return nextSource;
153
153
  });
154
154
  }
155
- function resolveAdminViewRegistryPath(projectDir) {
155
+ async function resolveAdminViewRegistryPath(projectDir) {
156
156
  const adminViewsDir = path.join(projectDir, 'src', 'admin-views');
157
- return ([
157
+ for (const candidatePath of [
158
158
  path.join(adminViewsDir, 'index.ts'),
159
159
  path.join(adminViewsDir, 'index.js'),
160
- ].find((candidatePath) => fs.existsSync(candidatePath)) ??
161
- path.join(adminViewsDir, 'index.ts'));
160
+ ]) {
161
+ if (await pathExists(candidatePath)) {
162
+ return candidatePath;
163
+ }
164
+ }
165
+ return path.join(adminViewsDir, 'index.ts');
162
166
  }
163
- function readAdminViewRegistrySlugs(registryPath) {
164
- if (!fs.existsSync(registryPath)) {
167
+ async function readAdminViewRegistrySlugs(registryPath) {
168
+ const source = await readOptionalUtf8File(registryPath);
169
+ if (source === null) {
165
170
  return [];
166
171
  }
167
- const source = fs.readFileSync(registryPath, 'utf8');
168
172
  return Array.from(source.matchAll(/^\s*import\s+['"]\.\/([^/'"]+)(?:\/index(?:\.[cm]?[jt]sx?)?)?['"];?\s*$/gmu)).map((match) => match[1]);
169
173
  }
170
174
  async function writeAdminViewRegistry(projectDir, adminViewSlug) {
171
175
  const adminViewsDir = path.join(projectDir, 'src', 'admin-views');
172
- const registryPath = resolveAdminViewRegistryPath(projectDir);
176
+ const registryPath = await resolveAdminViewRegistryPath(projectDir);
173
177
  await fsp.mkdir(adminViewsDir, { recursive: true });
174
- const existingAdminViewSlugs = readWorkspaceInventory(projectDir).adminViews.map((entry) => entry.slug);
175
- const existingRegistrySlugs = readAdminViewRegistrySlugs(registryPath);
178
+ const existingAdminViewSlugs = (await readWorkspaceInventoryAsync(projectDir)).adminViews.map((entry) => entry.slug);
179
+ const existingRegistrySlugs = await readAdminViewRegistrySlugs(registryPath);
176
180
  const nextAdminViewSlugs = Array.from(new Set([
177
181
  ...existingAdminViewSlugs,
178
182
  ...existingRegistrySlugs,
@@ -187,7 +191,7 @@ export async function scaffoldAdminViewWorkspace(options) {
187
191
  const buildScriptPath = path.join(workspace.projectDir, 'scripts', 'build-workspace.mjs');
188
192
  const packageJsonPath = path.join(workspace.projectDir, 'package.json');
189
193
  const webpackConfigPath = path.join(workspace.projectDir, 'webpack.config.js');
190
- const adminViewsIndexPath = resolveAdminViewRegistryPath(workspace.projectDir);
194
+ const adminViewsIndexPath = await resolveAdminViewRegistryPath(workspace.projectDir);
191
195
  const adminViewDir = path.join(workspace.projectDir, 'src', 'admin-views', adminViewSlug);
192
196
  const adminViewPhpPath = path.join(workspace.projectDir, 'inc', 'admin-views', `${adminViewSlug}.php`);
193
197
  await executeWorkspaceMutationPlan({
@@ -2,7 +2,7 @@ import { assertAdminViewDoesNotExist, assertValidGeneratedSlug, normalizeBlockSl
2
2
  import { assertAdminViewPackageAvailability, parseAdminViewSource, resolveAdminViewCoreDataSource, resolveRestResourceSource, } from './cli-add-workspace-admin-view-source.js';
3
3
  import { scaffoldAdminViewWorkspace } from './cli-add-workspace-admin-view-scaffold.js';
4
4
  import { formatAdminViewSourceLocator } from './cli-add-workspace-admin-view-types.js';
5
- import { readWorkspaceInventory } from './workspace-inventory.js';
5
+ import { readWorkspaceInventoryAsync } from './workspace-inventory.js';
6
6
  import { resolveWorkspaceProject } from './workspace-project.js';
7
7
  const ADD_ADMIN_VIEW_USAGE = 'wp-typia add admin-view <name> [--source <rest-resource:slug|core-data:kind/name>]';
8
8
  /**
@@ -14,7 +14,7 @@ export async function runAddAdminViewCommand({ adminViewName, cwd = process.cwd(
14
14
  assertAdminViewPackageAvailability();
15
15
  const adminViewSlug = assertValidGeneratedSlug('Admin view name', normalizeBlockSlug(adminViewName), ADD_ADMIN_VIEW_USAGE);
16
16
  const parsedSource = parseAdminViewSource(source);
17
- const inventory = readWorkspaceInventory(workspace.projectDir);
17
+ const inventory = await readWorkspaceInventoryAsync(workspace.projectDir);
18
18
  const restResource = resolveRestResourceSource(inventory.restResources, parsedSource);
19
19
  const coreDataSource = resolveAdminViewCoreDataSource(parsedSource);
20
20
  assertAdminViewDoesNotExist(workspace.projectDir, adminViewSlug, inventory);
@@ -1,6 +1,6 @@
1
1
  import { assertAiFeatureDoesNotExist, assertValidGeneratedSlug, normalizeBlockSlug, resolveRestResourceNamespace, } from "./cli-add-shared.js";
2
2
  import { scaffoldAiFeatureWorkspace } from "./cli-add-workspace-ai-scaffold.js";
3
- import { readWorkspaceInventory } from "./workspace-inventory.js";
3
+ import { readWorkspaceInventoryAsync } from "./workspace-inventory.js";
4
4
  import { resolveWorkspaceProject } from "./workspace-project.js";
5
5
  import { OPTIONAL_WORDPRESS_AI_CLIENT_COMPATIBILITY, resolveScaffoldCompatibilityPolicy, } from "./scaffold-compatibility.js";
6
6
  /**
@@ -15,7 +15,7 @@ export async function runAddAiFeatureCommand({ aiFeatureName, cwd = process.cwd(
15
15
  const aiFeatureSlug = assertValidGeneratedSlug("AI feature name", normalizeBlockSlug(aiFeatureName), "wp-typia add ai-feature <name> [--namespace <vendor/v1>]");
16
16
  const resolvedNamespace = resolveRestResourceNamespace(workspace.workspace.namespace, namespace);
17
17
  const compatibilityPolicy = resolveScaffoldCompatibilityPolicy(OPTIONAL_WORDPRESS_AI_CLIENT_COMPATIBILITY);
18
- const inventory = readWorkspaceInventory(workspace.projectDir);
18
+ const inventory = await readWorkspaceInventoryAsync(workspace.projectDir);
19
19
  assertAiFeatureDoesNotExist(workspace.projectDir, aiFeatureSlug, inventory);
20
20
  const scaffoldResult = await scaffoldAiFeatureWorkspace({
21
21
  aiFeatureSlug,