@wp-typia/project-tools 0.22.8 → 0.22.9

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.
@@ -9,7 +9,7 @@ import { copyInterpolatedDirectory, listInterpolatedDirectoryOutputs, } from "./
9
9
  import { appendWorkspaceInventoryEntries, } from "./workspace-inventory.js";
10
10
  import { createManagedTempRoot } from "./temp-roots.js";
11
11
  import { resolveWorkspaceProject, } from "./workspace-project.js";
12
- import { ADD_BLOCK_TEMPLATE_IDS, buildWorkspacePhpPrefix, isAddBlockTemplateId, patchFile, readOptionalFile, rollbackWorkspaceMutation, snapshotWorkspaceFiles, } from "./cli-add-shared.js";
12
+ import { ADD_BLOCK_TEMPLATE_IDS, buildWorkspacePhpPrefix, isAddBlockTemplateId, patchFile, readOptionalFile, rollbackWorkspaceMutation, snapshotWorkspaceFiles, suggestAddBlockTemplateId, } from "./cli-add-shared.js";
13
13
  import { resolveNonEmptyNormalizedBlockSlug, } from "./scaffold-identifiers.js";
14
14
  import { buildConfigEntries, buildMigrationBlocks, buildServerTemplateRoot, } from "./cli-add-block-config.js";
15
15
  import { COMPOUND_SHARED_SUPPORT_FILES, collectLegacyCompoundValidatorPaths, ensureBlockConfigCanAddRestManifests, ensureCompoundWorkspaceSupportFiles, } from "./cli-add-block-legacy-validator.js";
@@ -80,6 +80,13 @@ async function assertWorkspaceDependenciesInstalled(workspace) {
80
80
  }
81
81
  throw new Error(`Workspace dependencies have not been installed yet. Run \`${formatInstallCommand(workspace.packageManager)}\` from the workspace root before using \`wp-typia add block ...\`.`);
82
82
  }
