@wp-typia/project-tools 0.22.7 → 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.
- package/dist/runtime/block-targets.d.ts +40 -0
- package/dist/runtime/block-targets.js +71 -0
- package/dist/runtime/built-in-block-artifact-types.js +2 -1
- package/dist/runtime/built-in-block-attribute-specs.js +2 -1
- package/dist/runtime/built-in-block-code-artifacts.js +2 -0
- package/dist/runtime/built-in-block-non-ts-family-artifacts.js +12 -9
- package/dist/runtime/built-in-block-non-ts-render-utils.js +2 -0
- package/dist/runtime/cli-add-block-config.js +2 -1
- package/dist/runtime/cli-add-block.js +16 -4
- package/dist/runtime/cli-add-types.d.ts +8 -0
- package/dist/runtime/cli-add-types.js +11 -0
- package/dist/runtime/cli-add-workspace-ability-scaffold.js +21 -15
- package/dist/runtime/cli-add-workspace-ability.js +2 -2
- package/dist/runtime/cli-add-workspace-admin-view-scaffold.js +17 -13
- package/dist/runtime/cli-add-workspace-admin-view.js +2 -2
- package/dist/runtime/cli-add-workspace-ai.js +2 -2
- package/dist/runtime/cli-add-workspace-assets.js +42 -48
- package/dist/runtime/cli-add-workspace-rest.js +2 -2
- package/dist/runtime/cli-add-workspace.js +6 -38
- package/dist/runtime/cli-add.d.ts +3 -2
- package/dist/runtime/cli-add.js +2 -2
- package/dist/runtime/cli-core.d.ts +4 -2
- package/dist/runtime/cli-core.js +3 -2
- package/dist/runtime/cli-diagnostics.js +6 -0
- package/dist/runtime/cli-doctor-workspace.js +2 -0
- package/dist/runtime/cli-scaffold.js +5 -4
- package/dist/runtime/create-template-validation.d.ts +10 -0
- package/dist/runtime/create-template-validation.js +95 -0
- package/dist/runtime/id-suggestions.d.ts +21 -0
- package/dist/runtime/id-suggestions.js +48 -0
- package/dist/runtime/index.d.ts +5 -2
- package/dist/runtime/index.js +3 -1
- package/dist/runtime/migration-maintenance-verify.js +2 -0
- package/dist/runtime/package-versions.js +15 -2
- package/dist/runtime/php-utils.js +66 -0
- package/dist/runtime/scaffold-answer-resolution.js +5 -108
- package/dist/runtime/scaffold-apply-utils.js +3 -2
- package/dist/runtime/scaffold-identifiers.js +4 -3
- package/dist/runtime/scaffold-template-assertions.d.ts +6 -0
- package/dist/runtime/scaffold-template-assertions.js +33 -0
- package/dist/runtime/scaffold-template-variable-groups.d.ts +2 -0
- package/dist/runtime/scaffold-template-variable-groups.js +7 -0
- package/dist/runtime/scaffold.js +3 -3
- package/dist/runtime/string-case.d.ts +2 -4
- package/dist/runtime/string-case.js +13 -4
- package/dist/runtime/template-builtins.js +11 -8
- package/dist/runtime/template-layers.js +2 -2
- package/dist/runtime/template-source-cache-policy.d.ts +53 -0
- package/dist/runtime/template-source-cache-policy.js +135 -0
- package/dist/runtime/template-source-cache.d.ts +1 -45
- package/dist/runtime/template-source-cache.js +9 -152
- package/dist/runtime/template-source-external.d.ts +3 -0
- package/dist/runtime/template-source-external.js +5 -2
- package/dist/runtime/template-source-remote.d.ts +6 -0
- package/dist/runtime/template-source-remote.js +8 -2
- package/dist/runtime/template-source-seeds.d.ts +13 -0
- package/dist/runtime/template-source-seeds.js +36 -8
- package/dist/runtime/template-source.js +2 -2
- package/dist/runtime/ts-property-names.d.ts +11 -0
- package/dist/runtime/ts-property-names.js +16 -0
- package/dist/runtime/workspace-inventory.d.ts +33 -7
- package/dist/runtime/workspace-inventory.js +94 -41
- package/package.json +11 -1
|
@@ -1,15 +1,17 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
1
|
import { promises as fsp } from "node:fs";
|
|
3
2
|
import path from "node:path";
|
|
4
3
|
import { syncBlockMetadata, } from "@wp-typia/block-runtime/metadata-core";
|
|
5
4
|
import ts from "typescript";
|
|
6
5
|
import { resolveWorkspaceProject, } from "./workspace-project.js";
|
|
7
|
-
import {
|
|
6
|
+
import { appendWorkspaceInventoryEntries, readWorkspaceInventoryAsync, } from "./workspace-inventory.js";
|
|
8
7
|
import { toPascalCase, toTitleCase } from "./string-case.js";
|
|
9
8
|
import { findPhpFunctionRange, hasPhpFunctionDefinition, quotePhpString, replacePhpFunctionDefinition, } from "./php-utils.js";
|
|
10
9
|
import { assertBindingSourceDoesNotExist, assertEditorPluginDoesNotExist, assertPatternDoesNotExist, assertValidEditorPluginSlot, assertValidGeneratedSlug, getWorkspaceBootstrapPath, normalizeBlockSlug, patchFile, quoteTsString, resolveWorkspaceBlock, rollbackWorkspaceMutation, snapshotWorkspaceFiles, } from "./cli-add-shared.js";
|
|
11
10
|
import { appendPhpSnippetBeforeClosingTag, insertPhpSnippetBeforeWorkspaceAnchors, } from "./cli-add-workspace-mutation.js";
|
|
11
|
+
import { resolveWorkspaceBlockTargetName } from "./block-targets.js";
|
|
12
|
+
import { pathExists, readOptionalUtf8File } from "./fs-async.js";
|
|
12
13
|
import { normalizeOptionalCliString } from "./cli-validation.js";
|
|
14
|
+
import { getPropertyNameText } from "./ts-property-names.js";
|
|
13
15
|
const PATTERN_BOOTSTRAP_CATEGORY = "register_block_pattern_category";
|
|
14
16
|
const BINDING_SOURCE_SERVER_GLOB = "/src/bindings/*/server.php";
|
|
15
17
|
const BINDING_SOURCE_EDITOR_SCRIPT = "build/bindings/index.js";
|
|
@@ -48,26 +50,6 @@ function assertValidBindingAttributeName(attributeName) {
|
|
|
48
50
|
}
|
|
49
51
|
return trimmed;
|
|
50
52
|
}
|
|
51
|
-
function resolveBindingTargetBlockSlug(blockName, namespace) {
|
|
52
|
-
const trimmed = blockName.trim();
|
|
53
|
-
if (!trimmed) {
|
|
54
|
-
throw new Error("`wp-typia add binding-source` requires --block <block-slug|namespace/block-slug> to include a value when --attribute is provided.");
|
|
55
|
-
}
|
|
56
|
-
const blockNameSegments = trimmed.split("/");
|
|
57
|
-
if (blockNameSegments.length > 2) {
|
|
58
|
-
throw new Error(`Binding target block "${trimmed}" must use <block-slug> or <namespace/block-slug> format.`);
|
|
59
|
-
}
|
|
60
|
-
if (blockNameSegments.some((segment) => segment.trim() === "")) {
|
|
61
|
-
throw new Error(`Binding target block "${trimmed}" must use <block-slug> or <namespace/block-slug> format without empty path segments.`);
|
|
62
|
-
}
|
|
63
|
-
const [maybeNamespace, maybeSlug] = blockNameSegments.length === 2
|
|
64
|
-
? blockNameSegments
|
|
65
|
-
: [undefined, blockNameSegments[0]];
|
|
66
|
-
if (maybeNamespace && maybeNamespace !== namespace) {
|
|
67
|
-
throw new Error(`Binding target block "${trimmed}" uses namespace "${maybeNamespace}". Expected "${namespace}".`);
|
|
68
|
-
}
|
|
69
|
-
return normalizeBlockSlug(maybeSlug ?? "");
|
|
70
|
-
}
|
|
71
53
|
function buildEditorPluginConfigEntry(editorPluginSlug, slot) {
|
|
72
54
|
return [
|
|
73
55
|
"\t{",
|
|
@@ -237,9 +219,15 @@ function resolveBindingTarget(options, namespace) {
|
|
|
237
219
|
if (!hasBlock || !hasAttribute) {
|
|
238
220
|
throw new Error("`wp-typia add binding-source` requires --block and --attribute to be provided together.");
|
|
239
221
|
}
|
|
222
|
+
const targetBlock = resolveWorkspaceBlockTargetName(blockName ?? "", namespace, {
|
|
223
|
+
empty: () => "`wp-typia add binding-source` requires --block <block-slug|namespace/block-slug> to include a value when --attribute is provided.",
|
|
224
|
+
emptySegment: (input) => `Binding target block "${input}" must use <block-slug> or <namespace/block-slug> format without empty path segments.`,
|
|
225
|
+
invalidFormat: (input) => `Binding target block "${input}" must use <block-slug> or <namespace/block-slug> format.`,
|
|
226
|
+
namespaceMismatch: (input, actualNamespace, expectedNamespace) => `Binding target block "${input}" uses namespace "${actualNamespace}". Expected "${expectedNamespace}".`,
|
|
227
|
+
});
|
|
240
228
|
return {
|
|
241
229
|
attributeName: assertValidBindingAttributeName(attributeName ?? ""),
|
|
242
|
-
blockSlug:
|
|
230
|
+
blockSlug: targetBlock.blockSlug,
|
|
243
231
|
};
|
|
244
232
|
}
|
|
245
233
|
function formatBindingAttributeTypeMember(attributeName) {
|
|
@@ -266,12 +254,6 @@ function getInterfaceDeclaration(source, interfaceName) {
|
|
|
266
254
|
visit(sourceFile);
|
|
267
255
|
return declaration ? { declaration, sourceFile } : undefined;
|
|
268
256
|
}
|
|
269
|
-
function getPropertyNameText(name) {
|
|
270
|
-
if (ts.isIdentifier(name) || ts.isStringLiteral(name) || ts.isNumericLiteral(name)) {
|
|
271
|
-
return name.text;
|
|
272
|
-
}
|
|
273
|
-
return undefined;
|
|
274
|
-
}
|
|
275
257
|
function interfaceHasAttributeMember(declaration, attributeName) {
|
|
276
258
|
return declaration.members.some((member) => ts.isPropertySignature(member) &&
|
|
277
259
|
member.name !== undefined &&
|
|
@@ -722,41 +704,53 @@ async function ensureEditorPluginWebpackAnchors(workspace) {
|
|
|
722
704
|
return nextSource;
|
|
723
705
|
});
|
|
724
706
|
}
|
|
725
|
-
function resolveBindingSourceRegistryPath(projectDir) {
|
|
707
|
+
async function resolveBindingSourceRegistryPath(projectDir) {
|
|
726
708
|
const bindingsDir = path.join(projectDir, "src", "bindings");
|
|
727
|
-
|
|
709
|
+
for (const candidatePath of [
|
|
710
|
+
path.join(bindingsDir, "index.ts"),
|
|
711
|
+
path.join(bindingsDir, "index.js"),
|
|
712
|
+
]) {
|
|
713
|
+
if (await pathExists(candidatePath)) {
|
|
714
|
+
return candidatePath;
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
return path.join(bindingsDir, "index.ts");
|
|
728
718
|
}
|
|
729
719
|
async function writeBindingSourceRegistry(projectDir, bindingSourceSlug) {
|
|
730
720
|
const bindingsDir = path.join(projectDir, "src", "bindings");
|
|
731
|
-
const bindingsIndexPath = resolveBindingSourceRegistryPath(projectDir);
|
|
721
|
+
const bindingsIndexPath = await resolveBindingSourceRegistryPath(projectDir);
|
|
732
722
|
await fsp.mkdir(bindingsDir, { recursive: true });
|
|
733
|
-
const existingBindingSourceSlugs =
|
|
734
|
-
.readdirSync(bindingsDir, { withFileTypes: true })
|
|
723
|
+
const existingBindingSourceSlugs = (await fsp.readdir(bindingsDir, { withFileTypes: true }))
|
|
735
724
|
.filter((entry) => entry.isDirectory())
|
|
736
725
|
.map((entry) => entry.name);
|
|
737
726
|
const nextBindingSourceSlugs = Array.from(new Set([...existingBindingSourceSlugs, bindingSourceSlug])).sort();
|
|
738
727
|
await fsp.writeFile(bindingsIndexPath, buildBindingSourceIndexSource(nextBindingSourceSlugs), "utf8");
|
|
739
728
|
}
|
|
740
|
-
function resolveEditorPluginRegistryPath(projectDir) {
|
|
729
|
+
async function resolveEditorPluginRegistryPath(projectDir) {
|
|
741
730
|
const editorPluginsDir = path.join(projectDir, "src", "editor-plugins");
|
|
742
|
-
|
|
731
|
+
for (const candidatePath of [
|
|
743
732
|
path.join(editorPluginsDir, "index.ts"),
|
|
744
733
|
path.join(editorPluginsDir, "index.js"),
|
|
745
|
-
]
|
|
734
|
+
]) {
|
|
735
|
+
if (await pathExists(candidatePath)) {
|
|
736
|
+
return candidatePath;
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
return path.join(editorPluginsDir, "index.ts");
|
|
746
740
|
}
|
|
747
|
-
function readEditorPluginRegistrySlugs(registryPath) {
|
|
748
|
-
|
|
741
|
+
async function readEditorPluginRegistrySlugs(registryPath) {
|
|
742
|
+
const source = await readOptionalUtf8File(registryPath);
|
|
743
|
+
if (source === null) {
|
|
749
744
|
return [];
|
|
750
745
|
}
|
|
751
|
-
const source = fs.readFileSync(registryPath, "utf8");
|
|
752
746
|
return Array.from(source.matchAll(/^\s*import\s+['"]\.\/([^/'"]+)(?:\/index(?:\.[cm]?[jt]sx?)?)?['"];?\s*$/gmu)).map((match) => match[1]);
|
|
753
747
|
}
|
|
754
748
|
async function writeEditorPluginRegistry(projectDir, editorPluginSlug) {
|
|
755
749
|
const editorPluginsDir = path.join(projectDir, "src", "editor-plugins");
|
|
756
|
-
const registryPath = resolveEditorPluginRegistryPath(projectDir);
|
|
750
|
+
const registryPath = await resolveEditorPluginRegistryPath(projectDir);
|
|
757
751
|
await fsp.mkdir(editorPluginsDir, { recursive: true });
|
|
758
|
-
const existingEditorPluginSlugs =
|
|
759
|
-
const existingRegistrySlugs = readEditorPluginRegistrySlugs(registryPath);
|
|
752
|
+
const existingEditorPluginSlugs = (await readWorkspaceInventoryAsync(projectDir)).editorPlugins.map((entry) => entry.slug);
|
|
753
|
+
const existingRegistrySlugs = await readEditorPluginRegistrySlugs(registryPath);
|
|
760
754
|
const nextEditorPluginSlugs = Array.from(new Set([...existingEditorPluginSlugs, ...existingRegistrySlugs, editorPluginSlug])).sort();
|
|
761
755
|
await fsp.writeFile(registryPath, buildEditorPluginRegistrySource(nextEditorPluginSlugs), "utf8");
|
|
762
756
|
}
|
|
@@ -779,12 +773,12 @@ export async function runAddEditorPluginCommand({ cwd = process.cwd(), editorPlu
|
|
|
779
773
|
const workspace = resolveWorkspaceProject(cwd);
|
|
780
774
|
const editorPluginSlug = assertValidGeneratedSlug("Editor plugin name", normalizeBlockSlug(editorPluginName), "wp-typia add editor-plugin <name> [--slot <sidebar|document-setting-panel>]");
|
|
781
775
|
const resolvedSlot = assertValidEditorPluginSlot(slot);
|
|
782
|
-
const inventory =
|
|
776
|
+
const inventory = await readWorkspaceInventoryAsync(workspace.projectDir);
|
|
783
777
|
assertEditorPluginDoesNotExist(workspace.projectDir, editorPluginSlug, inventory);
|
|
784
778
|
const blockConfigPath = path.join(workspace.projectDir, "scripts", "block-config.ts");
|
|
785
779
|
const bootstrapPath = getWorkspaceBootstrapPath(workspace);
|
|
786
780
|
const buildScriptPath = path.join(workspace.projectDir, "scripts", "build-workspace.mjs");
|
|
787
|
-
const editorPluginsIndexPath = resolveEditorPluginRegistryPath(workspace.projectDir);
|
|
781
|
+
const editorPluginsIndexPath = await resolveEditorPluginRegistryPath(workspace.projectDir);
|
|
788
782
|
const webpackConfigPath = path.join(workspace.projectDir, "webpack.config.js");
|
|
789
783
|
const editorPluginDir = path.join(workspace.projectDir, "src", "editor-plugins", editorPluginSlug);
|
|
790
784
|
const entryFilePath = path.join(editorPluginDir, "index.tsx");
|
|
@@ -848,7 +842,7 @@ export async function runAddEditorPluginCommand({ cwd = process.cwd(), editorPlu
|
|
|
848
842
|
export async function runAddPatternCommand({ cwd = process.cwd(), patternName, }) {
|
|
849
843
|
const workspace = resolveWorkspaceProject(cwd);
|
|
850
844
|
const patternSlug = assertValidGeneratedSlug("Pattern name", normalizeBlockSlug(patternName), "wp-typia add pattern <name>");
|
|
851
|
-
const inventory =
|
|
845
|
+
const inventory = await readWorkspaceInventoryAsync(workspace.projectDir);
|
|
852
846
|
assertPatternDoesNotExist(workspace.projectDir, patternSlug, inventory);
|
|
853
847
|
const blockConfigPath = path.join(workspace.projectDir, "scripts", "block-config.ts");
|
|
854
848
|
const bootstrapPath = getWorkspaceBootstrapPath(workspace);
|
|
@@ -898,7 +892,7 @@ export async function runAddPatternCommand({ cwd = process.cwd(), patternName, }
|
|
|
898
892
|
export async function runAddBindingSourceCommand({ attributeName, bindingSourceName, blockName, cwd = process.cwd(), }) {
|
|
899
893
|
const workspace = resolveWorkspaceProject(cwd);
|
|
900
894
|
const bindingSourceSlug = assertValidGeneratedSlug("Binding source name", normalizeBlockSlug(bindingSourceName), "wp-typia add binding-source <name> [--block <block-slug|namespace/block-slug> --attribute <attribute>]");
|
|
901
|
-
const inventory =
|
|
895
|
+
const inventory = await readWorkspaceInventoryAsync(workspace.projectDir);
|
|
902
896
|
assertBindingSourceDoesNotExist(workspace.projectDir, bindingSourceSlug, inventory);
|
|
903
897
|
const target = resolveBindingTarget({
|
|
904
898
|
attributeName,
|
|
@@ -907,7 +901,7 @@ export async function runAddBindingSourceCommand({ attributeName, bindingSourceN
|
|
|
907
901
|
const targetBlock = target ? resolveWorkspaceBlock(inventory, target.blockSlug) : undefined;
|
|
908
902
|
const blockConfigPath = path.join(workspace.projectDir, "scripts", "block-config.ts");
|
|
909
903
|
const bootstrapPath = getWorkspaceBootstrapPath(workspace);
|
|
910
|
-
const bindingsIndexPath = resolveBindingSourceRegistryPath(workspace.projectDir);
|
|
904
|
+
const bindingsIndexPath = await resolveBindingSourceRegistryPath(workspace.projectDir);
|
|
911
905
|
const bindingSourceDir = path.join(workspace.projectDir, "src", "bindings", bindingSourceSlug);
|
|
912
906
|
const serverFilePath = path.join(bindingSourceDir, "server.php");
|
|
913
907
|
const editorFilePath = path.join(bindingSourceDir, "editor.ts");
|
|
@@ -7,7 +7,7 @@ import { buildRestResourceApiSource, buildRestResourceConfigEntry, buildRestReso
|
|
|
7
7
|
import { quotePhpString } from "./php-utils.js";
|
|
8
8
|
import { syncRestResourceArtifacts } from "./rest-resource-artifacts.js";
|
|
9
9
|
import { toPascalCase, toTitleCase } from "./string-case.js";
|
|
10
|
-
import { appendWorkspaceInventoryEntries,
|
|
10
|
+
import { appendWorkspaceInventoryEntries, readWorkspaceInventoryAsync, } from "./workspace-inventory.js";
|
|
11
11
|
import { resolveWorkspaceProject } from "./workspace-project.js";
|
|
12
12
|
function buildRestResourceRouteRegistrations(restResourceSlug, methods, functions) {
|
|
13
13
|
const collectionRoutes = [];
|
|
@@ -434,7 +434,7 @@ export async function runAddRestResourceCommand({ cwd = process.cwd(), methods,
|
|
|
434
434
|
const restResourceSlug = assertValidGeneratedSlug("REST resource name", normalizeBlockSlug(restResourceName), "wp-typia add rest-resource <name> [--namespace <vendor/v1>] [--methods <list,read,create>]");
|
|
435
435
|
const resolvedMethods = assertValidRestResourceMethods(methods);
|
|
436
436
|
const resolvedNamespace = resolveRestResourceNamespace(workspace.workspace.namespace, namespace);
|
|
437
|
-
const inventory =
|
|
437
|
+
const inventory = await readWorkspaceInventoryAsync(workspace.projectDir);
|
|
438
438
|
assertRestResourceDoesNotExist(workspace.projectDir, restResourceSlug, inventory);
|
|
439
439
|
const blockConfigPath = path.join(workspace.projectDir, "scripts", "block-config.ts");
|
|
440
440
|
const bootstrapPath = getWorkspaceBootstrapPath(workspace);
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { promises as fsp } from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { pathExists } from "./fs-async.js";
|
|
4
|
+
import { assertFullBlockName, resolveWorkspaceTargetBlockName, } from "./block-targets.js";
|
|
4
5
|
import { resolveWorkspaceProject } from "./workspace-project.js";
|
|
5
|
-
import { appendWorkspaceInventoryEntries,
|
|
6
|
+
import { appendWorkspaceInventoryEntries, readWorkspaceInventoryAsync, } from "./workspace-inventory.js";
|
|
6
7
|
import { toKebabCase, toSnakeCase, toTitleCase } from "./string-case.js";
|
|
7
8
|
import { assertBlockStyleDoesNotExist, assertBlockTransformDoesNotExist, assertValidGeneratedSlug, assertValidHookAnchor, assertValidHookedBlockPosition, assertVariationDoesNotExist, getMutableBlockHooks, normalizeBlockSlug, patchFile, quoteTsString, readWorkspaceBlockJson, resolveWorkspaceBlock, rollbackWorkspaceMutation, snapshotWorkspaceFiles, } from "./cli-add-shared.js";
|
|
8
9
|
import { findExecutablePatternMatch, hasExecutablePattern, hasUncommentedPattern, maskTypeScriptCommentsAndLiterals, } from "./ts-source-masking.js";
|
|
@@ -19,7 +20,6 @@ const BLOCK_TRANSFORMS_IMPORT_PATTERN = /^\s*import\s*\{\s*applyWorkspaceBlockTr
|
|
|
19
20
|
const BLOCK_TRANSFORMS_CALL_LINE = "applyWorkspaceBlockTransforms(registration.settings);";
|
|
20
21
|
const BLOCK_TRANSFORMS_CALL_PATTERN = /applyWorkspaceBlockTransforms\s*\(\s*registration\s*\.\s*settings\s*\)\s*;?/u;
|
|
21
22
|
const SCAFFOLD_REGISTRATION_SETTINGS_CALL_PATTERN = /registerScaffoldBlockType\s*\(\s*registration\s*\.\s*name\s*,\s*registration\s*\.\s*settings\s*\)\s*;?/u;
|
|
22
|
-
const FULL_BLOCK_NAME_PATTERN = /^[a-z0-9-]+\/[a-z0-9-]+$/u;
|
|
23
23
|
function isIdentifierBoundary(source, index) {
|
|
24
24
|
if (index < 0 || index >= source.length) {
|
|
25
25
|
return true;
|
|
@@ -407,38 +407,6 @@ async function writeBlockTransformRegistry(projectDir, blockSlug, transformSlug)
|
|
|
407
407
|
const nextTransformSlugs = Array.from(new Set([...existingTransformSlugs, transformSlug])).sort();
|
|
408
408
|
await fsp.writeFile(transformsIndexPath, buildBlockTransformIndexSource(nextTransformSlugs), "utf8");
|
|
409
409
|
}
|
|
410
|
-
function assertFullBlockName(blockName, flagName) {
|
|
411
|
-
const trimmed = blockName.trim();
|
|
412
|
-
if (!trimmed) {
|
|
413
|
-
throw new Error(`\`${flagName}\` requires a block name.`);
|
|
414
|
-
}
|
|
415
|
-
if (!FULL_BLOCK_NAME_PATTERN.test(trimmed)) {
|
|
416
|
-
throw new Error(`\`${flagName}\` must use <namespace/block-slug> format.`);
|
|
417
|
-
}
|
|
418
|
-
return trimmed;
|
|
419
|
-
}
|
|
420
|
-
function resolveWorkspaceTargetBlockName(blockName, namespace, flagName) {
|
|
421
|
-
const trimmed = blockName.trim();
|
|
422
|
-
if (!trimmed) {
|
|
423
|
-
throw new Error(`\`${flagName}\` requires <block-slug|namespace/block-slug>.`);
|
|
424
|
-
}
|
|
425
|
-
const blockNameSegments = trimmed.split("/");
|
|
426
|
-
if (blockNameSegments.length > 2 ||
|
|
427
|
-
blockNameSegments.some((segment) => segment.trim() === "")) {
|
|
428
|
-
throw new Error(`\`${flagName}\` must use <block-slug|namespace/block-slug> format.`);
|
|
429
|
-
}
|
|
430
|
-
const [maybeNamespace, maybeSlug] = blockNameSegments.length === 2
|
|
431
|
-
? blockNameSegments
|
|
432
|
-
: [undefined, blockNameSegments[0]];
|
|
433
|
-
if (maybeNamespace && maybeNamespace !== namespace) {
|
|
434
|
-
throw new Error(`\`${flagName}\` references namespace "${maybeNamespace}". Expected "${namespace}".`);
|
|
435
|
-
}
|
|
436
|
-
const blockSlug = normalizeBlockSlug(maybeSlug ?? "");
|
|
437
|
-
return {
|
|
438
|
-
blockName: `${namespace}/${blockSlug}`,
|
|
439
|
-
blockSlug,
|
|
440
|
-
};
|
|
441
|
-
}
|
|
442
410
|
/**
|
|
443
411
|
* Re-export the DataViews admin screen scaffold workflow from the focused
|
|
444
412
|
* admin-view runtime helper module.
|
|
@@ -484,7 +452,7 @@ export async function runAddVariationCommand({ blockName, cwd = process.cwd(), v
|
|
|
484
452
|
const workspace = resolveWorkspaceProject(cwd);
|
|
485
453
|
const blockSlug = normalizeBlockSlug(blockName);
|
|
486
454
|
const variationSlug = assertValidGeneratedSlug("Variation name", normalizeBlockSlug(variationName), "wp-typia add variation <name> --block <block-slug>");
|
|
487
|
-
const inventory =
|
|
455
|
+
const inventory = await readWorkspaceInventoryAsync(workspace.projectDir);
|
|
488
456
|
resolveWorkspaceBlock(inventory, blockSlug);
|
|
489
457
|
assertVariationDoesNotExist(workspace.projectDir, blockSlug, variationSlug, inventory);
|
|
490
458
|
const blockConfigPath = path.join(workspace.projectDir, "scripts", "block-config.ts");
|
|
@@ -544,7 +512,7 @@ export async function runAddBlockStyleCommand({ blockName, cwd = process.cwd(),
|
|
|
544
512
|
const workspace = resolveWorkspaceProject(cwd);
|
|
545
513
|
const blockSlug = normalizeBlockSlug(blockName);
|
|
546
514
|
const styleSlug = assertValidGeneratedSlug("Style name", normalizeBlockSlug(styleName), "wp-typia add style <name> --block <block-slug>");
|
|
547
|
-
const inventory =
|
|
515
|
+
const inventory = await readWorkspaceInventoryAsync(workspace.projectDir);
|
|
548
516
|
resolveWorkspaceBlock(inventory, blockSlug);
|
|
549
517
|
assertBlockStyleDoesNotExist(workspace.projectDir, blockSlug, styleSlug, inventory);
|
|
550
518
|
const blockConfigPath = path.join(workspace.projectDir, "scripts", "block-config.ts");
|
|
@@ -613,7 +581,7 @@ export async function runAddBlockTransformCommand({ cwd = process.cwd(), fromBlo
|
|
|
613
581
|
const transformSlug = assertValidGeneratedSlug("Transform name", normalizeBlockSlug(transformName), "wp-typia add transform <name> --from <namespace/block> --to <block-slug|namespace/block-slug>");
|
|
614
582
|
const resolvedFromBlockName = assertFullBlockName(fromBlockName, "--from");
|
|
615
583
|
const target = resolveWorkspaceTargetBlockName(toBlockName, workspace.workspace.namespace, "--to");
|
|
616
|
-
const inventory =
|
|
584
|
+
const inventory = await readWorkspaceInventoryAsync(workspace.projectDir);
|
|
617
585
|
resolveWorkspaceBlock(inventory, target.blockSlug);
|
|
618
586
|
assertBlockTransformDoesNotExist(workspace.projectDir, target.blockSlug, transformSlug, inventory);
|
|
619
587
|
const blockConfigPath = path.join(workspace.projectDir, "scripts", "block-config.ts");
|
|
@@ -684,7 +652,7 @@ export async function runAddBlockTransformCommand({ cwd = process.cwd(), fromBlo
|
|
|
684
652
|
export async function runAddHookedBlockCommand({ anchorBlockName, blockName, cwd = process.cwd(), position, }) {
|
|
685
653
|
const workspace = resolveWorkspaceProject(cwd);
|
|
686
654
|
const blockSlug = normalizeBlockSlug(blockName);
|
|
687
|
-
const inventory =
|
|
655
|
+
const inventory = await readWorkspaceInventoryAsync(workspace.projectDir);
|
|
688
656
|
resolveWorkspaceBlock(inventory, blockSlug);
|
|
689
657
|
const resolvedAnchorBlockName = assertValidHookAnchor(anchorBlockName);
|
|
690
658
|
const resolvedPosition = assertValidHookedBlockPosition(position);
|
|
@@ -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";
|
package/dist/runtime/cli-add.js
CHANGED
|
@@ -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`,
|
|
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";
|
package/dist/runtime/cli-core.js
CHANGED
|
@@ -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`,
|
|
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";
|
|
@@ -226,6 +226,12 @@ export function createCliDiagnosticCodeError(code, message, options) {
|
|
|
226
226
|
error.code = code;
|
|
227
227
|
return error;
|
|
228
228
|
}
|
|
229
|
+
/**
|
|
230
|
+
* Compatibility-only fallback for legacy or third-party errors that have not
|
|
231
|
+
* yet been tagged by their throw site. New user-facing failures should pass an
|
|
232
|
+
* explicit code through `createCliDiagnosticCodeError()` or
|
|
233
|
+
* `createCliCommandError({ code })` instead of relying on message matching.
|
|
234
|
+
*/
|
|
229
235
|
function inferCliDiagnosticCode(options) {
|
|
230
236
|
const inheritedCode = readCliDiagnosticCode(options.error);
|
|
231
237
|
if (inheritedCode) {
|
|
@@ -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,14 +1,15 @@
|
|
|
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";
|
|
5
4
|
import { parseAlternateRenderTargets } from "./alternate-render-targets.js";
|
|
6
5
|
import { parseCompoundInnerBlocksPreset } from "./compound-inner-blocks.js";
|
|
6
|
+
import { isCompoundPersistenceEnabled } from "./scaffold-template-variable-groups.js";
|
|
7
7
|
import { formatInstallCommand, formatRunScript, } from "./package-managers.js";
|
|
8
8
|
import { getPrimaryDevelopmentScript } from "./local-dev-presets.js";
|
|
9
9
|
import { createManagedTempRoot } from "./temp-roots.js";
|
|
10
10
|
import { getOptionalOnboardingNote, getOptionalOnboardingShortNote, getOptionalOnboardingSteps, } from "./scaffold-onboarding.js";
|
|
11
11
|
import { formatNonEmptyTargetDirectoryError } from "./scaffold-bootstrap.js";
|
|
12
|
+
import { pathExists } from "./fs-async.js";
|
|
12
13
|
import { OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE, isBuiltInTemplateId, } from "./template-registry.js";
|
|
13
14
|
import { resolveOptionalInteractiveExternalLayerId, } from "./external-layer-selection.js";
|
|
14
15
|
import { assertBuiltInTemplateVariantAllowed, resolveLocalCliPathOption, normalizeOptionalCliString, } from "./cli-validation.js";
|
|
@@ -31,7 +32,7 @@ async function listRelativeProjectFiles(rootDir) {
|
|
|
31
32
|
return relativeFiles.sort((left, right) => left.localeCompare(right));
|
|
32
33
|
}
|
|
33
34
|
async function assertDryRunTargetDirectoryReady(projectDir, allowExistingDir) {
|
|
34
|
-
if (!
|
|
35
|
+
if (!(await pathExists(projectDir)) || allowExistingDir) {
|
|
35
36
|
return;
|
|
36
37
|
}
|
|
37
38
|
const entries = await fsp.readdir(projectDir);
|
|
@@ -424,7 +425,7 @@ export async function runScaffoldFlow({ projectInput, cwd = process.cwd(), templ
|
|
|
424
425
|
let availableScripts;
|
|
425
426
|
if (!dryRun) {
|
|
426
427
|
try {
|
|
427
|
-
const parsedPackageJson = JSON.parse(
|
|
428
|
+
const parsedPackageJson = JSON.parse(await fsp.readFile(path.join(projectDir, "package.json"), "utf8"));
|
|
428
429
|
const scripts = parsedPackageJson.scripts &&
|
|
429
430
|
typeof parsedPackageJson.scripts === "object" &&
|
|
430
431
|
!Array.isArray(parsedPackageJson.scripts)
|
|
@@ -444,7 +445,7 @@ export async function runScaffoldFlow({ projectInput, cwd = process.cwd(), templ
|
|
|
444
445
|
availableScripts,
|
|
445
446
|
packageManager: resolvedPackageManager,
|
|
446
447
|
templateId: resolvedTemplateId,
|
|
447
|
-
compoundPersistenceEnabled: resolvedResult.result.variables
|
|
448
|
+
compoundPersistenceEnabled: isCompoundPersistenceEnabled(resolvedResult.result.variables),
|
|
448
449
|
}),
|
|
449
450
|
plan: resolvedResult.plan,
|
|
450
451
|
projectDir,
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare const CREATE_TEMPLATE_SELECTION_HINT: string;
|
|
2
|
+
/**
|
|
3
|
+
* Validate an explicitly supplied create template id before entering the full
|
|
4
|
+
* scaffold flow.
|
|
5
|
+
*
|
|
6
|
+
* Built-in template ids and the workspace alias resolve immediately, common
|
|
7
|
+
* built-in typos keep suggestion diagnostics, and explicit external template
|
|
8
|
+
* locators remain deferred to the external template resolver.
|
|
9
|
+
*/
|
|
10
|
+
export declare function validateExplicitCreateTemplateId(templateId: string): string;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { CLI_DIAGNOSTIC_CODES, createCliDiagnosticCodeError, } from "./cli-diagnostics.js";
|
|
3
|
+
import { OFFICIAL_WORKSPACE_TEMPLATE_ALIAS, OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE, TEMPLATE_IDS, getTemplateById, isBuiltInTemplateId, } from "./template-registry.js";
|
|
4
|
+
import { getRemovedBuiltInTemplateMessage, isRemovedBuiltInTemplateId, } from "./template-defaults.js";
|
|
5
|
+
import { parseNpmTemplateLocator } from "./template-source-locators.js";
|
|
6
|
+
import { suggestCloseId } from "./id-suggestions.js";
|
|
7
|
+
export const CREATE_TEMPLATE_SELECTION_HINT = `--template <${[
|
|
8
|
+
...TEMPLATE_IDS,
|
|
9
|
+
OFFICIAL_WORKSPACE_TEMPLATE_ALIAS,
|
|
10
|
+
].join("|")}|./path|github:owner/repo/path[#ref]|npm-package>`;
|
|
11
|
+
const TEMPLATE_SUGGESTION_IDS = [
|
|
12
|
+
...TEMPLATE_IDS,
|
|
13
|
+
OFFICIAL_WORKSPACE_TEMPLATE_ALIAS,
|
|
14
|
+
];
|
|
15
|
+
const USER_FACING_TEMPLATE_IDS = [
|
|
16
|
+
...TEMPLATE_IDS,
|
|
17
|
+
OFFICIAL_WORKSPACE_TEMPLATE_ALIAS,
|
|
18
|
+
];
|
|
19
|
+
function normalizeCreateTemplateSelection(templateId) {
|
|
20
|
+
return templateId === OFFICIAL_WORKSPACE_TEMPLATE_ALIAS
|
|
21
|
+
? OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE
|
|
22
|
+
: templateId;
|
|
23
|
+
}
|
|
24
|
+
function looksLikeWindowsAbsoluteTemplatePath(templateId) {
|
|
25
|
+
return /^[a-z]:[\\/]/iu.test(templateId) || /^\\\\[^\\]+\\[^\\]+/u.test(templateId);
|
|
26
|
+
}
|
|
27
|
+
function looksLikeExplicitNonNpmExternalTemplateLocator(templateId) {
|
|
28
|
+
return (path.isAbsolute(templateId) ||
|
|
29
|
+
looksLikeWindowsAbsoluteTemplatePath(templateId) ||
|
|
30
|
+
templateId.startsWith("./") ||
|
|
31
|
+
templateId.startsWith(".\\") ||
|
|
32
|
+
templateId.startsWith("../") ||
|
|
33
|
+
templateId.startsWith("..\\") ||
|
|
34
|
+
templateId.startsWith("@") ||
|
|
35
|
+
templateId.startsWith("github:") ||
|
|
36
|
+
templateId.includes("/") ||
|
|
37
|
+
templateId.includes("\\"));
|
|
38
|
+
}
|
|
39
|
+
function looksLikeExplicitCreateExternalTemplateLocator(templateId) {
|
|
40
|
+
return (looksLikeExplicitNonNpmExternalTemplateLocator(templateId) ||
|
|
41
|
+
parseNpmTemplateLocator(templateId) !== null);
|
|
42
|
+
}
|
|
43
|
+
function findMistypedBuiltInTemplateSuggestion(templateId) {
|
|
44
|
+
const normalizedTemplateId = templateId.trim().toLowerCase();
|
|
45
|
+
if (normalizedTemplateId.length === 0 ||
|
|
46
|
+
looksLikeExplicitNonNpmExternalTemplateLocator(normalizedTemplateId)) {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
return suggestCloseId(normalizedTemplateId, TEMPLATE_SUGGESTION_IDS);
|
|
50
|
+
}
|
|
51
|
+
function getMistypedBuiltInTemplateMessage(templateId) {
|
|
52
|
+
const suggestion = findMistypedBuiltInTemplateSuggestion(templateId);
|
|
53
|
+
if (!suggestion) {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
const suggestionDescription = suggestion === OFFICIAL_WORKSPACE_TEMPLATE_ALIAS
|
|
57
|
+
? "official workspace scaffold"
|
|
58
|
+
: "built-in scaffold";
|
|
59
|
+
return `Unknown template "${templateId}". Did you mean "${suggestion}"? Use \`--template ${suggestion}\` for the ${suggestionDescription}, or pass a local path, \`github:owner/repo/path[#ref]\`, or an npm package spec for an external template.`;
|
|
60
|
+
}
|
|
61
|
+
function getUnknownTemplateMessage(templateId) {
|
|
62
|
+
return [
|
|
63
|
+
`Unknown template "${templateId}". Expected one of: ${USER_FACING_TEMPLATE_IDS.join(", ")}.`,
|
|
64
|
+
"Run `wp-typia templates list` to inspect available templates.",
|
|
65
|
+
"Pass an explicit external template locator such as `./path`, `github:owner/repo/path[#ref]`, or `@scope/template` for custom templates.",
|
|
66
|
+
].join(" ");
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Validate an explicitly supplied create template id before entering the full
|
|
70
|
+
* scaffold flow.
|
|
71
|
+
*
|
|
72
|
+
* Built-in template ids and the workspace alias resolve immediately, common
|
|
73
|
+
* built-in typos keep suggestion diagnostics, and explicit external template
|
|
74
|
+
* locators remain deferred to the external template resolver.
|
|
75
|
+
*/
|
|
76
|
+
export function validateExplicitCreateTemplateId(templateId) {
|
|
77
|
+
const normalizedTemplateId = normalizeCreateTemplateSelection(templateId);
|
|
78
|
+
if (isRemovedBuiltInTemplateId(templateId)) {
|
|
79
|
+
throw createCliDiagnosticCodeError(CLI_DIAGNOSTIC_CODES.UNKNOWN_TEMPLATE, getRemovedBuiltInTemplateMessage(templateId));
|
|
80
|
+
}
|
|
81
|
+
if (normalizedTemplateId === OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE) {
|
|
82
|
+
return normalizedTemplateId;
|
|
83
|
+
}
|
|
84
|
+
if (isBuiltInTemplateId(normalizedTemplateId)) {
|
|
85
|
+
return getTemplateById(normalizedTemplateId).id;
|
|
86
|
+
}
|
|
87
|
+
const mistypedBuiltInTemplateMessage = getMistypedBuiltInTemplateMessage(templateId);
|
|
88
|
+
if (mistypedBuiltInTemplateMessage) {
|
|
89
|
+
throw createCliDiagnosticCodeError(CLI_DIAGNOSTIC_CODES.UNKNOWN_TEMPLATE, mistypedBuiltInTemplateMessage);
|
|
90
|
+
}
|
|
91
|
+
if (!looksLikeExplicitCreateExternalTemplateLocator(normalizedTemplateId)) {
|
|
92
|
+
throw createCliDiagnosticCodeError(CLI_DIAGNOSTIC_CODES.UNKNOWN_TEMPLATE, getUnknownTemplateMessage(templateId));
|
|
93
|
+
}
|
|
94
|
+
return normalizedTemplateId;
|
|
95
|
+
}
|
|
@@ -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
|
+
}
|