@wp-typia/project-tools 0.20.1 → 0.20.2

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.
@@ -281,7 +281,7 @@ export async function runAddBlockCommand({ alternateRenderTargets, blockName, cw
281
281
  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.");
282
282
  }
283
283
  if (!isAddBlockTemplateId(templateId)) {
284
- throw new Error(`Unknown add-block template "${templateId}". Expected one of: ${ADD_BLOCK_TEMPLATE_IDS.join(", ")}`);
284
+ 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.`);
285
285
  }
286
286
  const resolvedTemplateId = templateId;
287
287
  assertPersistenceFlagsAllowed(resolvedTemplateId, {
@@ -19,6 +19,7 @@ export declare const CLI_DIAGNOSTIC_CODES: {
19
19
  readonly OUTSIDE_PROJECT_ROOT: "outside-project-root";
20
20
  readonly TEMPLATE_SOURCE_TIMEOUT: "template-source-timeout";
21
21
  readonly TEMPLATE_SOURCE_TOO_LARGE: "template-source-too-large";
22
+ readonly UNKNOWN_TEMPLATE: "unknown-template";
22
23
  readonly UNSUPPORTED_COMMAND: "unsupported-command";
23
24
  };
24
25
  export type CliDiagnosticCode = (typeof CLI_DIAGNOSTIC_CODES)[keyof typeof CLI_DIAGNOSTIC_CODES];
@@ -10,6 +10,7 @@ export const CLI_DIAGNOSTIC_CODES = {
10
10
  OUTSIDE_PROJECT_ROOT: "outside-project-root",
11
11
  TEMPLATE_SOURCE_TIMEOUT: "template-source-timeout",
12
12
  TEMPLATE_SOURCE_TOO_LARGE: "template-source-too-large",
13
+ UNKNOWN_TEMPLATE: "unknown-template",
13
14
  UNSUPPORTED_COMMAND: "unsupported-command",
14
15
  };
15
16
  const DEFAULT_CLI_FAILURE_SUMMARIES = {
@@ -183,7 +184,10 @@ function inferCliDiagnosticCode(options) {
183
184
  if (/requires <|requires --|requires a value/u.test(haystack)) {
184
185
  return CLI_DIAGNOSTIC_CODES.MISSING_ARGUMENT;
185
186
  }
186
- if (/Unknown .*subcommand|Unknown add kind|Unknown template|removed in favor|does not support|The Bun-free fallback runtime does not support|The positional alias only accepts/u.test(haystack)) {
187
+ if (/Unknown (?:add-block )?template\s+(?:"|\\")/u.test(haystack)) {
188
+ return CLI_DIAGNOSTIC_CODES.UNKNOWN_TEMPLATE;
189
+ }
190
+ if (/Unknown .*subcommand|Unknown add kind|removed in favor|does not support|The Bun-free fallback runtime does not support|The positional alias only accepts/u.test(haystack)) {
187
191
  return haystack.includes("does not support") ||
188
192
  haystack.includes("The Bun-free fallback runtime does not support")
189
193
  ? CLI_DIAGNOSTIC_CODES.UNSUPPORTED_COMMAND
@@ -1,8 +1,10 @@
1
1
  import { execSync } from 'node:child_process';
2
+ import path from 'node:path';
2
3
  import { PACKAGE_MANAGER_IDS, getPackageManager, } from './package-managers.js';
3
4
  import { normalizeBlockSlug, resolveScaffoldIdentifiers, validateBlockSlug, validateNamespace, } from './scaffold-identifiers.js';
4
5
  import { OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE, TEMPLATE_IDS, getTemplateById, isBuiltInTemplateId, } from './template-registry.js';
5
6
  import { getRemovedBuiltInTemplateMessage, isRemovedBuiltInTemplateId, } from './template-defaults.js';
7
+ import { parseNpmTemplateLocator } from './template-source-locators.js';
6
8
  import { toSnakeCase, toTitleCase, } from './string-case.js';
7
9
  const WORKSPACE_TEMPLATE_ALIAS = 'workspace';
8
10
  const TEMPLATE_SELECTION_HINT = `--template <${[
@@ -11,6 +13,10 @@ const TEMPLATE_SELECTION_HINT = `--template <${[
11
13
  ].join('|')}|./path|github:owner/repo/path[#ref]|npm-package>`;
12
14
  const TEMPLATE_SUGGESTION_IDS = [...TEMPLATE_IDS, WORKSPACE_TEMPLATE_ALIAS];
13
15
  const QUERY_POST_TYPE_RULE = 'Use lowercase, 1-20 chars, and only a-z, 0-9, "_" or "-".';
16
+ const USER_FACING_TEMPLATE_IDS = [
17
+ ...TEMPLATE_IDS,
18
+ WORKSPACE_TEMPLATE_ALIAS,
19
+ ];
14
20
  /**
15
21
  * Detect the current author name from local Git config.
16
22
  *
@@ -78,14 +84,22 @@ function normalizeTemplateSelection(templateId) {
78
84
  ? OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE
79
85
  : templateId;
80
86
  }
81
- function looksLikeExplicitExternalTemplateLocator(templateId) {
82
- return (templateId.startsWith('./') ||
87
+ function looksLikeWindowsAbsoluteTemplatePath(templateId) {
88
+ return /^[a-z]:[\\/]/iu.test(templateId) || /^\\\\[^\\]+\\[^\\]+/u.test(templateId);
89
+ }
90
+ function looksLikeExplicitNonNpmExternalTemplateLocator(templateId) {
91
+ return (path.isAbsolute(templateId) ||
92
+ looksLikeWindowsAbsoluteTemplatePath(templateId) ||
93
+ templateId.startsWith('./') ||
83
94
  templateId.startsWith('../') ||
84
- templateId.startsWith('/') ||
85
95
  templateId.startsWith('@') ||
86
96
  templateId.startsWith('github:') ||
87
97
  templateId.includes('/'));
88
98
  }
99
+ function looksLikeExplicitExternalTemplateLocator(templateId) {
100
+ return (looksLikeExplicitNonNpmExternalTemplateLocator(templateId) ||
101
+ parseNpmTemplateLocator(templateId) !== null);
102
+ }
89
103
  function getEditDistance(left, right) {
90
104
  const previous = Array.from({ length: right.length + 1 }, (_, index) => index);
91
105
  const current = new Array(right.length + 1);
@@ -104,7 +118,7 @@ function getEditDistance(left, right) {
104
118
  function findMistypedBuiltInTemplateSuggestion(templateId) {
105
119
  const normalizedTemplateId = templateId.trim().toLowerCase();
106
120
  if (normalizedTemplateId.length === 0 ||
107
- looksLikeExplicitExternalTemplateLocator(normalizedTemplateId)) {
121
+ looksLikeExplicitNonNpmExternalTemplateLocator(normalizedTemplateId)) {
108
122
  return null;
109
123
  }
110
124
  let bestCandidate = null;
@@ -132,6 +146,13 @@ function getMistypedBuiltInTemplateMessage(templateId) {
132
146
  : 'built-in scaffold';
133
147
  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.`;
134
148
  }
149
+ function getUnknownTemplateMessage(templateId) {
150
+ return [
151
+ `Unknown template "${templateId}". Expected one of: ${USER_FACING_TEMPLATE_IDS.join(', ')}.`,
152
+ 'Run `wp-typia templates list` to inspect available templates.',
153
+ 'Pass an explicit external template locator such as `./path`, `github:owner/repo/path[#ref]`, or `@scope/template` for custom templates.',
154
+ ].join(' ');
155
+ }
135
156
  /**
136
157
  * Resolve the scaffold template id from flags, defaults, and interactive selection.
137
158
  *
@@ -154,6 +175,9 @@ export async function resolveTemplateId({ templateId, yes = false, isInteractive
154
175
  if (mistypedBuiltInTemplateMessage) {
155
176
  throw new Error(mistypedBuiltInTemplateMessage);
156
177
  }
178
+ if (!looksLikeExplicitExternalTemplateLocator(normalizedTemplateId)) {
179
+ throw new Error(getUnknownTemplateMessage(templateId));
180
+ }
157
181
  return normalizedTemplateId;
158
182
  }
159
183
  if (yes) {
@@ -190,7 +214,7 @@ export async function resolvePackageManagerId({ packageManager, yes = false, isI
190
214
  */
191
215
  export async function collectScaffoldAnswers({ projectName, templateId, yes = false, dataStorageMode, namespace, persistencePolicy, phpPrefix, promptText, queryPostType, textDomain, }) {
192
216
  const defaults = getDefaultAnswers(projectName, templateId);
193
- if (yes) {
217
+ if (yes || (!isBuiltInTemplateId(templateId) && !promptText)) {
194
218
  const identifiers = resolveScaffoldIdentifiers({
195
219
  namespace: namespace ?? defaults.namespace,
196
220
  phpPrefix,
@@ -6,9 +6,20 @@ import { spawnSync } from 'node:child_process';
6
6
  import semver from 'semver';
7
7
  import { x as extractTarball } from 'tar';
8
8
  import { createExternalTemplateTimeoutError, fetchWithExternalTemplateTimeout, getExternalTemplateMetadataMaxBytes, getExternalTemplateTarballMaxBytes, getExternalTemplateTimeoutMs, readBufferResponseWithLimit, readJsonResponseWithLimit, } from './external-template-guards.js';
9
- import { OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE, PROJECT_TOOLS_PACKAGE_ROOT, } from './template-registry.js';
9
+ import { OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE, OFFICIAL_WORKSPACE_TEMPLATE_ALIAS, PROJECT_TOOLS_PACKAGE_ROOT, TEMPLATE_IDS, } from './template-registry.js';
10
10
  import { isPlainObject } from './object-utils.js';
11
11
  import { createManagedTempRoot } from './temp-roots.js';
12
+ const USER_FACING_TEMPLATE_IDS = [
13
+ ...TEMPLATE_IDS,
14
+ OFFICIAL_WORKSPACE_TEMPLATE_ALIAS,
15
+ ];
16
+ function getUnknownNpmTemplateMessage(templateId) {
17
+ return [
18
+ `Unknown template "${templateId}". Expected one of: ${USER_FACING_TEMPLATE_IDS.join(', ')}.`,
19
+ 'Run `wp-typia templates list` to inspect available templates.',
20
+ 'If you meant an npm template package, verify the package name and configured npm registry.',
21
+ ].join(' ');
22
+ }
12
23
  function selectRegistryVersion(metadata, locator) {
13
24
  const distTags = isPlainObject(metadata['dist-tags'])
14
25
  ? metadata['dist-tags']
@@ -49,6 +60,9 @@ async function fetchNpmTemplateSource(locator) {
49
60
  label: metadataLabel,
50
61
  });
51
62
  if (!metadataResponse.ok) {
63
+ if (metadataResponse.status === 404) {
64
+ throw new Error(getUnknownNpmTemplateMessage(locator.raw));
65
+ }
52
66
  throw new Error(`Failed to fetch npm template metadata for ${locator.raw}: ${metadataResponse.status}`);
53
67
  }
54
68
  const metadata = await readJsonResponseWithLimit(metadataResponse, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wp-typia/project-tools",
3
- "version": "0.20.1",
3
+ "version": "0.20.2",
4
4
  "description": "Project orchestration and programmatic tooling for wp-typia",
5
5
  "packageManager": "bun@1.3.11",
6
6
  "type": "module",