@wp-typia/project-tools 0.17.0 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/dist/runtime/alternate-render-targets.d.ts +5 -0
  2. package/dist/runtime/alternate-render-targets.js +29 -0
  3. package/dist/runtime/block-generator-service-core.d.ts +2 -2
  4. package/dist/runtime/block-generator-service-core.js +13 -8
  5. package/dist/runtime/block-generator-service-spec.d.ts +10 -2
  6. package/dist/runtime/block-generator-service-spec.js +43 -1
  7. package/dist/runtime/built-in-block-artifacts.js +1 -0
  8. package/dist/runtime/built-in-block-code-templates/compound-child.d.ts +2 -2
  9. package/dist/runtime/built-in-block-code-templates/compound-child.js +35 -2
  10. package/dist/runtime/built-in-block-code-templates/compound-parent.d.ts +2 -2
  11. package/dist/runtime/built-in-block-code-templates/compound-parent.js +204 -27
  12. package/dist/runtime/built-in-block-code-templates/compound-persistence.d.ts +1 -1
  13. package/dist/runtime/built-in-block-code-templates/compound-persistence.js +11 -8
  14. package/dist/runtime/built-in-block-non-ts-artifacts.js +505 -2
  15. package/dist/runtime/cli-add-block.d.ts +6 -2
  16. package/dist/runtime/cli-add-block.js +71 -24
  17. package/dist/runtime/cli-add-shared.d.ts +58 -2
  18. package/dist/runtime/cli-add-shared.js +111 -12
  19. package/dist/runtime/cli-add-workspace-assets.d.ts +21 -1
  20. package/dist/runtime/cli-add-workspace-assets.js +417 -1
  21. package/dist/runtime/cli-add-workspace-rest.d.ts +14 -0
  22. package/dist/runtime/cli-add-workspace-rest.js +1060 -0
  23. package/dist/runtime/cli-add-workspace.d.ts +10 -1
  24. package/dist/runtime/cli-add-workspace.js +10 -1
  25. package/dist/runtime/cli-add.d.ts +3 -3
  26. package/dist/runtime/cli-add.js +2 -2
  27. package/dist/runtime/cli-core.d.ts +5 -1
  28. package/dist/runtime/cli-core.js +3 -1
  29. package/dist/runtime/cli-doctor-workspace.js +135 -1
  30. package/dist/runtime/cli-help.js +12 -7
  31. package/dist/runtime/cli-scaffold.d.ts +12 -2
  32. package/dist/runtime/cli-scaffold.js +222 -46
  33. package/dist/runtime/cli-templates.d.ts +4 -4
  34. package/dist/runtime/cli-templates.js +104 -39
  35. package/dist/runtime/cli-validation.d.ts +66 -0
  36. package/dist/runtime/cli-validation.js +92 -0
  37. package/dist/runtime/compound-inner-blocks.d.ts +78 -0
  38. package/dist/runtime/compound-inner-blocks.js +88 -0
  39. package/dist/runtime/index.d.ts +6 -3
  40. package/dist/runtime/index.js +4 -2
  41. package/dist/runtime/local-dev-presets.js +7 -2
  42. package/dist/runtime/migration-command-surface.js +2 -0
  43. package/dist/runtime/package-versions.d.ts +1 -0
  44. package/dist/runtime/package-versions.js +12 -0
  45. package/dist/runtime/rest-resource-artifacts.d.ts +35 -0
  46. package/dist/runtime/rest-resource-artifacts.js +158 -0
  47. package/dist/runtime/scaffold-answer-resolution.js +78 -8
  48. package/dist/runtime/scaffold-apply-utils.d.ts +4 -3
  49. package/dist/runtime/scaffold-apply-utils.js +34 -17
  50. package/dist/runtime/scaffold-bootstrap.d.ts +15 -0
  51. package/dist/runtime/scaffold-bootstrap.js +29 -7
  52. package/dist/runtime/scaffold-documents.js +24 -3
  53. package/dist/runtime/scaffold-identifiers.d.ts +17 -0
  54. package/dist/runtime/scaffold-identifiers.js +22 -0
  55. package/dist/runtime/scaffold-onboarding.js +25 -13
  56. package/dist/runtime/scaffold-package-manager-files.js +6 -1
  57. package/dist/runtime/scaffold-template-variables.js +22 -0
  58. package/dist/runtime/scaffold.d.ts +22 -1
  59. package/dist/runtime/scaffold.js +56 -11
  60. package/dist/runtime/template-render.d.ts +5 -2
  61. package/dist/runtime/template-render.js +9 -3
  62. package/dist/runtime/template-source-contracts.d.ts +11 -0
  63. package/dist/runtime/template-source-external.d.ts +1 -1
  64. package/dist/runtime/template-source-external.js +45 -13
  65. package/dist/runtime/template-source-normalization.d.ts +1 -1
  66. package/dist/runtime/template-source-normalization.js +5 -1
  67. package/dist/runtime/template-source-remote.d.ts +5 -0
  68. package/dist/runtime/template-source-remote.js +33 -0
  69. package/dist/runtime/template-source.js +35 -4
  70. package/dist/runtime/workspace-inventory.d.ts +43 -1
  71. package/dist/runtime/workspace-inventory.js +132 -1
  72. package/dist/runtime/workspace-project.d.ts +1 -1
  73. package/dist/runtime/workspace-project.js +3 -3
  74. package/package.json +9 -4
  75. package/templates/_shared/compound/core/scripts/add-compound-child.ts.mustache +728 -49
  76. package/templates/query-loop/src/validator-toolkit.ts.mustache +0 -1