83
+ function getMistypedAddBlockTemplateMessage(templateId) {
84
+ const suggestion = suggestAddBlockTemplateId(templateId);
85
+ if (!suggestion) {
86
+ return null;
87
+ }
88
+ return `Unknown add-block template "${templateId}". Did you mean "${suggestion}"? Use \`--template ${suggestion}\`, or run \`wp-typia templates list\` to inspect available templates.`;
89
+ }
83
90
  async function copyScaffoldedBlockSlice(projectDir, templateId, tempProjectDir, variables, legacyValidatorPaths = []) {
84
91
  if (templateId === "compound") {
85
92
  await ensureCompoundWorkspaceSupportFiles(projectDir, tempProjectDir, legacyValidatorPaths);
@@ -287,6 +294,10 @@ export async function runAddBlockCommand({ alternateRenderTargets, blockName, cw
287
294
  throw new Error("`wp-typia add block --template query-loop` is not supported. Query Loop is a create-time `core/query` variation scaffold, so use `wp-typia create <project-dir> --template query-loop` instead.");
288
295
  }
289
296
  if (!isAddBlockTemplateId(templateId)) {
297
+ const mistypedAddBlockTemplateMessage = getMistypedAddBlockTemplateMessage(templateId);
298
+ if (mistypedAddBlockTemplateMessage) {
299
+ throw new Error(mistypedAddBlockTemplateMessage);
300
+ }
290
301
  throw new Error(`Unknown add-block template "${templateId}". Expected one of: ${ADD_BLOCK_TEMPLATE_IDS.join(", ")}. Run \`wp-typia templates list\` to inspect available templates.`);
291
302
  }
292
303
  const resolvedTemplateId = templateId;
@@ -42,6 +42,14 @@ export declare const ADD_BLOCK_TEMPLATE_IDS: readonly ["basic", "interactivity",
42
42
  * Union of supported built-in block template ids.
43
43
  */
44
44
  export type AddBlockTemplateId = (typeof ADD_BLOCK_TEMPLATE_IDS)[number];
45
+ /**
46
+ * Suggest the closest supported add-block template id for typo diagnostics.
47
+ *
48
+ * @param templateId Raw `wp-typia add block --template` value.
49
+ * @returns The closest supported template id when it is within the shared
50
+ * close-id threshold, otherwise `null`.
51
+ */
52
+ export declare function suggestAddBlockTemplateId(templateId: string): AddBlockTemplateId | null;
45
53
  /**
46
54
  * Options for `wp-typia add variation`.
47
55
  *
@@ -1,3 +1,4 @@
1
+ import { suggestCloseId } from "./id-suggestions.js";
1
2
  export { ADD_KIND_IDS } from "./cli-add-kind-ids.js";
2
3
  /**
3
4
  * Supported plugin-level REST resource methods accepted by
@@ -46,3 +47,13 @@ export const ADD_BLOCK_TEMPLATE_IDS = [
46
47
  "persistence",
47
48
  "compound",
48
49
  ];
50
+ /**
51
+ * Suggest the closest supported add-block template id for typo diagnostics.
52
+ *
53
+ * @param templateId Raw `wp-typia add block --template` value.
54
+ * @returns The closest supported template id when it is within the shared
55
+ * close-id threshold, otherwise `null`.
56
+ */
57
+ export function suggestAddBlockTemplateId(templateId) {
58
+ return suggestCloseId(templateId, ADD_BLOCK_TEMPLATE_IDS);
59
+ }
@@ -7,8 +7,9 @@
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, isAddBlockTemplateId, } from "./cli-add-shared.js";
10
+ export { ADD_BLOCK_TEMPLATE_IDS, ADD_KIND_IDS, EDITOR_PLUGIN_SLOT_IDS, formatAddHelpText, isAddBlockTemplateId, suggestAddBlockTemplateId, } 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";
14
- export { getWorkspaceBlockSelectOptions } from "./workspace-inventory.js";
14
+ export { getWorkspaceBlockSelectOptions, getWorkspaceBlockSelectOptionsAsync, } from "./workspace-inventory.js";
15
+ export type { WorkspaceBlockSelectOption } from "./workspace-inventory.js";
@@ -7,7 +7,7 @@
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, isAddBlockTemplateId, } from "./cli-add-shared.js";
10
+ export { ADD_BLOCK_TEMPLATE_IDS, ADD_KIND_IDS, EDITOR_PLUGIN_SLOT_IDS, formatAddHelpText, isAddBlockTemplateId, suggestAddBlockTemplateId, } from "./cli-add-shared.js";
11
11
  export { runAddBlockCommand, seedWorkspaceMigrationProject, } from "./cli-add-block.js";
12
12
  export { runAddAdminViewCommand, runAddAbilityCommand, runAddAiFeatureCommand, runAddBindingSourceCommand, runAddBlockStyleCommand, runAddBlockTransformCommand, runAddEditorPluginCommand, runAddHookedBlockCommand, runAddPatternCommand, runAddRestResourceCommand, runAddVariationCommand, } from "./cli-add-workspace.js";
13
- export { getWorkspaceBlockSelectOptions } from "./workspace-inventory.js";
13
+ export { getWorkspaceBlockSelectOptions, getWorkspaceBlockSelectOptionsAsync, } from "./workspace-inventory.js";
@@ -11,7 +11,8 @@
11
11
  * `runAddAdminViewCommand` for DataViews-backed admin screen scaffolds,
12
12
  * `runAddAbilityCommand` for typed workflow ability scaffolds,
13
13
  * and `HOOKED_BLOCK_POSITION_IDS`,
14
- * `getWorkspaceBlockSelectOptions`, and `seedWorkspaceMigrationProject` for
14
+ * `getWorkspaceBlockSelectOptions`, `getWorkspaceBlockSelectOptionsAsync`, and
15
+ * `seedWorkspaceMigrationProject` for
15
16
  * explicit `wp-typia add` flows,
16
17
  * `runAddAiFeatureCommand` for server-owned WordPress AI feature scaffolds,
17
18
  * `runAddRestResourceCommand` for plugin-level REST resource scaffolds,
@@ -31,11 +32,12 @@
31
32
  export { getDoctorChecks, runDoctor, type DoctorCheck } from "./cli-doctor.js";
32
33
  export { createCliCommandError, createCliDiagnosticCodeError, CliDiagnosticError, CLI_DIAGNOSTIC_CODE_METADATA, CLI_DIAGNOSTIC_CODES, formatCliDiagnosticError, formatDoctorCheckLine, formatDoctorSummaryLine, getCliDiagnosticCodeMetadata, getDoctorFailureDetailLines, getFailingDoctorChecks, isCliDiagnosticError, } from "./cli-diagnostics.js";
33
34
  export type { CliDiagnosticCode, CliDiagnosticCodeError, CliDiagnosticMessage, } from "./cli-diagnostics.js";
34
- export { EDITOR_PLUGIN_SLOT_IDS, formatAddHelpText, getWorkspaceBlockSelectOptions, runAddAdminViewCommand, runAddAbilityCommand, runAddBindingSourceCommand, runAddAiFeatureCommand, runAddBlockCommand, runAddBlockStyleCommand, runAddBlockTransformCommand, runAddEditorPluginCommand, runAddHookedBlockCommand, runAddPatternCommand, runAddRestResourceCommand, runAddVariationCommand, seedWorkspaceMigrationProject, } from "./cli-add.js";
35
+ export { EDITOR_PLUGIN_SLOT_IDS, formatAddHelpText, getWorkspaceBlockSelectOptions, getWorkspaceBlockSelectOptionsAsync, runAddAdminViewCommand, runAddAbilityCommand, runAddBindingSourceCommand, runAddAiFeatureCommand, runAddBlockCommand, runAddBlockStyleCommand, runAddBlockTransformCommand, runAddEditorPluginCommand, runAddHookedBlockCommand, runAddPatternCommand, runAddRestResourceCommand, runAddVariationCommand, seedWorkspaceMigrationProject, } from "./cli-add.js";
35
36
  export { COMPOUND_INNER_BLOCKS_PRESET_IDS, getCompoundInnerBlocksPresetDefinition, } from "./compound-inner-blocks.js";
36
37
  export type { CompoundInnerBlocksPresetId } from "./compound-inner-blocks.js";
37
38
  export { HOOKED_BLOCK_POSITION_IDS } from "./hooked-blocks.js";
38
39
  export type { EditorPluginSlotId } from "./cli-add.js";
40
+ export type { WorkspaceBlockSelectOption } from "./workspace-inventory.js";
39
41
  export type { HookedBlockPositionId } from "./hooked-blocks.js";
40
42
  export { formatHelpText } from "./cli-help.js";
41
43
  export { getNextSteps, getOptionalOnboarding, runScaffoldFlow, } from "./cli-scaffold.js";
@@ -11,7 +11,8 @@
11
11
  * `runAddAdminViewCommand` for DataViews-backed admin screen scaffolds,
12
12
  * `runAddAbilityCommand` for typed workflow ability scaffolds,
13
13
  * and `HOOKED_BLOCK_POSITION_IDS`,
14
- * `getWorkspaceBlockSelectOptions`, and `seedWorkspaceMigrationProject` for
14
+ * `getWorkspaceBlockSelectOptions`, `getWorkspaceBlockSelectOptionsAsync`, and
15
+ * `seedWorkspaceMigrationProject` for
15
16
  * explicit `wp-typia add` flows,
16
17
  * `runAddAiFeatureCommand` for server-owned WordPress AI feature scaffolds,
17
18
  * `runAddRestResourceCommand` for plugin-level REST resource scaffolds,
@@ -30,7 +31,7 @@
30
31
  */
31
32
  export { getDoctorChecks, runDoctor } from "./cli-doctor.js";
32
33
  export { createCliCommandError, createCliDiagnosticCodeError, CliDiagnosticError, CLI_DIAGNOSTIC_CODE_METADATA, CLI_DIAGNOSTIC_CODES, formatCliDiagnosticError, formatDoctorCheckLine, formatDoctorSummaryLine, getCliDiagnosticCodeMetadata, getDoctorFailureDetailLines, getFailingDoctorChecks, isCliDiagnosticError, } from "./cli-diagnostics.js";
33
- export { EDITOR_PLUGIN_SLOT_IDS, formatAddHelpText, getWorkspaceBlockSelectOptions, runAddAdminViewCommand, runAddAbilityCommand, runAddBindingSourceCommand, runAddAiFeatureCommand, runAddBlockCommand, runAddBlockStyleCommand, runAddBlockTransformCommand, runAddEditorPluginCommand, runAddHookedBlockCommand, runAddPatternCommand, runAddRestResourceCommand, runAddVariationCommand, seedWorkspaceMigrationProject, } from "./cli-add.js";
34
+ export { EDITOR_PLUGIN_SLOT_IDS, formatAddHelpText, getWorkspaceBlockSelectOptions, getWorkspaceBlockSelectOptionsAsync, runAddAdminViewCommand, runAddAbilityCommand, runAddBindingSourceCommand, runAddAiFeatureCommand, runAddBlockCommand, runAddBlockStyleCommand, runAddBlockTransformCommand, runAddEditorPluginCommand, runAddHookedBlockCommand, runAddPatternCommand, runAddRestResourceCommand, runAddVariationCommand, seedWorkspaceMigrationProject, } from "./cli-add.js";
34
35
  export { COMPOUND_INNER_BLOCKS_PRESET_IDS, getCompoundInnerBlocksPresetDefinition, } from "./compound-inner-blocks.js";
35
36
  export { HOOKED_BLOCK_POSITION_IDS } from "./hooked-blocks.js";
36
37
  export { formatHelpText } from "./cli-help.js";
@@ -70,6 +70,8 @@ export function getWorkspaceDoctorChecks(cwd) {
70
70
  }
71
71
  checks.push(getWorkspacePackageMetadataCheck(workspace, workspacePackageJson));
72
72
  try {
73
+ // Doctor checks expose a synchronous API so callers can collect a stable
74
+ // snapshot without mixing async inventory reads into check aggregation.
73
75
  const inventory = readWorkspaceInventory(workspace.projectDir);
74
76
  checks.push(createDoctorCheck("Workspace inventory", "pass", formatWorkspaceInventorySummary(inventory)));
75
77
  checks.push(...getWorkspaceBlockDoctorChecks(workspace, inventory));
@@ -1,4 +1,3 @@
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 { collectScaffoldAnswers, DATA_STORAGE_MODES, PERSISTENCE_POLICIES, isDataStorageMode, isPersistencePolicy, resolvePackageManagerId, resolveTemplateId, scaffoldProject, } from "./scaffold.js";
@@ -10,6 +9,7 @@ import { getPrimaryDevelopmentScript } from "./local-dev-presets.js";
10
9
  import { createManagedTempRoot } from "./temp-roots.js";
11
10
  import { getOptionalOnboardingNote, getOptionalOnboardingShortNote, getOptionalOnboardingSteps, } from "./scaffold-onboarding.js";
12
11
  import { formatNonEmptyTargetDirectoryError } from "./scaffold-bootstrap.js";
12
+ import { pathExists } from "./fs-async.js";
13
13
  import { OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE, isBuiltInTemplateId, } from "./template-registry.js";
14
14
  import { resolveOptionalInteractiveExternalLayerId, } from "./external-layer-selection.js";
15
15
  import { assertBuiltInTemplateVariantAllowed, resolveLocalCliPathOption, normalizeOptionalCliString, } from "./cli-validation.js";
@@ -32,7 +32,7 @@ async function listRelativeProjectFiles(rootDir) {
32
32
  return relativeFiles.sort((left, right) => left.localeCompare(right));
33
33
  }
34
34
  async function assertDryRunTargetDirectoryReady(projectDir, allowExistingDir) {
35
- if (!fs.existsSync(projectDir) || allowExistingDir) {
35
+ if (!(await pathExists(projectDir)) || allowExistingDir) {
36
36
  return;
37
37
  }
38
38
  const entries = await fsp.readdir(projectDir);
@@ -425,7 +425,7 @@ export async function runScaffoldFlow({ projectInput, cwd = process.cwd(), templ
425
425
  let availableScripts;
426
426
  if (!dryRun) {
427
427
  try {
428
- const parsedPackageJson = JSON.parse(fs.readFileSync(path.join(projectDir, "package.json"), "utf8"));
428
+ const parsedPackageJson = JSON.parse(await fsp.readFile(path.join(projectDir, "package.json"), "utf8"));
429
429
  const scripts = parsedPackageJson.scripts &&
430
430
  typeof parsedPackageJson.scripts === "object" &&
431
431
  !Array.isArray(parsedPackageJson.scripts)
@@ -3,6 +3,7 @@ import { CLI_DIAGNOSTIC_CODES, createCliDiagnosticCodeError, } from "./cli-diagn
3
3
  import { OFFICIAL_WORKSPACE_TEMPLATE_ALIAS, OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE, TEMPLATE_IDS, getTemplateById, isBuiltInTemplateId, } from "./template-registry.js";
4
4
  import { getRemovedBuiltInTemplateMessage, isRemovedBuiltInTemplateId, } from "./template-defaults.js";
5
5
  import { parseNpmTemplateLocator } from "./template-source-locators.js";
6
+ import { suggestCloseId } from "./id-suggestions.js";
6
7
  export const CREATE_TEMPLATE_SELECTION_HINT = `--template <${[
7
8
  ...TEMPLATE_IDS,
8
9
  OFFICIAL_WORKSPACE_TEMPLATE_ALIAS,
@@ -39,40 +40,13 @@ function looksLikeExplicitCreateExternalTemplateLocator(templateId) {
39
40
  return (looksLikeExplicitNonNpmExternalTemplateLocator(templateId) ||
40
41
  parseNpmTemplateLocator(templateId) !== null);
41
42
  }
42
- function getEditDistance(left, right) {
43
- const previous = Array.from({ length: right.length + 1 }, (_, index) => index);
44
- const current = new Array(right.length + 1);
45
- for (let leftIndex = 0; leftIndex < left.length; leftIndex += 1) {
46
- current[0] = leftIndex + 1;
47
- for (let rightIndex = 0; rightIndex < right.length; rightIndex += 1) {
48
- const substitutionCost = left[leftIndex] === right[rightIndex] ? 0 : 1;
49
- current[rightIndex + 1] = Math.min(current[rightIndex] + 1, previous[rightIndex + 1] + 1, previous[rightIndex] + substitutionCost);
50
- }
51
- for (let index = 0; index < current.length; index += 1) {
52
- previous[index] = current[index];
53
- }
54
- }
55
- return previous[right.length];
56
- }
57
43
  function findMistypedBuiltInTemplateSuggestion(templateId) {
58
44
  const normalizedTemplateId = templateId.trim().toLowerCase();
59
45
  if (normalizedTemplateId.length === 0 ||
60
46
  looksLikeExplicitNonNpmExternalTemplateLocator(normalizedTemplateId)) {
61
47
  return null;
62
48
  }
63
- let bestCandidate = null;
64
- for (const candidateId of TEMPLATE_SUGGESTION_IDS) {
65
- const distance = getEditDistance(normalizedTemplateId, candidateId);
66
- if (bestCandidate === null || distance < bestCandidate.distance) {
67
- bestCandidate = {
68
- distance,
69
- id: candidateId,
70
- };
71
- }
72
- }
73
- return bestCandidate && bestCandidate.distance <= 2
74
- ? bestCandidate.id
75
- : null;
49
+ return suggestCloseId(normalizedTemplateId, TEMPLATE_SUGGESTION_IDS);
76
50
  }
77
51
  function getMistypedBuiltInTemplateMessage(templateId) {
78
52
  const suggestion = findMistypedBuiltInTemplateSuggestion(templateId);
@@ -0,0 +1,21 @@
1
+ export interface SuggestCloseIdOptions {
2
+ /**
3
+ * Maximum edit distance accepted for a suggestion.
4
+ *
5
+ * Defaults to `2`, matching the create-template typo guard.
6
+ */
7
+ maxDistance?: number;
8
+ /**
9
+ * Normalizes user input and candidates before comparing them.
10
+ *
11
+ * Defaults to trimming and lowercasing for CLI id-like values.
12
+ */
13
+ normalize?: (value: string) => string;
14
+ }
15
+ /**
16
+ * Suggest the closest known id for a user-provided CLI value.
17
+ *
18
+ * This helper is intentionally generic so command-specific validation can keep
19
+ * its own wording and special-case handling while sharing typo thresholds.
20
+ */
21
+ export declare function suggestCloseId<const TCandidate extends string>(input: string, candidates: readonly TCandidate[], options?: SuggestCloseIdOptions): TCandidate | null;
@@ -0,0 +1,48 @@
1
+ function normalizeCloseId(value) {
2
+ return value.trim().toLowerCase();
3
+ }
4
+ function getEditDistance(left, right) {
5
+ const previous = Array.from({ length: right.length + 1 }, (_, index) => index);
6
+ const current = new Array(right.length + 1);
7
+ for (let leftIndex = 0; leftIndex < left.length; leftIndex += 1) {
8
+ current[0] = leftIndex + 1;
9
+ for (let rightIndex = 0; rightIndex < right.length; rightIndex += 1) {
10
+ const substitutionCost = left[leftIndex] === right[rightIndex] ? 0 : 1;
11
+ current[rightIndex + 1] = Math.min(current[rightIndex] + 1, previous[rightIndex + 1] + 1, previous[rightIndex] + substitutionCost);
12
+ }
13
+ for (let index = 0; index < current.length; index += 1) {
14
+ previous[index] = current[index];
15
+ }
16
+ }
17
+ return previous[right.length];
18
+ }
19
+ /**
20
+ * Suggest the closest known id for a user-provided CLI value.
21
+ *
22
+ * This helper is intentionally generic so command-specific validation can keep
23
+ * its own wording and special-case handling while sharing typo thresholds.
24
+ */
25
+ export function suggestCloseId(input, candidates, options = {}) {
26
+ const normalize = options.normalize ?? normalizeCloseId;
27
+ const normalizedInput = normalize(input);
28
+ if (normalizedInput.length === 0) {
29
+ return null;
30
+ }
31
+ const maxDistance = options.maxDistance ?? 2;
32
+ if (maxDistance < 0) {
33
+ return null;
34
+ }
35
+ let bestCandidate = null;
36
+ for (const candidateId of candidates) {
37
+ const distance = getEditDistance(normalizedInput, normalize(candidateId));
38
+ if (bestCandidate === null || distance < bestCandidate.distance) {
39
+ bestCandidate = {
40
+ distance,
41
+ id: candidateId,
42
+ };
43
+ }
44
+ }
45
+ return bestCandidate && bestCandidate.distance <= maxDistance
46
+ ? bestCandidate.id
47
+ : null;
48
+ }
@@ -5,6 +5,7 @@
5
5
  * CLI while keeping reusable project logic out of the CLI package itself.
6
6
  * Consumers should prefer these exports for scaffold, add, migrate, doctor,
7
7
  * and workspace-aware helpers such as `getWorkspaceBlockSelectOptions`,
8
+ * `getWorkspaceBlockSelectOptionsAsync`,
8
9
  * `runAddBlockCommand`, `runAddBlockStyleCommand`,
9
10
  * `runAddBlockTransformCommand`, `runAddVariationCommand`,
10
11
  * `runAddPatternCommand`, `runAddBindingSourceCommand`,
@@ -29,11 +30,13 @@ export { manifestAttributeToJsonSchema, projectJsonSchemaDocument, manifestToJso
29
30
  export { buildCompoundChildStarterManifestDocument, getStarterManifestFiles, stringifyStarterManifest, } from "./starter-manifests.js";
30
31
  export type { EndpointAuthIntent, EndpointOpenApiAuthMode, EndpointOpenApiContractDocument, EndpointOpenApiDocumentOptions, EndpointOpenApiEndpointDefinition, EndpointOpenApiMethod, EndpointWordPressAuthDefinition, EndpointWordPressAuthMechanism, JsonSchemaDocument, JsonSchemaProjectionProfile, JsonSchemaObject, NormalizedEndpointAuthDefinition, OpenApiDocument, OpenApiInfo, OpenApiOperation, OpenApiParameter, OpenApiPathItem, OpenApiSecurityScheme, } from "./schema-core.js";
31
32
  export { PACKAGE_MANAGER_IDS, PACKAGE_MANAGERS, formatPackageExecCommand, formatInstallCommand, formatRunScript, getPackageManager, getPackageManagerSelectOptions, transformPackageManagerText, } from "./package-managers.js";
33
+ export { suggestCloseId } from "./id-suggestions.js";
34
+ export type { SuggestCloseIdOptions } from "./id-suggestions.js";
32
35
  export { clearPackageVersionsCache, getPackageVersions, invalidatePackageVersionsCache, } from "./package-versions.js";
33
36
  export type { PackageVersions } from "./package-versions.js";
34
37
  export { TEMPLATE_IDS, TEMPLATE_REGISTRY, getTemplateById, getTemplateSelectOptions, listTemplates, } from "./template-registry.js";
35
38
  export { EXTERNAL_TEMPLATE_CACHE_TTL_DAYS_ENV, pruneExternalTemplateCache, } from "./template-source-cache.js";
36
39
  export type { ExternalTemplateCachePruneOptions, ExternalTemplateCachePruneResult, } from "./template-source-cache.js";
37
40
  export { STALE_TEMP_ROOT_MAX_AGE_MS, WP_TYPIA_TEMP_ROOT_PREFIX, cleanupManagedTempRoot, cleanupStaleTempRoots, createManagedTempRoot, getTrackedTempRoots, } from "./temp-roots.js";
38
- export { createReadlinePrompt, createCliCommandError, createCliDiagnosticCodeError, CliDiagnosticError, CLI_DIAGNOSTIC_CODES, formatCliDiagnosticError, formatAddHelpText, formatDoctorCheckLine, formatDoctorSummaryLine, formatHelpText, formatTemplateDetails, formatTemplateFeatures, formatTemplateSummary, getDoctorChecks, getDoctorFailureDetailLines, getFailingDoctorChecks, getNextSteps, getOptionalOnboarding, getWorkspaceBlockSelectOptions, HOOKED_BLOCK_POSITION_IDS, EDITOR_PLUGIN_SLOT_IDS, isCliDiagnosticError, runAddAdminViewCommand, runAddAbilityCommand, runAddAiFeatureCommand, runAddBindingSourceCommand, runAddBlockCommand, runAddBlockStyleCommand, runAddBlockTransformCommand, runAddEditorPluginCommand, runAddHookedBlockCommand, runAddPatternCommand, runDoctor, runAddVariationCommand, runScaffoldFlow, } from "./cli-core.js";
39
- export type { CliDiagnosticCode, CliDiagnosticCodeError, CliDiagnosticMessage, DoctorCheck, EditorPluginSlotId, HookedBlockPositionId, ReadlinePrompt, } from "./cli-core.js";
41
+ export { createReadlinePrompt, createCliCommandError, createCliDiagnosticCodeError, CliDiagnosticError, CLI_DIAGNOSTIC_CODES, formatCliDiagnosticError, formatAddHelpText, formatDoctorCheckLine, formatDoctorSummaryLine, formatHelpText, formatTemplateDetails, formatTemplateFeatures, formatTemplateSummary, getDoctorChecks, getDoctorFailureDetailLines, getFailingDoctorChecks, getNextSteps, getOptionalOnboarding, getWorkspaceBlockSelectOptions, getWorkspaceBlockSelectOptionsAsync, HOOKED_BLOCK_POSITION_IDS, EDITOR_PLUGIN_SLOT_IDS, isCliDiagnosticError, runAddAdminViewCommand, runAddAbilityCommand, runAddAiFeatureCommand, runAddBindingSourceCommand, runAddBlockCommand, runAddBlockStyleCommand, runAddBlockTransformCommand, runAddEditorPluginCommand, runAddHookedBlockCommand, runAddPatternCommand, runDoctor, runAddVariationCommand, runScaffoldFlow, } from "./cli-core.js";
42
+ export type { CliDiagnosticCode, CliDiagnosticCodeError, CliDiagnosticMessage, DoctorCheck, EditorPluginSlotId, HookedBlockPositionId, ReadlinePrompt, WorkspaceBlockSelectOption, } from "./cli-core.js";
@@ -5,6 +5,7 @@
5
5
  * CLI while keeping reusable project logic out of the CLI package itself.
6
6
  * Consumers should prefer these exports for scaffold, add, migrate, doctor,
7
7
  * and workspace-aware helpers such as `getWorkspaceBlockSelectOptions`,
8
+ * `getWorkspaceBlockSelectOptionsAsync`,
8
9
  * `runAddBlockCommand`, `runAddBlockStyleCommand`,
9
10
  * `runAddBlockTransformCommand`, `runAddVariationCommand`,
10
11
  * `runAddPatternCommand`, `runAddBindingSourceCommand`,
@@ -23,8 +24,9 @@ export { parseWorkspacePackageManagerId, resolveWorkspaceProject, tryResolveWork
23
24
  export { manifestAttributeToJsonSchema, projectJsonSchemaDocument, manifestToJsonSchema, manifestToOpenApi, normalizeEndpointAuthDefinition, } from "./schema-core.js";
24
25
  export { buildCompoundChildStarterManifestDocument, getStarterManifestFiles, stringifyStarterManifest, } from "./starter-manifests.js";
25
26
  export { PACKAGE_MANAGER_IDS, PACKAGE_MANAGERS, formatPackageExecCommand, formatInstallCommand, formatRunScript, getPackageManager, getPackageManagerSelectOptions, transformPackageManagerText, } from "./package-managers.js";
27
+ export { suggestCloseId } from "./id-suggestions.js";
26
28
  export { clearPackageVersionsCache, getPackageVersions, invalidatePackageVersionsCache, } from "./package-versions.js";
27
29
  export { TEMPLATE_IDS, TEMPLATE_REGISTRY, getTemplateById, getTemplateSelectOptions, listTemplates, } from "./template-registry.js";
28
30
  export { EXTERNAL_TEMPLATE_CACHE_TTL_DAYS_ENV, pruneExternalTemplateCache, } from "./template-source-cache.js";
29
31
  export { STALE_TEMP_ROOT_MAX_AGE_MS, WP_TYPIA_TEMP_ROOT_PREFIX, cleanupManagedTempRoot, cleanupStaleTempRoots, createManagedTempRoot, getTrackedTempRoots, } from "./temp-roots.js";
30
- export { createReadlinePrompt, createCliCommandError, createCliDiagnosticCodeError, CliDiagnosticError, CLI_DIAGNOSTIC_CODES, formatCliDiagnosticError, formatAddHelpText, formatDoctorCheckLine, formatDoctorSummaryLine, formatHelpText, formatTemplateDetails, formatTemplateFeatures, formatTemplateSummary, getDoctorChecks, getDoctorFailureDetailLines, getFailingDoctorChecks, getNextSteps, getOptionalOnboarding, getWorkspaceBlockSelectOptions, HOOKED_BLOCK_POSITION_IDS, EDITOR_PLUGIN_SLOT_IDS, isCliDiagnosticError, runAddAdminViewCommand, runAddAbilityCommand, runAddAiFeatureCommand, runAddBindingSourceCommand, runAddBlockCommand, runAddBlockStyleCommand, runAddBlockTransformCommand, runAddEditorPluginCommand, runAddHookedBlockCommand, runAddPatternCommand, runDoctor, runAddVariationCommand, runScaffoldFlow, } from "./cli-core.js";
32
+ export { createReadlinePrompt, createCliCommandError, createCliDiagnosticCodeError, CliDiagnosticError, CLI_DIAGNOSTIC_CODES, formatCliDiagnosticError, formatAddHelpText, formatDoctorCheckLine, formatDoctorSummaryLine, formatHelpText, formatTemplateDetails, formatTemplateFeatures, formatTemplateSummary, getDoctorChecks, getDoctorFailureDetailLines, getFailingDoctorChecks, getNextSteps, getOptionalOnboarding, getWorkspaceBlockSelectOptions, getWorkspaceBlockSelectOptionsAsync, HOOKED_BLOCK_POSITION_IDS, EDITOR_PLUGIN_SLOT_IDS, isCliDiagnosticError, runAddAdminViewCommand, runAddAbilityCommand, runAddAiFeatureCommand, runAddBindingSourceCommand, runAddBlockCommand, runAddBlockStyleCommand, runAddBlockTransformCommand, runAddEditorPluginCommand, runAddHookedBlockCommand, runAddPatternCommand, runDoctor, runAddVariationCommand, runScaffoldFlow, } from "./cli-core.js";
@@ -76,6 +76,8 @@ function recordWorkspaceMigrationTargetAlignment(projectDir, state, recordCheck)
76
76
  return;
77
77
  }
78
78
  try {
79
+ // Maintenance verification is intentionally synchronous so it can compare
80
+ // migration state against a single inventory snapshot.
79
81
  const inventory = readWorkspaceInventory(workspace.projectDir);
80
82
  const expectedTargets = inventory.blocks.map((block) => `${workspace.workspace.namespace}/${block.slug}`);
81
83
  const configuredTargets = state.blocks.map((block) => block.blockName);
@@ -1,4 +1,3 @@
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 { getPackageManager } from "./package-managers.js";
@@ -14,6 +13,7 @@ import { BlockGeneratorService, } from "./block-generator-service.js";
14
13
  import { getTemplateVariables } from "./scaffold-template-variables.js";
15
14
  import { getScaffoldTemplateVariableGroups } from "./scaffold-template-variable-groups.js";
16
15
  import { assertExternalLayerCompositionOptions, } from "./cli-validation.js";
16
+ import { pathExists } from "./fs-async.js";
17
17
  export const DATA_STORAGE_MODES = ["post-meta", "custom-table"];
18
18
  export const PERSISTENCE_POLICIES = ["authenticated", "public"];
19
19
  export { buildBlockCssClassName } from "./scaffold-identifiers.js";
@@ -149,7 +149,7 @@ export async function scaffoldProject({ projectDir, templateId, answers, alterna
149
149
  title: "Finalizing scaffold output",
150
150
  });
151
151
  const readmePath = path.join(projectDir, "README.md");
152
- if (!fs.existsSync(readmePath)) {
152
+ if (!(await pathExists(readmePath))) {
153
153
  await fsp.writeFile(readmePath, buildReadme(resolvedTemplateId, variables, resolvedPackageManager, {
154
154
  withMigrationUi: isBuiltInTemplate || isWorkspace ? withMigrationUi : false,
155
155
  withTestPreset: isBuiltInTemplate ? withTestPreset : false,
@@ -157,7 +157,7 @@ export async function scaffoldProject({ projectDir, templateId, answers, alterna
157
157
  }), "utf8");
158
158
  }
159
159
  const gitignorePath = path.join(projectDir, ".gitignore");
160
- const existingGitignore = fs.existsSync(gitignorePath)
160
+ const existingGitignore = (await pathExists(gitignorePath))
161
161
  ? await fsp.readFile(gitignorePath, "utf8")
162
162
  : "";
163
163
  await fsp.writeFile(gitignorePath, mergeTextLines(buildGitignore(), existingGitignore), "utf8");
@@ -1,6 +1,6 @@
1
- import fs from "node:fs";
2
1
  import path from "node:path";
3
2
  import { promises as fsp } from "node:fs";
3
+ import { pathExists } from "./fs-async.js";
4
4
  import { createManagedTempRoot } from "./temp-roots.js";
5
5
  import { getTemplateById, SHARED_BASE_TEMPLATE_ROOT, SHARED_COMPOUND_TEMPLATE_ROOT, SHARED_MIGRATION_UI_TEMPLATE_ROOT, SHARED_PERSISTENCE_TEMPLATE_ROOT, SHARED_PRESET_TEMPLATE_ROOT, SHARED_REST_HELPER_TEMPLATE_ROOT, SHARED_WORKSPACE_TEMPLATE_ROOT, } from "./template-registry.js";
6
6
  const BUILT_IN_SHARED_TEMPLATE_LAYERS = Object.freeze([
@@ -138,20 +138,23 @@ export function isOmittableBuiltInTemplateLayerDir(templateId, layerDir) {
138
138
  return (OMITTABLE_BUILT_IN_OVERLAY_TEMPLATE_IDS.has(templateId) &&
139
139
  layerDir === getTemplateById(templateId).templateDir);
140
140
  }
141
- function resolveMaterializedTemplateLayerDirs(templateId, layerDirs) {
142
- return layerDirs.flatMap((layerDir) => {
143
- if (fs.existsSync(layerDir)) {
144
- return [layerDir];
141
+ async function resolveMaterializedTemplateLayerDirs(templateId, layerDirs) {
142
+ const materializedLayerDirs = [];
143
+ for (const layerDir of layerDirs) {
144
+ if (await pathExists(layerDir)) {
145
+ materializedLayerDirs.push(layerDir);
146
+ continue;
145
147
  }
146
148
  if (isOmittableBuiltInTemplateLayerDir(templateId, layerDir)) {
147
- return [];
149
+ continue;
148
150
  }
149
151
  throw new Error(`Built-in template layer is missing: ${layerDir}`);
150
- });
152
+ }
153
+ return materializedLayerDirs;
151
154
  }
152
155
  async function materializeBuiltInTemplateSource(templateId, layerDirs) {
153
156
  const template = getTemplateById(templateId);
154
- const materializedLayerDirs = resolveMaterializedTemplateLayerDirs(templateId, layerDirs);
157
+ const materializedLayerDirs = await resolveMaterializedTemplateLayerDirs(templateId, layerDirs);
155
158
  const { path: tempRoot, cleanup } = await createManagedTempRoot("wp-typia-template-");
156
159
  const templateDir = path.join(tempRoot, templateId);
157
160
  try {
@@ -1,6 +1,6 @@
1
- import fs from "node:fs";
2
1
  import path from "node:path";
3
2
  import { promises as fsp } from "node:fs";
3
+ import { pathExists } from "./fs-async.js";
4
4
  import { isPlainObject } from "./object-utils.js";
5
5
  import { getBuiltInSharedTemplateLayerDir, isBuiltInSharedTemplateLayerId, } from "./template-builtins.js";
6
6
  import { listInterpolatedDirectoryOutputs } from "./template-render.js";
@@ -56,7 +56,7 @@ function parseLayerDefinition(layerId, value) {
56
56
  }
57
57
  export async function loadExternalTemplateLayerManifest(sourceRoot) {
58
58
  const manifestPath = path.join(sourceRoot, TEMPLATE_LAYER_MANIFEST_FILENAME);
59
- if (!fs.existsSync(manifestPath)) {
59
+ if (!(await pathExists(manifestPath))) {
60
60
  return null;
61
61
  }
62
62
  const raw = JSON.parse(await fsp.readFile(manifestPath, "utf8"));
@@ -7,6 +7,9 @@ export declare const EXTERNAL_TEMPLATE_TRUST_WARNING = "External template config
7
7
  /**
8
8
  * Search a source directory for the first supported external template entry.
9
9
  *
10
+ * @deprecated Use `findExternalTemplateEntry()` from async template-source
11
+ * paths. This synchronous helper remains only for compatibility callers.
12
+ *
10
13
  * @param sourceDir Directory that may contain an external template config entry.
11
14
  * @returns The first matching entry path, or null when no supported entry exists.
12
15
  */
@@ -33,6 +33,9 @@ function resolveSourceSubpath(sourceDir, relativePath) {
33
33
  /**
34
34
  * Search a source directory for the first supported external template entry.
35
35
  *
36
+ * @deprecated Use `findExternalTemplateEntry()` from async template-source
37
+ * paths. This synchronous helper remains only for compatibility callers.
38
+ *
36
39
  * @param sourceDir Directory that may contain an external template config entry.
37
40
  * @returns The first matching entry path, or null when no supported entry exists.
38
41
  */
@@ -205,11 +208,11 @@ export async function renderCreateBlockExternalTemplate(sourceDir, context, requ
205
208
  const view = await buildExternalTemplateView(context, config, selectedVariant, variantConfig);
206
209
  const blockTemplateDir = resolveSourceSubpath(sourceDir, templatePath);
207
210
  await copyRenderedDirectory(blockTemplateDir, blockDir, view, {
208
- filter: (sourcePath, _destinationPath, entry) => {
211
+ filter: async (sourcePath, _destinationPath, entry) => {
209
212
  const mustacheVariantPath = path.join(path.dirname(sourcePath), `${entry.name}.mustache`);
210
213
  return !(entry.isFile() &&
211
214
  (entry.name === 'package.json' || entry.name === 'README.md') &&
212
- fs.existsSync(mustacheVariantPath));
215
+ (await pathExists(mustacheVariantPath)));
213
216
  },
214
217
  });
215
218
  const assetsPath = typeof variantConfig.assetsPath === 'string'
@@ -2,6 +2,9 @@ import type { ResolvedTemplateSource, SeedSource, TemplateVariableContext } from
2
2
  /**
3
3
  * Read a remote block source and return its default block category.
4
4
  *
5
+ * @deprecated Use `getDefaultCategoryAsync()` from async template-source paths.
6
+ * This synchronous helper remains only for compatibility callers.
7
+ *
5
8
  * @param sourceDir Block source directory that may contain a block.json file.
6
9
  * @returns The declared block category, or "widgets" when detection fails.
7
10
  */
@@ -16,6 +19,9 @@ export declare function getDefaultCategoryAsync(sourceDir: string): Promise<stri
16
19
  /**
17
20
  * Read `wpTypia.projectType` from a rendered or source template package
18
21
  * manifest and return it when present.
22
+ *
23
+ * @deprecated Use `getTemplateProjectTypeAsync()` from async template-source
24
+ * paths. This synchronous helper remains only for compatibility callers.
19
25
  */
20
26
  export declare function getTemplateProjectType(sourceDir: string): string | null;
21
27
  /**
@@ -59,6 +59,9 @@ async function readRemoteBlockJsonAsync(blockDir) {
59
59
  /**
60
60
  * Read a remote block source and return its default block category.
61
61
  *
62
+ * @deprecated Use `getDefaultCategoryAsync()` from async template-source paths.
63
+ * This synchronous helper remains only for compatibility callers.
64
+ *
62
65
  * @param sourceDir Block source directory that may contain a block.json file.
63
66
  * @returns The declared block category, or "widgets" when detection fails.
64
67
  */
@@ -139,6 +142,9 @@ async function readTemplatePackageJsonAsync(sourceDir) {
139
142
  /**
140
143
  * Read `wpTypia.projectType` from a rendered or source template package
141
144
  * manifest and return it when present.
145
+ *
146
+ * @deprecated Use `getTemplateProjectTypeAsync()` from async template-source
147
+ * paths. This synchronous helper remains only for compatibility callers.
142
148
  */
143
149
  export function getTemplateProjectType(sourceDir) {
144
150
  const packageJsonEntry = readTemplatePackageJson(sourceDir);
@@ -185,11 +191,11 @@ export async function normalizeWpTypiaTemplateSeed(seed) {
185
191
  const normalizedDir = path.join(tempRoot, 'template');
186
192
  try {
187
193
  await copyRawDirectory(seed.blockDir, normalizedDir, {
188
- filter: (sourcePath, _targetPath, entry) => {
194
+ filter: async (sourcePath, _targetPath, entry) => {
189
195
  const mustacheVariantPath = path.join(path.dirname(sourcePath), `${entry.name}.mustache`);
190
196
  return !(entry.isFile() &&
191
197
  (entry.name === 'package.json' || entry.name === 'README.md') &&
192
- fs.existsSync(mustacheVariantPath));
198
+ (await pathExists(mustacheVariantPath)));
193
199
  },
194
200
  });
195
201
  if (seed.assetsDir && (await pathExists(seed.assetsDir))) {
@@ -1,5 +1,18 @@
1
1
  import type { RemoteTemplateLocator, SeedSource } from './template-source-contracts.js';
2
+ /**
3
+ * Synchronously identify the bundled workspace template seed.
4
+ *
5
+ * This remains sync-only for compatibility callers. Async template resolution
6
+ * paths should use `isOfficialWorkspaceTemplateSeedAsync()`.
7
+ */
2
8
  export declare function isOfficialWorkspaceTemplateSeed(seed: SeedSource): boolean;
9
+ /**
10
+ * Asynchronously identify the bundled workspace template seed.
11
+ *
12
+ * @param seed Seed source to inspect.
13
+ * @returns Whether the seed resolves to the official workspace template package.
14
+ */
15
+ export declare function isOfficialWorkspaceTemplateSeedAsync(seed: SeedSource): Promise<boolean>;
3
16
  export declare function assertNoSymlinks(sourceDir: string): Promise<void>;
4
17
  /**
5
18
  * Resolves a template locator into a local seed source directory.
@@ -8,6 +8,7 @@ import { x as extractTarball } from 'tar';
8
8
  import { createExternalTemplateTimeoutError, fetchWithExternalTemplateTimeout, getExternalTemplateMetadataMaxBytes, getExternalTemplateTarballMaxBytes, getExternalTemplateTimeoutMs, readBufferResponseWithLimit, readJsonResponseWithLimit, } from './external-template-guards.js';
9
9
  import { findReusableExternalTemplateSourceCache, isExternalTemplateCacheEnabled, resolveExternalTemplateSourceCache, } from './template-source-cache.js';
10
10
  import { CLI_DIAGNOSTIC_CODES, createCliDiagnosticCodeError, } from './cli-diagnostics.js';
11
+ import { pathExists } from './fs-async.js';
11
12
  import { OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE, OFFICIAL_WORKSPACE_TEMPLATE_ALIAS, PROJECT_TOOLS_PACKAGE_ROOT, TEMPLATE_IDS, } from './template-registry.js';
12
13
  import { isPlainObject } from './object-utils.js';
13
14
  import { createManagedTempRoot } from './temp-roots.js';
@@ -184,7 +185,9 @@ async function fetchNpmTemplateSource(locator) {
184
185
  * Resolve a locally installed npm template package from the caller workspace.
185
186
  *
186
187
  * Bare package ids are preferred here so monorepo and offline workflows can
187
- * use an already-installed template without forcing a registry fetch.
188
+ * use an already-installed template without forcing a registry fetch. This
189
+ * path intentionally keeps Node's synchronous `require.resolve` semantics
190
+ * because the module resolver itself has no async equivalent.
188
191
  */
189
192
  function resolveInstalledNpmTemplateSource(locator, cwd) {
190
193
  if (locator.rawSpec !== '' && locator.rawSpec !== '*') {
@@ -244,6 +247,12 @@ function resolveInstalledNpmTemplateSource(locator, cwd) {
244
247
  throw error;
245
248
  }
246
249
  }
250
+ /**
251
+ * Synchronously identify the bundled workspace template seed.
252
+ *
253
+ * This remains sync-only for compatibility callers. Async template resolution
254
+ * paths should use `isOfficialWorkspaceTemplateSeedAsync()`.
255
+ */
247
256
  export function isOfficialWorkspaceTemplateSeed(seed) {
248
257
  const packageJsonPath = path.join(seed.rootDir, 'package.json');
249
258
  if (!fs.existsSync(packageJsonPath)) {
@@ -257,6 +266,25 @@ export function isOfficialWorkspaceTemplateSeed(seed) {
257
266
  return false;
258
267
  }
259
268
  }
269
+ /**
270
+ * Asynchronously identify the bundled workspace template seed.
271
+ *
272
+ * @param seed Seed source to inspect.
273
+ * @returns Whether the seed resolves to the official workspace template package.
274
+ */
275
+ export async function isOfficialWorkspaceTemplateSeedAsync(seed) {
276
+ const packageJsonPath = path.join(seed.rootDir, 'package.json');
277
+ if (!(await pathExists(packageJsonPath))) {
278
+ return false;
279
+ }
280
+ try {
281
+ const packageJson = JSON.parse(await fsp.readFile(packageJsonPath, 'utf8'));
282
+ return packageJson.name === OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE;
283
+ }
284
+ catch {
285
+ return false;
286
+ }
287
+ }
260
288
  export async function assertNoSymlinks(sourceDir) {
261
289
  const stats = await fsp.lstat(sourceDir);
262
290
  if (stats.isSymbolicLink()) {
@@ -300,13 +328,13 @@ function runGitTemplateCommand(args, label, options = {}) {
300
328
  function getGitHubTemplateRepositoryUrl(locator) {
301
329
  return `https://github.com/${locator.owner}/${locator.repo}.git`;
302
330
  }
303
- function resolveGitHubTemplateDirectory(checkoutDir, locator) {
331
+ async function resolveGitHubTemplateDirectory(checkoutDir, locator) {
304
332
  const sourceDir = path.resolve(checkoutDir, locator.sourcePath);
305
333
  const relativeSourceDir = path.relative(checkoutDir, sourceDir);
306
334
  if (relativeSourceDir.startsWith('..') || path.isAbsolute(relativeSourceDir)) {
307
335
  throw new Error('GitHub template path must stay within the cloned repository.');
308
336
  }
309
- if (!fs.existsSync(sourceDir)) {
337
+ if (!(await pathExists(sourceDir))) {
310
338
  throw new Error(`GitHub template path does not exist: ${locator.sourcePath}`);
311
339
  }
312
340
  return sourceDir;
@@ -438,7 +466,7 @@ async function reuseGitHubTemplateCacheByMetadata(locator) {
438
466
  if (!cachedSource) {
439
467
  return null;
440
468
  }
441
- const sourceDir = resolveGitHubTemplateDirectory(cachedSource.sourceDir, locator);
469
+ const sourceDir = await resolveGitHubTemplateDirectory(cachedSource.sourceDir, locator);
442
470
  await assertNoSymlinks(sourceDir);
443
471
  return {
444
472
  blockDir: sourceDir,
@@ -482,12 +510,12 @@ async function resolveGitHubTemplateSource(locator) {
482
510
  namespace: 'github',
483
511
  }, async (checkoutDir) => {
484
512
  cloneGitHubTemplateSource(locator, checkoutDir);
485
- const sourceDir = resolveGitHubTemplateDirectory(checkoutDir, locator);
513
+ const sourceDir = await resolveGitHubTemplateDirectory(checkoutDir, locator);
486
514
  await assertNoSymlinks(sourceDir);
487
515
  pinGitHubTemplateCacheRevision(locator, checkoutDir, resolvedCacheRevision);
488
516
  });
489
517
  if (cachedSource) {
490
- const sourceDir = resolveGitHubTemplateDirectory(cachedSource.sourceDir, locator);
518
+ const sourceDir = await resolveGitHubTemplateDirectory(cachedSource.sourceDir, locator);
491
519
  await assertNoSymlinks(sourceDir);
492
520
  return {
493
521
  blockDir: sourceDir,
@@ -506,7 +534,7 @@ async function resolveGitHubTemplateSource(locator) {
506
534
  const checkoutDir = path.join(remoteRoot, 'source');
507
535
  try {
508
536
  cloneGitHubTemplateSource(locator, checkoutDir);
509
- const sourceDir = resolveGitHubTemplateDirectory(checkoutDir, locator);
537
+ const sourceDir = await resolveGitHubTemplateDirectory(checkoutDir, locator);
510
538
  await assertNoSymlinks(sourceDir);
511
539
  return {
512
540
  blockDir: sourceDir,
@@ -530,7 +558,7 @@ async function resolveGitHubTemplateSource(locator) {
530
558
  export async function resolveTemplateSeed(locator, cwd) {
531
559
  if (locator.kind === 'path') {
532
560
  const sourceDir = path.resolve(cwd, locator.templatePath);
533
- if (!fs.existsSync(sourceDir)) {
561
+ if (!(await pathExists(sourceDir))) {
534
562
  throw new Error(`Template path does not exist: ${sourceDir}`);
535
563
  }
536
564
  await assertNoSymlinks(sourceDir);
@@ -2,7 +2,7 @@ import { OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE, isBuiltInTemplateId, } from './tem
2
2
  import { resolveBuiltInTemplateSource } from './template-builtins.js';
3
3
  import { parseTemplateLocator, } from './template-source-locators.js';
4
4
  import { detectTemplateSourceFormat, getTemplateProjectTypeAsync, getDefaultCategoryAsync, getTemplateVariableContext, normalizeCreateBlockSubset, normalizeWpTypiaTemplateSeed, renderCreateBlockExternalTemplate, } from './template-source-normalization.js';
5
- import { isOfficialWorkspaceTemplateSeed, resolveTemplateSeed, } from './template-source-seeds.js';
5
+ import { isOfficialWorkspaceTemplateSeedAsync, resolveTemplateSeed, } from './template-source-seeds.js';
6
6
  import { assertBuiltInTemplateVariantAllowed, } from './cli-validation.js';
7
7
  import { getScaffoldTemplateVariableGroups } from './scaffold-template-variable-groups.js';
8
8
  export { parseGitHubTemplateLocator, parseNpmTemplateLocator, parseTemplateLocator, } from './template-source-locators.js';
@@ -27,7 +27,7 @@ export async function resolveTemplateSource(templateId, cwd, variables, variant)
27
27
  const context = getTemplateVariableContext(variables);
28
28
  const seed = await resolveTemplateSeed(locator, cwd);
29
29
  const isOfficialWorkspaceTemplate = templateId === OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE ||
30
- isOfficialWorkspaceTemplateSeed(seed);
30
+ (await isOfficialWorkspaceTemplateSeedAsync(seed));
31
31
  let normalizedSeed = null;
32
32
  try {
33
33
  const format = await detectTemplateSourceFormat(seed.blockDir);
@@ -142,6 +142,11 @@ export interface WorkspaceInventory {
142
142
  source: string;
143
143
  variations: WorkspaceVariationInventoryEntry[];
144
144
  }
145
+ export type WorkspaceBlockSelectOption = {
146
+ description: string;
147
+ name: string;
148
+ value: string;
149
+ };
145
150
  export declare const BLOCK_CONFIG_ENTRY_MARKER = "\t// wp-typia add block entries";
146
151
  export declare const VARIATION_CONFIG_ENTRY_MARKER = "\t// wp-typia add variation entries";
147
152
  export declare const BLOCK_STYLE_CONFIG_ENTRY_MARKER = "\t// wp-typia add style entries";
@@ -199,19 +204,28 @@ export declare function readWorkspaceInventory(projectDir: string): WorkspaceInv
199
204
  */
200
205
  export declare function readWorkspaceInventoryAsync(projectDir: string): Promise<WorkspaceInventory>;
201
206
  /**
202
- * Return select options for the current workspace block inventory.
207
+ * Synchronously return select options for the current workspace block inventory.
203
208
  *
204
209
  * The `description` field mirrors `block.typesFile`, while `name` and `value`
205
210
  * both map to the block slug for use in interactive add flows.
206
211
  *
212
+ * @deprecated Use `getWorkspaceBlockSelectOptionsAsync()` from async command
213
+ * paths. This helper intentionally remains sync-only for compatibility callers.
214
+ *
207
215
  * @param projectDir Workspace root directory.
208
216
  * @returns Block options for variation-target selection.
209
217
  */
210
- export declare function getWorkspaceBlockSelectOptions(projectDir: string): Array<{
211
- description: string;
212
- name: string;
213
- value: string;
214
- }>;
218
+ export declare function getWorkspaceBlockSelectOptions(projectDir: string): WorkspaceBlockSelectOption[];
219
+ /**
220
+ * Asynchronously return select options for the current workspace block inventory.
221
+ *
222
+ * The returned option shape matches `getWorkspaceBlockSelectOptions()` while
223
+ * avoiding synchronous inventory reads in interactive or async command paths.
224
+ *
225
+ * @param projectDir Workspace root directory.
226
+ * @returns Block options for variation-target selection.
227
+ */
228
+ export declare function getWorkspaceBlockSelectOptionsAsync(projectDir: string): Promise<WorkspaceBlockSelectOption[]>;
215
229
  /**
216
230
  * Update `scripts/block-config.ts` source text with additional inventory entries.
217
231
  *
@@ -5,8 +5,8 @@ import ts from "typescript";
5
5
  import { REST_RESOURCE_METHOD_IDS } from "./cli-add-shared.js";
6
6
  import { escapeRegex } from "./php-utils.js";
7
7
  import { getPropertyNameText } from "./ts-property-names.js";
8
- function defineInventoryEntryParser(descriptor) {
9
- return descriptor;
8
+ function defineInventoryEntryParser() {
9
+ return (descriptor) => descriptor;
10
10
  }
11
11
  export const BLOCK_CONFIG_ENTRY_MARKER = "\t// wp-typia add block entries";
12
12
  export const VARIATION_CONFIG_ENTRY_MARKER = "\t// wp-typia add variation entries";
@@ -213,7 +213,7 @@ const BLOCK_INVENTORY_SECTION = {
213
213
  },
214
214
  parse: {
215
215
  entriesKey: "blocks",
216
- entry: defineInventoryEntryParser({
216
+ entry: defineInventoryEntryParser()({
217
217
  entryName: "BLOCKS",
218
218
  fields: [
219
219
  { key: "apiTypesFile" },
@@ -239,7 +239,7 @@ const INVENTORY_SECTIONS = [
239
239
  },
240
240
  parse: {
241
241
  entriesKey: "variations",
242
- entry: defineInventoryEntryParser({
242
+ entry: defineInventoryEntryParser()({
243
243
  entryName: "VARIATIONS",
244
244
  fields: [
245
245
  { key: "block", required: true },
@@ -265,7 +265,7 @@ const INVENTORY_SECTIONS = [
265
265
  },
266
266
  parse: {
267
267
  entriesKey: "blockStyles",
268
- entry: defineInventoryEntryParser({
268
+ entry: defineInventoryEntryParser()({
269
269
  entryName: "BLOCK_STYLES",
270
270
  fields: [
271
271
  { key: "block", required: true },
@@ -291,7 +291,7 @@ const INVENTORY_SECTIONS = [
291
291
  },
292
292
  parse: {
293
293
  entriesKey: "blockTransforms",
294
- entry: defineInventoryEntryParser({
294
+ entry: defineInventoryEntryParser()({
295
295
  entryName: "BLOCK_TRANSFORMS",
296
296
  fields: [
297
297
  { key: "block", required: true },
@@ -319,7 +319,7 @@ const INVENTORY_SECTIONS = [
319
319
  },
320
320
  parse: {
321
321
  entriesKey: "patterns",
322
- entry: defineInventoryEntryParser({
322
+ entry: defineInventoryEntryParser()({
323
323
  entryName: "PATTERNS",
324
324
  fields: [
325
325
  { key: "file", required: true },
@@ -344,7 +344,7 @@ const INVENTORY_SECTIONS = [
344
344
  },
345
345
  parse: {
346
346
  entriesKey: "bindingSources",
347
- entry: defineInventoryEntryParser({
347
+ entry: defineInventoryEntryParser()({
348
348
  entryName: "BINDING_SOURCES",
349
349
  fields: [
350
350
  { key: "attribute" },
@@ -372,7 +372,7 @@ const INVENTORY_SECTIONS = [
372
372
  },
373
373
  parse: {
374
374
  entriesKey: "restResources",
375
- entry: defineInventoryEntryParser({
375
+ entry: defineInventoryEntryParser()({
376
376
  entryName: "REST_RESOURCES",
377
377
  fields: [
378
378
  { key: "apiFile", required: true },
@@ -416,7 +416,7 @@ const INVENTORY_SECTIONS = [
416
416
  },
417
417
  parse: {
418
418
  entriesKey: "abilities",
419
- entry: defineInventoryEntryParser({
419
+ entry: defineInventoryEntryParser()({
420
420
  entryName: "ABILITIES",
421
421
  fields: [
422
422
  { key: "clientFile", required: true },
@@ -449,7 +449,7 @@ const INVENTORY_SECTIONS = [
449
449
  },
450
450
  parse: {
451
451
  entriesKey: "aiFeatures",
452
- entry: defineInventoryEntryParser({
452
+ entry: defineInventoryEntryParser()({
453
453
  entryName: "AI_FEATURES",
454
454
  fields: [
455
455
  { key: "aiSchemaFile", required: true },
@@ -482,7 +482,7 @@ const INVENTORY_SECTIONS = [
482
482
  },
483
483
  parse: {
484
484
  entriesKey: "adminViews",
485
- entry: defineInventoryEntryParser({
485
+ entry: defineInventoryEntryParser()({
486
486
  entryName: "ADMIN_VIEWS",
487
487
  fields: [
488
488
  { key: "file", required: true },
@@ -509,7 +509,7 @@ const INVENTORY_SECTIONS = [
509
509
  },
510
510
  parse: {
511
511
  entriesKey: "editorPlugins",
512
- entry: defineInventoryEntryParser({
512
+ entry: defineInventoryEntryParser()({
513
513
  entryName: "EDITOR_PLUGINS",
514
514
  fields: [
515
515
  { key: "file", required: true },
@@ -570,14 +570,7 @@ function getOptionalStringProperty(entryName, elementIndex, objectLiteral, key)
570
570
  }
571
571
  return undefined;
572
572
  }
573
- function getRequiredStringProperty(entryName, elementIndex, objectLiteral, key) {
574
- const value = getOptionalStringProperty(entryName, elementIndex, objectLiteral, key);
575
- if (!value) {
576
- throw new Error(`${entryName}[${elementIndex}] is missing required "${key}" in scripts/block-config.ts.`);
577
- }
578
- return value;
579
- }
580
- function getRequiredStringArrayProperty(entryName, elementIndex, objectLiteral, key) {
573
+ function getOptionalStringArrayProperty(entryName, elementIndex, objectLiteral, key) {
581
574
  for (const property of objectLiteral.properties) {
582
575
  if (!ts.isPropertyAssignment(property)) {
583
576
  continue;
@@ -596,7 +589,24 @@ function getRequiredStringArrayProperty(entryName, elementIndex, objectLiteral,
596
589
  return element.text;
597
590
  });
598
591
  }
599
- throw new Error(`${entryName}[${elementIndex}] is missing required "${key}" in scripts/block-config.ts.`);
592
+ return undefined;
593
+ }
594
+ function isMissingRequiredInventoryValue(value) {
595
+ return value === undefined || (typeof value === "string" && value.length === 0);
596
+ }
597
+ function formatMissingRequiredInventoryFields(keys) {
598
+ return keys.length === 1
599
+ ? `required "${keys[0]}"`
600
+ : `required fields ${keys.map((key) => `"${key}"`).join(", ")}`;
601
+ }
602
+ function assertParsedInventoryEntry(entry, descriptor, elementIndex) {
603
+ const missingRequiredKeys = descriptor.fields
604
+ .filter((field) => field.required === true &&
605
+ isMissingRequiredInventoryValue(entry[field.key]))
606
+ .map((field) => field.key);
607
+ if (missingRequiredKeys.length > 0) {
608
+ throw new Error(`${descriptor.entryName}[${elementIndex}] is missing ${formatMissingRequiredInventoryFields(missingRequiredKeys)} in scripts/block-config.ts.`);
609
+ }
600
610
  }
601
611
  function parseInventoryEntries(arrayLiteral, descriptor) {
602
612
  return arrayLiteral.elements.map((element, elementIndex) => {
@@ -607,10 +617,8 @@ function parseInventoryEntries(arrayLiteral, descriptor) {
607
617
  for (const field of descriptor.fields) {
608
618
  const kind = field.kind ?? "string";
609
619
  const value = kind === "stringArray"
610
- ? getRequiredStringArrayProperty(descriptor.entryName, elementIndex, element, field.key)
611
- : field.required
612
- ? getRequiredStringProperty(descriptor.entryName, elementIndex, element, field.key)
613
- : getOptionalStringProperty(descriptor.entryName, elementIndex, element, field.key);
620
+ ? getOptionalStringArrayProperty(descriptor.entryName, elementIndex, element, field.key)
621
+ : getOptionalStringProperty(descriptor.entryName, elementIndex, element, field.key);
614
622
  field.validate?.(value, {
615
623
  elementIndex,
616
624
  entryName: descriptor.entryName,
@@ -618,6 +626,7 @@ function parseInventoryEntries(arrayLiteral, descriptor) {
618
626
  });
619
627
  entry[field.key] = value;
620
628
  }
629
+ assertParsedInventoryEntry(entry, descriptor, elementIndex);
621
630
  return entry;
622
631
  });
623
632
  }
@@ -757,21 +766,39 @@ export async function readWorkspaceInventoryAsync(projectDir) {
757
766
  ...parseWorkspaceInventorySource(source),
758
767
  };
759
768
  }
769
+ function toWorkspaceBlockSelectOptions(blocks) {
770
+ return blocks.map((block) => ({
771
+ description: block.typesFile,
772
+ name: block.slug,
773
+ value: block.slug,
774
+ }));
775
+ }
760
776
  /**
761
- * Return select options for the current workspace block inventory.
777
+ * Synchronously return select options for the current workspace block inventory.
762
778
  *
763
779
  * The `description` field mirrors `block.typesFile`, while `name` and `value`
764
780
  * both map to the block slug for use in interactive add flows.
765
781
  *
782
+ * @deprecated Use `getWorkspaceBlockSelectOptionsAsync()` from async command
783
+ * paths. This helper intentionally remains sync-only for compatibility callers.
784
+ *
766
785
  * @param projectDir Workspace root directory.
767
786
  * @returns Block options for variation-target selection.
768
787
  */
769
788
  export function getWorkspaceBlockSelectOptions(projectDir) {
770
- return readWorkspaceInventory(projectDir).blocks.map((block) => ({
771
- description: block.typesFile,
772
- name: block.slug,
773
- value: block.slug,
774
- }));
789
+ return toWorkspaceBlockSelectOptions(readWorkspaceInventory(projectDir).blocks);
790
+ }
791
+ /**
792
+ * Asynchronously return select options for the current workspace block inventory.
793
+ *
794
+ * The returned option shape matches `getWorkspaceBlockSelectOptions()` while
795
+ * avoiding synchronous inventory reads in interactive or async command paths.
796
+ *
797
+ * @param projectDir Workspace root directory.
798
+ * @returns Block options for variation-target selection.
799
+ */
800
+ export async function getWorkspaceBlockSelectOptionsAsync(projectDir) {
801
+ return toWorkspaceBlockSelectOptions((await readWorkspaceInventoryAsync(projectDir)).blocks);
775
802
  }
776
803
  function ensureWorkspaceInventorySections(source) {
777
804
  let nextSource = source.trimEnd();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wp-typia/project-tools",
3
- "version": "0.22.8",
3
+ "version": "0.22.9",
4
4
  "description": "Project orchestration and programmatic tooling for wp-typia",
5
5
  "packageManager": "bun@1.3.11",
6
6
  "type": "module",
@@ -52,6 +52,11 @@
52
52
  "import": "./dist/runtime/create-template-validation.js",
53
53
  "default": "./dist/runtime/create-template-validation.js"
54
54
  },
55
+ "./id-suggestions": {
56
+ "types": "./dist/runtime/id-suggestions.d.ts",
57
+ "import": "./dist/runtime/id-suggestions.js",
58
+ "default": "./dist/runtime/id-suggestions.js"
59
+ },
55
60
  "./ai-artifacts": {
56
61
  "types": "./dist/runtime/ai-artifacts.d.ts",
57
62
  "import": "./dist/runtime/ai-artifacts.js",