@@ -1,11 +1,83 @@
1
1
  import fs from "node:fs";
2
+ import { promises as fsp } from "node:fs";
3
+ import os from "node:os";
2
4
  import path from "node:path";
3
5
  import { collectScaffoldAnswers, DATA_STORAGE_MODES, PERSISTENCE_POLICIES, isDataStorageMode, isPersistencePolicy, resolvePackageManagerId, resolveTemplateId, scaffoldProject, } from "./scaffold.js";
6
+ import { parseAlternateRenderTargets } from "./alternate-render-targets.js";
7
+ import { parseCompoundInnerBlocksPreset } from "./compound-inner-blocks.js";
4
8
  import { formatInstallCommand, formatRunScript, } from "./package-managers.js";
5
9
  import { getPrimaryDevelopmentScript } from "./local-dev-presets.js";
6
10
  import { getOptionalOnboardingNote, getOptionalOnboardingSteps, } from "./scaffold-onboarding.js";
11
+ import { formatNonEmptyTargetDirectoryError } from "./scaffold-bootstrap.js";
7
12
  import { OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE, isBuiltInTemplateId, } from "./template-registry.js";
8
13
  import { resolveOptionalInteractiveExternalLayerId, } from "./external-layer-selection.js";
14
+ import { assertBuiltInTemplateVariantAllowed, resolveLocalCliPathOption, normalizeOptionalCliString, } from "./cli-validation.js";
15
+ async function listRelativeProjectFiles(rootDir) {
16
+ const relativeFiles = [];
17
+ async function visit(currentDir) {
18
+ const entries = await fsp.readdir(currentDir, { withFileTypes: true });
19
+ for (const entry of entries) {
20
+ const absolutePath = path.join(currentDir, entry.name);
21
+ if (entry.isDirectory()) {
22
+ await visit(absolutePath);
23
+ continue;
24
+ }
25
+ relativeFiles.push(path
26
+ .relative(rootDir, absolutePath)
27
+ .replace(path.sep === "\\" ? /\\/gu : /\//gu, "/"));
28
+ }
29
+ }
30
+ await visit(rootDir);
31
+ return relativeFiles.sort((left, right) => left.localeCompare(right));
32
+ }
33
+ async function assertDryRunTargetDirectoryReady(projectDir, allowExistingDir) {
34
+ if (!fs.existsSync(projectDir) || allowExistingDir) {
35
+ return;
36
+ }
37
+ const entries = await fsp.readdir(projectDir);
38
+ if (entries.length > 0) {
39
+ throw new Error(formatNonEmptyTargetDirectoryError(projectDir));
40
+ }
41
+ }
42
+ async function buildScaffoldDryRunPlan({ allowExistingDir, alternateRenderTargets, answers, cwd, dataStorageMode, externalLayerId, externalLayerSource, externalLayerSourceLabel, installDependencies, noInstall, onProgress, packageManager, persistencePolicy, projectDir, templateId, variant, withMigrationUi, withTestPreset, withWpEnv, }) {
43
+ await assertDryRunTargetDirectoryReady(projectDir, allowExistingDir);
44
+ const tempRoot = await fsp.mkdtemp(path.join(os.tmpdir(), "wp-typia-scaffold-plan-"));
45
+ const previewProjectDir = path.join(tempRoot, "preview-project");
46
+ try {
47
+ const result = await scaffoldProject({
48
+ allowExistingDir: false,
49
+ alternateRenderTargets,
50
+ answers,
51
+ cwd,
52
+ dataStorageMode,
53
+ externalLayerId,
54
+ externalLayerSource,
55
+ externalLayerSourceLabel,
56
+ installDependencies,
57
+ noInstall: true,
58
+ onProgress,
59
+ packageManager,
60
+ persistencePolicy,
61
+ projectDir: previewProjectDir,
62
+ templateId,
63
+ variant,
64
+ withMigrationUi,
65
+ withTestPreset,
66
+ withWpEnv,
67
+ });
68
+ const files = await listRelativeProjectFiles(previewProjectDir);
69
+ return {
70
+ plan: {
71
+ dependencyInstall: noInstall ? "skipped-by-flag" : "would-install",
72
+ files,
73
+ },
74
+ result,
75
+ };
76
+ }
77
+ finally {
78
+ await fsp.rm(tempRoot, { force: true, recursive: true });
79
+ }
80
+ }
9
81
  function validateCreateProjectInput(projectInput) {
10
82
  const normalizedProjectInput = projectInput.trim();
11
83
  if (normalizedProjectInput.length === 0) {
@@ -41,14 +113,74 @@ function templateUsesPersistenceSettings(templateId, options) {
41
113
  function templateSupportsPersistenceFlags(templateId) {
42
114
  return templateId === "persistence" || templateId === "compound";
43
115
  }
116
+ function templateSupportsCompoundInnerBlocksPreset(templateId) {
117
+ return templateId === "compound";
118
+ }
119
+ function createTemplateLabel(templateId) {
120
+ return templateId === OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE
121
+ ? "`--template workspace`"
122
+ : `"${templateId}"`;
123
+ }
124
+ function collectTemplateCapabilityWarnings(options) {
125
+ const warnings = [];
126
+ const trimmedQueryPostType = options.queryPostType?.trim();
127
+ if (trimmedQueryPostType &&
128
+ options.templateId !== "query-loop" &&
129
+ (isBuiltInTemplateId(options.templateId) ||
130
+ options.templateId === OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE)) {
131
+ warnings.push(`\`--query-post-type\` only applies to \`wp-typia create --template query-loop\`, which scaffolds a create-time \`core/query\` variation instead of a standalone block. ${createTemplateLabel(options.templateId)} will ignore "${trimmedQueryPostType}".`);
132
+ }
133
+ if (options.withMigrationUi === true &&
134
+ !isBuiltInTemplateId(options.templateId) &&
135
+ options.templateId !== OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE) {
136
+ warnings.push(`\`--with-migration-ui\` was ignored for ${createTemplateLabel(options.templateId)}. Migration UI currently scaffolds built-in templates and the official \`--template workspace\` flow; external templates still need to opt into that surface explicitly.`);
137
+ }
138
+ return warnings;
139
+ }
140
+ function templateSupportsAlternateRenderTargets(options) {
141
+ if (!options.alternateRenderTargets) {
142
+ return false;
143
+ }
144
+ if (options.templateId === "persistence") {
145
+ return true;
146
+ }
147
+ if (options.templateId !== "compound") {
148
+ return false;
149
+ }
150
+ return templateUsesPersistenceSettings(options.templateId, {
151
+ dataStorageMode: options.dataStorageMode,
152
+ persistencePolicy: options.persistencePolicy,
153
+ });
154
+ }
44
155
  function validateCreateFlagContract(options) {
45
- const { dataStorageMode, persistencePolicy, templateId, variant } = options;
156
+ const { alternateRenderTargets, dataStorageMode, innerBlocksPreset, persistencePolicy, templateId, variant, } = options;
46
157
  if ((dataStorageMode || persistencePolicy) &&
47
158
  !templateSupportsPersistenceFlags(templateId)) {
48
159
  throw new Error("`--data-storage` and `--persistence-policy` are supported only for `wp-typia create --template persistence` or `--template compound`.");
49
160
  }
50
- if (variant && isBuiltInTemplateId(templateId)) {
51
- throw new Error(`--variant is only supported for official external template configs. Received variant "${variant}" for built-in template "${templateId}".`);
161
+ if (alternateRenderTargets &&
162
+ !templateSupportsAlternateRenderTargets({
163
+ alternateRenderTargets,
164
+ dataStorageMode,
165
+ persistencePolicy,
166
+ templateId,
167
+ })) {
168
+ if (templateId === "compound") {
169
+ throw new Error("`--alternate-render-targets` on `wp-typia create --template compound` requires the persistence-enabled server render path. Add `--data-storage <post-meta|custom-table>` or `--persistence-policy <authenticated|public>` first.");
170
+ }
171
+ throw new Error("`--alternate-render-targets` is supported only for `wp-typia create --template persistence` or persistence-enabled `--template compound` scaffolds.");
172
+ }
173
+ parseAlternateRenderTargets(alternateRenderTargets);
174
+ if (innerBlocksPreset &&
175
+ !templateSupportsCompoundInnerBlocksPreset(templateId)) {
176
+ throw new Error("`--inner-blocks-preset` is supported only for `wp-typia create --template compound`.");
177
+ }
178
+ parseCompoundInnerBlocksPreset(innerBlocksPreset);
179
+ if (isBuiltInTemplateId(templateId)) {
180
+ assertBuiltInTemplateVariantAllowed({
181
+ templateId,
182
+ variant,
183
+ });
52
184
  }
53
185
  }
54
186
  function parseSelectableValue(label, value, isValue, allowedValues) {
@@ -135,14 +267,13 @@ export function getOptionalOnboarding({ availableScripts, packageManager, templa
135
267
  * project.
136
268
  * @returns The scaffold result together with next-step guidance.
137
269
  */
138
- export async function runScaffoldFlow({ projectInput, cwd = process.cwd(), templateId, dataStorageMode, externalLayerId, externalLayerSource, persistencePolicy, packageManager, namespace, textDomain, phpPrefix, queryPostType, yes = false, noInstall = false, isInteractive = false, allowExistingDir = false, selectTemplate, selectDataStorage, selectExternalLayerId, selectPersistencePolicy, selectPackageManager, promptText, installDependencies = undefined, variant, selectWithTestPreset, selectWithWpEnv, selectWithMigrationUi, withMigrationUi, withTestPreset, withWpEnv, }) {
139
- const normalizedExternalLayerId = typeof externalLayerId === "string" && externalLayerId.trim().length > 0
140
- ? externalLayerId.trim()
141
- : undefined;
142
- const normalizedExternalLayerSource = typeof externalLayerSource === "string" &&
143
- externalLayerSource.trim().length > 0
144
- ? externalLayerSource.trim()
145
- : undefined;
270
+ export async function runScaffoldFlow({ projectInput, cwd = process.cwd(), templateId, alternateRenderTargets, dataStorageMode, dryRun = false, externalLayerId, externalLayerSource, innerBlocksPreset, persistencePolicy, packageManager, namespace, textDomain, phpPrefix, queryPostType, yes = false, noInstall = false, onProgress, isInteractive = false, allowExistingDir = false, selectTemplate, selectDataStorage, selectExternalLayerId, selectPersistencePolicy, selectPackageManager, promptText, installDependencies = undefined, variant, selectWithTestPreset, selectWithWpEnv, selectWithMigrationUi, withMigrationUi, withTestPreset, withWpEnv, }) {
271
+ const normalizedExternalLayerId = normalizeOptionalCliString(externalLayerId);
272
+ const normalizedExternalLayerSource = resolveLocalCliPathOption({
273
+ cwd,
274
+ label: "--external-layer-source",
275
+ value: externalLayerSource,
276
+ });
146
277
  validateCreateProjectInput(projectInput);
147
278
  const resolvedTemplateId = await resolveTemplateId({
148
279
  templateId,
@@ -151,11 +282,14 @@ export async function runScaffoldFlow({ projectInput, cwd = process.cwd(), templ
151
282
  selectTemplate,
152
283
  });
153
284
  validateCreateFlagContract({
285
+ alternateRenderTargets,
154
286
  dataStorageMode,
287
+ innerBlocksPreset,
155
288
  persistencePolicy,
156
289
  templateId: resolvedTemplateId,
157
290
  variant,
158
291
  });
292
+ const resolvedInnerBlocksPreset = parseCompoundInnerBlocksPreset(innerBlocksPreset);
159
293
  const resolvedExternalLayerSelection = isBuiltInTemplateId(resolvedTemplateId) && isInteractive
160
294
  ? await resolveOptionalInteractiveExternalLayerId({
161
295
  callerCwd: cwd,
@@ -234,47 +368,81 @@ export async function runScaffoldFlow({ projectInput, cwd = process.cwd(), templ
234
368
  yes,
235
369
  promptText,
236
370
  });
237
- const result = await scaffoldProject({
238
- answers,
239
- allowExistingDir,
240
- cwd,
241
- dataStorageMode: resolvedDataStorage,
242
- externalLayerId: resolvedExternalLayerSelection.externalLayerId,
243
- externalLayerSource: resolvedExternalLayerSelection.externalLayerSource,
244
- externalLayerSourceLabel: normalizedExternalLayerSource,
245
- installDependencies,
246
- noInstall,
247
- packageManager: resolvedPackageManager,
248
- persistencePolicy: resolvedPersistencePolicy,
249
- projectDir,
250
- templateId: resolvedTemplateId,
251
- variant,
252
- withMigrationUi: resolvedWithMigrationUi,
253
- withTestPreset: resolvedWithTestPreset,
254
- withWpEnv: resolvedWithWpEnv,
255
- });
256
- let availableScripts;
257
- try {
258
- const parsedPackageJson = JSON.parse(fs.readFileSync(path.join(projectDir, "package.json"), "utf8"));
259
- const scripts = parsedPackageJson.scripts &&
260
- typeof parsedPackageJson.scripts === "object" &&
261
- !Array.isArray(parsedPackageJson.scripts)
262
- ? parsedPackageJson.scripts
263
- : {};
264
- availableScripts = Object.entries(scripts)
265
- .filter(([, value]) => typeof value === "string")
266
- .map(([scriptName]) => scriptName);
371
+ if (resolvedTemplateId === "compound" && resolvedInnerBlocksPreset) {
372
+ answers.compoundInnerBlocksPreset = resolvedInnerBlocksPreset;
267
373
  }
268
- catch {
269
- availableScripts = undefined;
374
+ const resolvedResult = dryRun
375
+ ? await buildScaffoldDryRunPlan({
376
+ allowExistingDir,
377
+ alternateRenderTargets,
378
+ answers,
379
+ cwd,
380
+ dataStorageMode: resolvedDataStorage,
381
+ externalLayerId: resolvedExternalLayerSelection.externalLayerId,
382
+ externalLayerSource: resolvedExternalLayerSelection.externalLayerSource,
383
+ externalLayerSourceLabel: normalizedExternalLayerSource,
384
+ installDependencies,
385
+ noInstall,
386
+ onProgress,
387
+ packageManager: resolvedPackageManager,
388
+ persistencePolicy: resolvedPersistencePolicy,
389
+ projectDir,
390
+ templateId: resolvedTemplateId,
391
+ variant,
392
+ withMigrationUi: resolvedWithMigrationUi,
393
+ withTestPreset: resolvedWithTestPreset,
394
+ withWpEnv: resolvedWithWpEnv,
395
+ })
396
+ : {
397
+ plan: undefined,
398
+ result: await scaffoldProject({
399
+ alternateRenderTargets,
400
+ answers,
401
+ allowExistingDir,
402
+ cwd,
403
+ dataStorageMode: resolvedDataStorage,
404
+ externalLayerId: resolvedExternalLayerSelection.externalLayerId,
405
+ externalLayerSource: resolvedExternalLayerSelection.externalLayerSource,
406
+ externalLayerSourceLabel: normalizedExternalLayerSource,
407
+ installDependencies,
408
+ noInstall,
409
+ onProgress,
410
+ packageManager: resolvedPackageManager,
411
+ persistencePolicy: resolvedPersistencePolicy,
412
+ projectDir,
413
+ templateId: resolvedTemplateId,
414
+ variant,
415
+ withMigrationUi: resolvedWithMigrationUi,
416
+ withTestPreset: resolvedWithTestPreset,
417
+ withWpEnv: resolvedWithWpEnv,
418
+ }),
419
+ };
420
+ let availableScripts;
421
+ if (!dryRun) {
422
+ try {
423
+ const parsedPackageJson = JSON.parse(fs.readFileSync(path.join(projectDir, "package.json"), "utf8"));
424
+ const scripts = parsedPackageJson.scripts &&
425
+ typeof parsedPackageJson.scripts === "object" &&
426
+ !Array.isArray(parsedPackageJson.scripts)
427
+ ? parsedPackageJson.scripts
428
+ : {};
429
+ availableScripts = Object.entries(scripts)
430
+ .filter(([, value]) => typeof value === "string")
431
+ .map(([scriptName]) => scriptName);
432
+ }
433
+ catch {
434
+ availableScripts = undefined;
435
+ }
270
436
  }
271
437
  return {
438
+ dryRun,
272
439
  optionalOnboarding: getOptionalOnboarding({
273
440
  availableScripts,
274
441
  packageManager: resolvedPackageManager,
275
442
  templateId: resolvedTemplateId,
276
- compoundPersistenceEnabled: result.variables.compoundPersistenceEnabled === "true",
443
+ compoundPersistenceEnabled: resolvedResult.result.variables.compoundPersistenceEnabled === "true",
277
444
  }),
445
+ plan: resolvedResult.plan,
278
446
  projectDir,
279
447
  projectInput,
280
448
  packageManager: resolvedPackageManager,
@@ -286,8 +454,16 @@ export async function runScaffoldFlow({ projectInput, cwd = process.cwd(), templ
286
454
  templateId: resolvedTemplateId,
287
455
  }),
288
456
  result: {
289
- ...result,
290
- warnings: [...result.warnings, ...collectProjectDirectoryWarnings(projectDir)],
457
+ ...resolvedResult.result,
458
+ warnings: [
459
+ ...resolvedResult.result.warnings,
460
+ ...collectTemplateCapabilityWarnings({
461
+ queryPostType,
462
+ templateId: resolvedTemplateId,
463
+ withMigrationUi,
464
+ }),
465
+ ...collectProjectDirectoryWarnings(projectDir),
466
+ ],
291
467
  },
292
468
  };
293
469
  }
@@ -8,18 +8,18 @@ import type { TemplateDefinition } from "./template-registry.js";
8
8
  */
9
9
  export declare function formatTemplateSummary(template: TemplateDefinition): string;
10
10
  /**
11
- * Format the feature hint line shown under a template summary.
11
+ * Format the feature and capability hint lines shown under a template summary.
12
12
  *
13
13
  * @param template Template metadata including the `features` list.
14
- * @returns Indented feature text for CLI list output.
14
+ * @returns Indented feature and capability text for CLI list output.
15
15
  */
16
16
  export declare function formatTemplateFeatures(template: TemplateDefinition): string;
17
17
  /**
18
18
  * Format the detailed template description for `templates inspect`.
19
19
  *
20
20
  * This expands special layer combinations for the `persistence` and `compound`
21
- * templates and returns a multi-line block including category, overlay path,
22
- * resolved layers, and feature labels.
21
+ * templates and returns a multi-line block centered on human-facing identity,
22
+ * capabilities, and logical layer composition.
23
23
  *
24
24
  * @param template Template metadata including `id`, `defaultCategory`,
25
25
  * `templateDir`, and `features`.
@@ -1,5 +1,5 @@
1
- import { getBuiltInTemplateLayerDirs } from "./template-builtins.js";
2
- import { getTemplateById, getTemplateSelectOptions, isBuiltInTemplateId, listTemplates, } from "./template-registry.js";
1
+ import { OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE, getTemplateById, getTemplateSelectOptions, isBuiltInTemplateId, listTemplates, } from "./template-registry.js";
2
+ const WORKSPACE_TEMPLATE_ALIAS = "workspace";
3
3
  /**
4
4
  * Format one line of template list output for a built-in template.
5
5
  *
@@ -10,62 +10,127 @@ export function formatTemplateSummary(template) {
10
10
  return `${template.id.padEnd(14)} ${template.description}`;
11
11
  }
12
12
  /**
13
- * Format the feature hint line shown under a template summary.
13
+ * Format the feature and capability hint lines shown under a template summary.
14
14
  *
15
15
  * @param template Template metadata including the `features` list.
16
- * @returns Indented feature text for CLI list output.
16
+ * @returns Indented feature and capability text for CLI list output.
17
17
  */
18
18
  export function formatTemplateFeatures(template) {
19
- return ` ${template.features.join(" • ")}`;
19
+ const lines = [` Features: ${template.features.join(" • ")}`];
20
+ const capabilityHints = getTemplateCapabilityHints(template);
21
+ if (capabilityHints.length > 0) {
22
+ lines.push(` Supports: ${capabilityHints.join(" • ")}`);
23
+ }
24
+ const specialNotes = getTemplateSpecialNotes(template);
25
+ if (specialNotes.length > 0) {
26
+ lines.push(` Notes: ${specialNotes.join(" • ")}`);
27
+ }
28
+ if (template.id === OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE) {
29
+ lines.push(` Alias: ${WORKSPACE_TEMPLATE_ALIAS} (\`--template ${WORKSPACE_TEMPLATE_ALIAS}\`)`);
30
+ }
31
+ return lines.join("\n");
20
32
  }
21
33
  /**
22
34
  * Format the detailed template description for `templates inspect`.
23
35
  *
24
36
  * This expands special layer combinations for the `persistence` and `compound`
25
- * templates and returns a multi-line block including category, overlay path,
26
- * resolved layers, and feature labels.
37
+ * templates and returns a multi-line block centered on human-facing identity,
38
+ * capabilities, and logical layer composition.
27
39
  *
28
40
  * @param template Template metadata including `id`, `defaultCategory`,
29
41
  * `templateDir`, and `features`.
30
42
  * @returns Multi-line template details text for CLI output.
31
43
  */
32
44
  export function formatTemplateDetails(template) {
45
+ const detailLines = [
46
+ template.id,
47
+ `Summary: ${template.description}`,
48
+ ...getTemplateIdentityLines(template),
49
+ `Category: ${template.defaultCategory}`,
50
+ ];
51
+ const capabilityHints = getTemplateCapabilityHints(template);
52
+ if (capabilityHints.length > 0) {
53
+ detailLines.push("Capabilities:");
54
+ for (const capabilityHint of capabilityHints) {
55
+ detailLines.push(` - ${capabilityHint}`);
56
+ }
57
+ }
58
+ const specialNotes = getTemplateSpecialNotes(template);
59
+ if (specialNotes.length > 0) {
60
+ detailLines.push("Notes:");
61
+ for (const specialNote of specialNotes) {
62
+ detailLines.push(` - ${specialNote}`);
63
+ }
64
+ }
65
+ detailLines.push("Logical layers:");
66
+ for (const logicalLayer of getTemplateLogicalLayerSummaries(template)) {
67
+ detailLines.push(` - ${logicalLayer}`);
68
+ }
69
+ detailLines.push(`Features: ${template.features.join(", ")}`);
70
+ return detailLines.join("\n");
71
+ }
72
+ function getTemplateCapabilityHints(template) {
73
+ if (template.id === "persistence" || template.id === "compound") {
74
+ return [
75
+ "--alternate-render-targets",
76
+ "--data-storage",
77
+ "--persistence-policy",
78
+ "external layers",
79
+ ];
80
+ }
81
+ if (template.id === "query-loop") {
82
+ return ["--query-post-type", "external layers"];
83
+ }
84
+ if (isBuiltInTemplateId(template.id)) {
85
+ return ["external layers"];
86
+ }
87
+ return [];
88
+ }
89
+ function getTemplateSpecialNotes(template) {
90
+ if (template.id === "query-loop") {
91
+ return [
92
+ "Create-time variation scaffold only; use `wp-typia create <project-dir> --template query-loop` instead of `wp-typia add block`.",
93
+ "Owns a `core/query` variation, so it does not generate `src/types.ts`, `block.json`, or Typia manifests.",
94
+ ];
95
+ }
96
+ return [];
97
+ }
98
+ function getTemplateIdentityLines(template) {
99
+ if (template.id === OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE) {
100
+ return [
101
+ "Identity:",
102
+ ` - Official package: ${OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE}`,
103
+ ` - Alias: ${WORKSPACE_TEMPLATE_ALIAS} (\`--template ${WORKSPACE_TEMPLATE_ALIAS}\`)`,
104
+ "Type: official workspace scaffold",
105
+ ];
106
+ }
107
+ return [
108
+ "Identity:",
109
+ ` - Built-in template id: ${template.id}`,
110
+ "Type: built-in block scaffold",
111
+ ];
112
+ }
113
+ function getTemplateLogicalLayerSummaries(template) {
33
114
  if (!isBuiltInTemplateId(template.id)) {
115
+ return ["workspace package scaffold"];
116
+ }
117
+ if (template.id === "persistence") {
118
+ return [
119
+ "authenticated write policy: shared/base -> rest helpers (shared) -> persistence core -> authenticated write policy -> persistence overlay",
120
+ "public write policy: shared/base -> rest helpers (shared) -> persistence core -> public write policy -> persistence overlay",
121
+ ];
122
+ }
123
+ if (template.id === "compound") {
34
124
  return [
35
- template.id,
36
- template.description,
37
- `Category: ${template.defaultCategory}`,
38
- `Overlay path: ${template.templateDir}`,
39
- "Layers: workspace package scaffold",
40
- `Features: ${template.features.join(", ")}`,
41
- ].join("\n");
125
+ "pure block family: shared/base -> compound core -> compound overlay",
126
+ "authenticated persistence: shared/base -> compound core -> rest helpers (shared) -> compound persistence core -> authenticated write policy -> compound overlay",
127
+ "public persistence: shared/base -> compound core -> rest helpers (shared) -> compound persistence core -> public write policy -> compound overlay",
128
+ ];
42
129
  }
43
- const layers = template.id === "persistence"
44
- ? [
45
- `authenticated: ${getBuiltInTemplateLayerDirs(template.id, { persistencePolicy: "authenticated" }).join(" -> ")}`,
46
- `public: ${getBuiltInTemplateLayerDirs(template.id, { persistencePolicy: "public" }).join(" -> ")}`,
47
- ]
48
- : template.id === "compound"
49
- ? [
50
- `pure: ${getBuiltInTemplateLayerDirs(template.id).join(" -> ")}`,
51
- `authenticated+persistence: ${getBuiltInTemplateLayerDirs(template.id, {
52
- persistenceEnabled: true,
53
- persistencePolicy: "authenticated",
54
- }).join(" -> ")}`,
55
- `public+persistence: ${getBuiltInTemplateLayerDirs(template.id, {
56
- persistenceEnabled: true,
57
- persistencePolicy: "public",
58
- }).join(" -> ")}`,
59
- ]
60
- : [getBuiltInTemplateLayerDirs(template.id).join(" -> ")];
130
+ const overlayName = template.id === "query-loop" ? "query-loop overlay" : `${template.id} overlay`;
61
131
  return [
62
- template.id,
63
- template.description,
64
- `Category: ${template.defaultCategory}`,
65
- `Overlay path: ${template.templateDir}`,
66
- `Layers: ${layers.join("\n")}`,
67
- `Features: ${template.features.join(", ")}`,
68
- ].join("\n");
132
+ `shared/base -> ${overlayName}`,
133
+ ];
69
134
  }
70
135
  export { getTemplateById, getTemplateSelectOptions, listTemplates };
71
136
  export { isBuiltInTemplateId };
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Normalize one optional CLI string flag by trimming whitespace and collapsing
3
+ * empty strings to `undefined`.
4
+ *
5
+ * @param value Raw CLI value before normalization.
6
+ * @returns The trimmed string when present, otherwise `undefined`.
7
+ */
8
+ export declare function normalizeOptionalCliString(value?: string): string | undefined;
9
+ /**
10
+ * Resolve one CLI path flag relative to the caller when it is expressed as a
11
+ * local filesystem path.
12
+ *
13
+ * Non-local values such as npm package specs or `github:` locators pass
14
+ * through unchanged. Local relative and absolute paths are resolved against the
15
+ * provided `cwd` and must exist on disk.
16
+ *
17
+ * @param options Path resolution inputs for one CLI flag.
18
+ * @param options.cwd Caller working directory used for relative path
19
+ * resolution.
20
+ * @param options.label Human-readable option label used in thrown errors.
21
+ * @param options.value Raw CLI value before trimming and path resolution.
22
+ * @returns The normalized string, or `undefined` when the option was omitted.
23
+ * @throws When a local-looking path resolves to a missing filesystem entry.
24
+ */
25
+ export declare function resolveLocalCliPathOption(options: {
26
+ cwd: string;
27
+ label: string;
28
+ value?: string;
29
+ }): string | undefined;
30
+ /**
31
+ * Validate the built-in template composition rule for external layers.
32
+ *
33
+ * @param options External layer CLI options after normalization.
34
+ * @param options.externalLayerId Optional selected layer id.
35
+ * @param options.externalLayerSource Optional layer source locator or path.
36
+ * @throws When `externalLayerId` is provided without `externalLayerSource`.
37
+ */
38
+ export declare function assertExternalLayerCompositionOptions(options: {
39
+ externalLayerId?: string;
40
+ externalLayerSource?: string;
41
+ }): void;
42
+ /**
43
+ * Build the shared error message used when a built-in template receives a
44
+ * `--variant` override.
45
+ *
46
+ * @param options Built-in template context.
47
+ * @param options.templateId Built-in template id that rejected the variant.
48
+ * @param options.variant User-supplied variant override.
49
+ * @returns The canonical user-facing error message.
50
+ */
51
+ export declare function createBuiltInVariantErrorMessage(options: {
52
+ templateId: string;
53
+ variant: string;
54
+ }): string;
55
+ /**
56
+ * Reject unsupported `--variant` usage for built-in templates.
57
+ *
58
+ * @param options Built-in template validation context.
59
+ * @param options.templateId Built-in template id being scaffolded.
60
+ * @param options.variant Optional variant override from CLI flags.
61
+ * @throws When a built-in template receives any explicit `--variant` value.
62
+ */
63
+ export declare function assertBuiltInTemplateVariantAllowed(options: {
64
+ templateId: string;
65
+ variant?: string;
66
+ }): void;