@wp-typia/project-tools 0.24.1 → 0.24.3

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.
@@ -0,0 +1,190 @@
1
+ import path from "node:path";
2
+ import { parseAlternateRenderTargets } from "./alternate-render-targets.js";
3
+ import { parseCompoundInnerBlocksPreset } from "./compound-inner-blocks.js";
4
+ import { assertBuiltInTemplateVariantAllowed } from "./cli-validation.js";
5
+ import { OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE, isBuiltInTemplateId, } from "./template-registry.js";
6
+ /**
7
+ * Validate the project directory argument before template resolution.
8
+ *
9
+ * @param projectInput Raw project directory input from the CLI.
10
+ * @throws Error when the input is empty or points at the current/parent directory.
11
+ */
12
+ export function validateCreateProjectInput(projectInput) {
13
+ const normalizedProjectInput = projectInput.trim();
14
+ if (normalizedProjectInput.length === 0) {
15
+ throw new Error("Project directory is required. Usage: wp-typia create <project-dir> (or wp-typia <project-dir> when <project-dir> is the only positional argument).");
16
+ }
17
+ const normalizedProjectPath = path.normalize(normalizedProjectInput).replace(/[\\/]+$/u, "") ||
18
+ path.normalize(normalizedProjectInput);
19
+ if (normalizedProjectPath === "." || normalizedProjectPath === "..") {
20
+ throw new Error("`wp-typia create` requires a new project directory. Use an explicit child directory instead of `.` or `..`.");
21
+ }
22
+ }
23
+ /**
24
+ * Collect warnings for project directory names that are awkward in shells.
25
+ *
26
+ * @param projectDir Absolute target project directory.
27
+ * @returns User-facing warning messages for non-fatal directory concerns.
28
+ */
29
+ export function collectProjectDirectoryWarnings(projectDir) {
30
+ const warnings = [];
31
+ const projectName = path.basename(projectDir);
32
+ if (/\s/u.test(projectName)) {
33
+ warnings.push(`Project directory "${projectName}" contains spaces. The generated next-step commands will be quoted, but a simple kebab-case directory name is usually easier to use with shells and downstream tooling.`);
34
+ }
35
+ const shellSensitiveCharacters = Array.from(new Set(projectName.match(/[^A-Za-z0-9._ -]/gu) ?? []));
36
+ if (shellSensitiveCharacters.length > 0) {
37
+ warnings.push(`Project directory "${projectName}" contains shell-sensitive characters (${shellSensitiveCharacters.join(", ")}). Prefer letters, numbers, ".", "_" and "-" when possible.`);
38
+ }
39
+ return warnings;
40
+ }
41
+ /**
42
+ * Determine whether a template should resolve persistence-related options.
43
+ *
44
+ * @param templateId Resolved template id.
45
+ * @param options Explicit persistence flags provided by the caller.
46
+ * @returns True when persistence defaults or prompts should be applied.
47
+ */
48
+ export function templateUsesPersistenceSettings(templateId, options) {
49
+ if (templateId === "persistence") {
50
+ return true;
51
+ }
52
+ if (templateId !== "compound") {
53
+ return false;
54
+ }
55
+ return Boolean(options.dataStorageMode || options.persistencePolicy);
56
+ }
57
+ function templateSupportsPersistenceFlags(templateId) {
58
+ return templateId === "persistence" || templateId === "compound";
59
+ }
60
+ function templateSupportsCompoundInnerBlocksPreset(templateId) {
61
+ return templateId === "compound";
62
+ }
63
+ function createTemplateLabel(templateId) {
64
+ return templateId === OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE
65
+ ? "`--template workspace`"
66
+ : `"${templateId}"`;
67
+ }
68
+ /**
69
+ * Collect warnings for flags that do not apply to the selected template.
70
+ *
71
+ * @param options Template id and raw CLI flags that may be ignored.
72
+ * @returns User-facing warnings for non-fatal capability mismatches.
73
+ */
74
+ export function collectTemplateCapabilityWarnings(options) {
75
+ const warnings = [];
76
+ const trimmedQueryPostType = options.queryPostType?.trim();
77
+ if (trimmedQueryPostType &&
78
+ options.templateId !== "query-loop" &&
79
+ (isBuiltInTemplateId(options.templateId) ||
80
+ options.templateId === OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE)) {
81
+ 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}".`);
82
+ }
83
+ if (options.withMigrationUi === true &&
84
+ !isBuiltInTemplateId(options.templateId) &&
85
+ options.templateId !== OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE) {
86
+ 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.`);
87
+ }
88
+ return warnings;
89
+ }
90
+ function templateSupportsAlternateRenderTargets(options) {
91
+ if (!options.alternateRenderTargets) {
92
+ return false;
93
+ }
94
+ if (options.templateId === "persistence") {
95
+ return true;
96
+ }
97
+ if (options.templateId !== "compound") {
98
+ return false;
99
+ }
100
+ return templateUsesPersistenceSettings(options.templateId, {
101
+ dataStorageMode: options.dataStorageMode,
102
+ persistencePolicy: options.persistencePolicy,
103
+ });
104
+ }
105
+ /**
106
+ * Validate create flags that depend on the selected template capability set.
107
+ *
108
+ * @param options Raw create flags plus the resolved template id.
109
+ * @throws Error when a flag is unsupported for the selected template.
110
+ */
111
+ export function validateCreateFlagContract(options) {
112
+ const { alternateRenderTargets, dataStorageMode, innerBlocksPreset, persistencePolicy, templateId, variant, } = options;
113
+ if ((dataStorageMode || persistencePolicy) &&
114
+ !templateSupportsPersistenceFlags(templateId)) {
115
+ throw new Error("`--data-storage` and `--persistence-policy` are supported only for `wp-typia create --template persistence` or `--template compound`.");
116
+ }
117
+ if (alternateRenderTargets &&
118
+ !templateSupportsAlternateRenderTargets({
119
+ alternateRenderTargets,
120
+ dataStorageMode,
121
+ persistencePolicy,
122
+ templateId,
123
+ })) {
124
+ if (templateId === "compound") {
125
+ 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.");
126
+ }
127
+ throw new Error("`--alternate-render-targets` is supported only for `wp-typia create --template persistence` or persistence-enabled `--template compound` scaffolds.");
128
+ }
129
+ parseAlternateRenderTargets(alternateRenderTargets);
130
+ if (innerBlocksPreset &&
131
+ !templateSupportsCompoundInnerBlocksPreset(templateId)) {
132
+ throw new Error("`--inner-blocks-preset` is supported only for `wp-typia create --template compound`.");
133
+ }
134
+ parseCompoundInnerBlocksPreset(innerBlocksPreset);
135
+ if (isBuiltInTemplateId(templateId)) {
136
+ assertBuiltInTemplateVariantAllowed({
137
+ templateId,
138
+ variant,
139
+ });
140
+ }
141
+ }
142
+ function parseSelectableValue(label, value, isValue, allowedValues) {
143
+ if (isValue(value)) {
144
+ return value;
145
+ }
146
+ throw new Error(`Unsupported ${label} "${value}". Expected one of: ${allowedValues.join(", ")}`);
147
+ }
148
+ /**
149
+ * Resolve an optional string selection from explicit input, prompts, or defaults.
150
+ *
151
+ * @param options Selection configuration including allowed values and prompt hooks.
152
+ * @returns The resolved value, or undefined when resolution is disabled.
153
+ * @throws Error when an explicit value is not in the allowed set.
154
+ */
155
+ export async function resolveOptionalSelection({ defaultValue, explicitValue, isInteractive, isValue, label, allowedValues, select, shouldResolve = true, yes, }) {
156
+ if (!shouldResolve) {
157
+ return undefined;
158
+ }
159
+ if (explicitValue !== undefined) {
160
+ return parseSelectableValue(label, explicitValue, isValue, allowedValues);
161
+ }
162
+ if (yes) {
163
+ return defaultValue;
164
+ }
165
+ if (isInteractive && select) {
166
+ return select();
167
+ }
168
+ return defaultValue;
169
+ }
170
+ /**
171
+ * Resolve an optional boolean flag from explicit input, prompts, or defaults.
172
+ *
173
+ * @param options Boolean selection configuration and optional prompt hook.
174
+ * @returns The resolved boolean value.
175
+ */
176
+ export async function resolveOptionalBooleanFlag({ defaultValue = false, disabled = false, explicitValue, isInteractive, select, yes, }) {
177
+ if (disabled) {
178
+ return defaultValue;
179
+ }
180
+ if (typeof explicitValue === "boolean") {
181
+ return explicitValue;
182
+ }
183
+ if (yes) {
184
+ return defaultValue;
185
+ }
186
+ if (isInteractive && select) {
187
+ return select();
188
+ }
189
+ return defaultValue;
190
+ }
@@ -1,30 +1,12 @@
1
- import { collectScaffoldAnswers, scaffoldProject } from "./scaffold.js";
2
- import type { DataStorageMode, PersistencePolicy, ScaffoldProgressEvent } from "./scaffold.js";
1
+ import { collectScaffoldAnswers } from "./scaffold.js";
2
+ import { type ScaffoldEmissionOptions, type ScaffoldInstallDependencies } from "./cli-scaffold-emission.js";
3
+ import type { DataStorageMode, PersistencePolicy } from "./scaffold.js";
3
4
  import type { PackageManagerId } from "./package-managers.js";
4
5
  import { type ExternalLayerSelectionOption } from "./external-layer-selection.js";
5
6
  import type { TemplateDefinition } from "./template-registry.js";
6
- interface GetNextStepsOptions {
7
- noInstall: boolean;
8
- packageManager: PackageManagerId;
9
- projectDir: string;
10
- projectInput: string;
11
- templateId: string;
12
- }
13
- interface GetOptionalOnboardingOptions {
14
- availableScripts?: string[];
15
- packageManager: PackageManagerId;
16
- templateId: string;
17
- compoundPersistenceEnabled?: boolean;
18
- }
19
- interface OptionalOnboardingGuidance {
20
- note: string;
21
- shortNote: string;
22
- steps: string[];
23
- }
24
- export interface ScaffoldDryRunPlan {
25
- dependencyInstall: "skipped-by-flag" | "would-install";
26
- files: string[];
27
- }
7
+ export { getNextSteps, getOptionalOnboarding } from "./cli-scaffold-output.js";
8
+ export type { OptionalOnboardingGuidance, ScaffoldNextStepsOptions, ScaffoldOptionalOnboardingOptions, } from "./cli-scaffold-output.js";
9
+ export type { ScaffoldDryRunPlan } from "./cli-scaffold-emission.js";
28
10
  interface RunScaffoldFlowOptions {
29
11
  allowExistingDir?: boolean;
30
12
  alternateRenderTargets?: string;
@@ -33,12 +15,12 @@ interface RunScaffoldFlowOptions {
33
15
  dryRun?: boolean;
34
16
  externalLayerId?: string;
35
17
  externalLayerSource?: string;
36
- installDependencies?: Parameters<typeof scaffoldProject>[0]["installDependencies"];
18
+ installDependencies?: ScaffoldInstallDependencies;
37
19
  innerBlocksPreset?: string;
38
20
  isInteractive?: boolean;
39
21
  namespace?: string;
40
22
  noInstall?: boolean;
41
- onProgress?: ((event: ScaffoldProgressEvent) => void | Promise<void>) | undefined;
23
+ onProgress?: ScaffoldEmissionOptions["onProgress"];
42
24
  packageManager?: string;
43
25
  phpPrefix?: string;
44
26
  profile?: string;
@@ -62,21 +44,6 @@ interface RunScaffoldFlowOptions {
62
44
  withWpEnv?: boolean;
63
45
  yes?: boolean;
64
46
  }
65
- /**
66
- * Build the printed next-step commands for a scaffolded project.
67
- *
68
- * @param options Project location and package-manager details used to format
69
- * next-step commands.
70
- * @returns Ordered shell commands shown after scaffolding succeeds.
71
- */
72
- export declare function getNextSteps({ projectInput, projectDir, packageManager, noInstall, templateId, }: GetNextStepsOptions): string[];
73
- /**
74
- * Compute optional onboarding guidance shown after scaffolding completes.
75
- *
76
- * @param options Package-manager and template context for optional guidance.
77
- * @returns Optional onboarding note and step list.
78
- */
79
- export declare function getOptionalOnboarding({ availableScripts, packageManager, templateId, compoundPersistenceEnabled, }: GetOptionalOnboardingOptions): OptionalOnboardingGuidance;
80
47
  /**
81
48
  * Resolve scaffold options, prompts, and follow-up steps for one CLI run.
82
49
  *
@@ -86,8 +53,8 @@ export declare function getOptionalOnboarding({ availableScripts, packageManager
86
53
  */
87
54
  export declare function runScaffoldFlow({ projectInput, cwd, templateId, alternateRenderTargets, dataStorageMode, dryRun, externalLayerId, externalLayerSource, innerBlocksPreset, persistencePolicy, packageManager, namespace, profile, textDomain, phpPrefix, queryPostType, yes, noInstall, onProgress, isInteractive, allowExistingDir, selectTemplate, selectDataStorage, selectExternalLayerId, selectPersistencePolicy, selectPackageManager, promptText, installDependencies, variant, selectWithTestPreset, selectWithWpEnv, selectWithMigrationUi, withMigrationUi, withTestPreset, withWpEnv, }: RunScaffoldFlowOptions): Promise<{
88
55
  dryRun: boolean;
89
- optionalOnboarding: OptionalOnboardingGuidance;
90
- plan: ScaffoldDryRunPlan | undefined;
56
+ optionalOnboarding: import("./cli-scaffold-output.js").OptionalOnboardingGuidance;
57
+ plan: import("./cli-scaffold-emission.js").ScaffoldDryRunPlan | undefined;
91
58
  projectDir: string;
92
59
  projectInput: string;
93
60
  packageManager: PackageManagerId;
@@ -101,4 +68,3 @@ export declare function runScaffoldFlow({ projectInput, cwd, templateId, alterna
101
68
  variables: import("./scaffold.js").ScaffoldTemplateVariables;
102
69
  };
103
70
  }>;
104
- export {};
@@ -1,272 +1,15 @@
1
- import { promises as fsp } from "node:fs";
2
1
  import path from "node:path";
3
- import { collectScaffoldAnswers, DATA_STORAGE_MODES, PERSISTENCE_POLICIES, isDataStorageMode, resolveCreateProfileId, isPersistencePolicy, resolvePackageManagerId, resolveTemplateId, scaffoldProject, } from "./scaffold.js";
4
- import { parseAlternateRenderTargets } from "./alternate-render-targets.js";
2
+ import { collectScaffoldAnswers, DATA_STORAGE_MODES, PERSISTENCE_POLICIES, isDataStorageMode, resolveCreateProfileId, isPersistencePolicy, resolvePackageManagerId, resolveTemplateId, } from "./scaffold.js";
5
3
  import { parseCompoundInnerBlocksPreset } from "./compound-inner-blocks.js";
6
4
  import { isCompoundPersistenceEnabled } from "./scaffold-template-variable-groups.js";
7
- import { formatInstallCommand, formatRunScript, } from "./package-managers.js";
8
- import { getPrimaryDevelopmentScript } from "./local-dev-presets.js";
9
- import { createManagedTempRoot } from "./temp-roots.js";
10
- import { getOptionalOnboardingNote, getOptionalOnboardingShortNote, getOptionalOnboardingSteps, } from "./scaffold-onboarding.js";
11
- import { formatNonEmptyTargetDirectoryError } from "./scaffold-bootstrap.js";
12
- import { pathExists } from "./fs-async.js";
13
- import { readJsonFile } from "./json-utils.js";
5
+ import { buildScaffoldDryRunPlan, emitScaffoldProject, } from "./cli-scaffold-emission.js";
6
+ import { readGeneratedPackageScripts } from "./cli-scaffold-files.js";
7
+ import { getNextSteps, getOptionalOnboarding, } from "./cli-scaffold-output.js";
8
+ import { collectProjectDirectoryWarnings, collectTemplateCapabilityWarnings, resolveOptionalBooleanFlag, resolveOptionalSelection, templateUsesPersistenceSettings, validateCreateFlagContract, validateCreateProjectInput, } from "./cli-scaffold-validation.js";
14
9
  import { OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE, isBuiltInTemplateId, } from "./template-registry.js";
15
10
  import { resolveOptionalInteractiveExternalLayerId, } from "./external-layer-selection.js";
16
- import { assertBuiltInTemplateVariantAllowed, resolveLocalCliPathOption, normalizeOptionalCliString, } from "./cli-validation.js";
17
- async function listRelativeProjectFiles(rootDir) {
18
- const relativeFiles = [];
19
- async function visit(currentDir) {
20
- const entries = await fsp.readdir(currentDir, { withFileTypes: true });
21
- for (const entry of entries) {
22
- const absolutePath = path.join(currentDir, entry.name);
23
- if (entry.isDirectory()) {
24
- await visit(absolutePath);
25
- continue;
26
- }
27
- relativeFiles.push(path
28
- .relative(rootDir, absolutePath)
29
- .replace(path.sep === "\\" ? /\\/gu : /\//gu, "/"));
30
- }
31
- }
32
- await visit(rootDir);
33
- return relativeFiles.sort((left, right) => left.localeCompare(right));
34
- }
35
- async function assertDryRunTargetDirectoryReady(projectDir, allowExistingDir) {
36
- if (!(await pathExists(projectDir)) || allowExistingDir) {
37
- return;
38
- }
39
- const entries = await fsp.readdir(projectDir);
40
- if (entries.length > 0) {
41
- throw new Error(formatNonEmptyTargetDirectoryError(projectDir));
42
- }
43
- }
44
- async function buildScaffoldDryRunPlan({ allowExistingDir, alternateRenderTargets, answers, cwd, dataStorageMode, externalLayerId, externalLayerSource, externalLayerSourceLabel, installDependencies, noInstall, onProgress, packageManager, persistencePolicy, profile, projectDir, templateId, variant, withMigrationUi, withTestPreset, withWpEnv, }) {
45
- await assertDryRunTargetDirectoryReady(projectDir, allowExistingDir);
46
- const { path: tempRoot, cleanup } = await createManagedTempRoot("wp-typia-scaffold-plan-");
47
- const previewProjectDir = path.join(tempRoot, "preview-project");
48
- try {
49
- const result = await scaffoldProject({
50
- allowExistingDir: false,
51
- alternateRenderTargets,
52
- answers,
53
- cwd,
54
- dataStorageMode,
55
- externalLayerId,
56
- externalLayerSource,
57
- externalLayerSourceLabel,
58
- installDependencies,
59
- noInstall: true,
60
- onProgress,
61
- packageManager,
62
- persistencePolicy,
63
- profile,
64
- projectDir: previewProjectDir,
65
- templateId,
66
- variant,
67
- withMigrationUi,
68
- withTestPreset,
69
- withWpEnv,
70
- });
71
- const files = await listRelativeProjectFiles(previewProjectDir);
72
- return {
73
- plan: {
74
- dependencyInstall: noInstall ? "skipped-by-flag" : "would-install",
75
- files,
76
- },
77
- result,
78
- };
79
- }
80
- finally {
81
- await cleanup();
82
- }
83
- }
84
- function validateCreateProjectInput(projectInput) {
85
- const normalizedProjectInput = projectInput.trim();
86
- if (normalizedProjectInput.length === 0) {
87
- throw new Error("Project directory is required. Usage: wp-typia create <project-dir> (or wp-typia <project-dir> when <project-dir> is the only positional argument).");
88
- }
89
- const normalizedProjectPath = path.normalize(normalizedProjectInput).replace(/[\\/]+$/u, "") ||
90
- path.normalize(normalizedProjectInput);
91
- if (normalizedProjectPath === "." || normalizedProjectPath === "..") {
92
- throw new Error("`wp-typia create` requires a new project directory. Use an explicit child directory instead of `.` or `..`.");
93
- }
94
- }
95
- function collectProjectDirectoryWarnings(projectDir) {
96
- const warnings = [];
97
- const projectName = path.basename(projectDir);
98
- if (/\s/u.test(projectName)) {
99
- warnings.push(`Project directory "${projectName}" contains spaces. The generated next-step commands will be quoted, but a simple kebab-case directory name is usually easier to use with shells and downstream tooling.`);
100
- }
101
- const shellSensitiveCharacters = Array.from(new Set(projectName.match(/[^A-Za-z0-9._ -]/gu) ?? []));
102
- if (shellSensitiveCharacters.length > 0) {
103
- warnings.push(`Project directory "${projectName}" contains shell-sensitive characters (${shellSensitiveCharacters.join(", ")}). Prefer letters, numbers, ".", "_" and "-" when possible.`);
104
- }
105
- return warnings;
106
- }
107
- function templateUsesPersistenceSettings(templateId, options) {
108
- if (templateId === "persistence") {
109
- return true;
110
- }
111
- if (templateId !== "compound") {
112
- return false;
113
- }
114
- return Boolean(options.dataStorageMode || options.persistencePolicy);
115
- }
116
- function templateSupportsPersistenceFlags(templateId) {
117
- return templateId === "persistence" || templateId === "compound";
118
- }
119
- function templateSupportsCompoundInnerBlocksPreset(templateId) {
120
- return templateId === "compound";
121
- }
122
- function createTemplateLabel(templateId) {
123
- return templateId === OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE
124
- ? "`--template workspace`"
125
- : `"${templateId}"`;
126
- }
127
- function collectTemplateCapabilityWarnings(options) {
128
- const warnings = [];
129
- const trimmedQueryPostType = options.queryPostType?.trim();
130
- if (trimmedQueryPostType &&
131
- options.templateId !== "query-loop" &&
132
- (isBuiltInTemplateId(options.templateId) ||
133
- options.templateId === OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE)) {
134
- 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}".`);
135
- }
136
- if (options.withMigrationUi === true &&
137
- !isBuiltInTemplateId(options.templateId) &&
138
- options.templateId !== OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE) {
139
- 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.`);
140
- }
141
- return warnings;
142
- }
143
- function templateSupportsAlternateRenderTargets(options) {
144
- if (!options.alternateRenderTargets) {
145
- return false;
146
- }
147
- if (options.templateId === "persistence") {
148
- return true;
149
- }
150
- if (options.templateId !== "compound") {
151
- return false;
152
- }
153
- return templateUsesPersistenceSettings(options.templateId, {
154
- dataStorageMode: options.dataStorageMode,
155
- persistencePolicy: options.persistencePolicy,
156
- });
157
- }
158
- function validateCreateFlagContract(options) {
159
- const { alternateRenderTargets, dataStorageMode, innerBlocksPreset, persistencePolicy, templateId, variant, } = options;
160
- if ((dataStorageMode || persistencePolicy) &&
161
- !templateSupportsPersistenceFlags(templateId)) {
162
- throw new Error("`--data-storage` and `--persistence-policy` are supported only for `wp-typia create --template persistence` or `--template compound`.");
163
- }
164
- if (alternateRenderTargets &&
165
- !templateSupportsAlternateRenderTargets({
166
- alternateRenderTargets,
167
- dataStorageMode,
168
- persistencePolicy,
169
- templateId,
170
- })) {
171
- if (templateId === "compound") {
172
- 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.");
173
- }
174
- throw new Error("`--alternate-render-targets` is supported only for `wp-typia create --template persistence` or persistence-enabled `--template compound` scaffolds.");
175
- }
176
- parseAlternateRenderTargets(alternateRenderTargets);
177
- if (innerBlocksPreset &&
178
- !templateSupportsCompoundInnerBlocksPreset(templateId)) {
179
- throw new Error("`--inner-blocks-preset` is supported only for `wp-typia create --template compound`.");
180
- }
181
- parseCompoundInnerBlocksPreset(innerBlocksPreset);
182
- if (isBuiltInTemplateId(templateId)) {
183
- assertBuiltInTemplateVariantAllowed({
184
- templateId,
185
- variant,
186
- });
187
- }
188
- }
189
- function parseSelectableValue(label, value, isValue, allowedValues) {
190
- if (isValue(value)) {
191
- return value;
192
- }
193
- throw new Error(`Unsupported ${label} "${value}". Expected one of: ${allowedValues.join(", ")}`);
194
- }
195
- async function resolveOptionalSelection({ defaultValue, explicitValue, isInteractive, isValue, label, allowedValues, select, shouldResolve = true, yes, }) {
196
- if (!shouldResolve) {
197
- return undefined;
198
- }
199
- if (explicitValue) {
200
- return parseSelectableValue(label, explicitValue, isValue, allowedValues);
201
- }
202
- if (yes) {
203
- return defaultValue;
204
- }
205
- if (isInteractive && select) {
206
- return select();
207
- }
208
- return defaultValue;
209
- }
210
- async function resolveOptionalBooleanFlag({ defaultValue = false, disabled = false, explicitValue, isInteractive, select, yes, }) {
211
- if (disabled) {
212
- return defaultValue;
213
- }
214
- if (typeof explicitValue === "boolean") {
215
- return explicitValue;
216
- }
217
- if (yes) {
218
- return defaultValue;
219
- }
220
- if (isInteractive && select) {
221
- return select();
222
- }
223
- return defaultValue;
224
- }
225
- function quoteShellValue(value) {
226
- if (!value.startsWith("-") &&
227
- /^[A-Za-z0-9._/@:-]+(?:\/[A-Za-z0-9._@:-]+)*$/.test(value)) {
228
- return value;
229
- }
230
- return `'${value.replace(/'/g, `'\"'\"'`)}'`;
231
- }
232
- /**
233
- * Build the printed next-step commands for a scaffolded project.
234
- *
235
- * @param options Project location and package-manager details used to format
236
- * next-step commands.
237
- * @returns Ordered shell commands shown after scaffolding succeeds.
238
- */
239
- export function getNextSteps({ projectInput, projectDir, packageManager, noInstall, templateId, }) {
240
- const cdTarget = path.isAbsolute(projectInput) ? projectDir : projectInput;
241
- const steps = [`cd ${quoteShellValue(cdTarget)}`];
242
- if (noInstall) {
243
- steps.push(formatInstallCommand(packageManager));
244
- }
245
- steps.push(formatRunScript(packageManager, getPrimaryDevelopmentScript(templateId)));
246
- return steps;
247
- }
248
- /**
249
- * Compute optional onboarding guidance shown after scaffolding completes.
250
- *
251
- * @param options Package-manager and template context for optional guidance.
252
- * @returns Optional onboarding note and step list.
253
- */
254
- export function getOptionalOnboarding({ availableScripts, packageManager, templateId, compoundPersistenceEnabled = false, }) {
255
- return {
256
- note: getOptionalOnboardingNote(packageManager, templateId, {
257
- availableScripts,
258
- compoundPersistenceEnabled,
259
- }),
260
- shortNote: getOptionalOnboardingShortNote(packageManager, templateId, {
261
- availableScripts,
262
- compoundPersistenceEnabled,
263
- }),
264
- steps: getOptionalOnboardingSteps(packageManager, templateId, {
265
- availableScripts,
266
- compoundPersistenceEnabled,
267
- }),
268
- };
269
- }
11
+ import { resolveLocalCliPathOption, normalizeOptionalCliString, } from "./cli-validation.js";
12
+ export { getNextSteps, getOptionalOnboarding } from "./cli-scaffold-output.js";
270
13
  /**
271
14
  * Resolve scaffold options, prompts, and follow-up steps for one CLI run.
272
15
  *
@@ -381,73 +124,37 @@ export async function runScaffoldFlow({ projectInput, cwd = process.cwd(), templ
381
124
  if (resolvedTemplateId === "compound" && resolvedInnerBlocksPreset) {
382
125
  answers.compoundInnerBlocksPreset = resolvedInnerBlocksPreset;
383
126
  }
127
+ const emissionOptions = {
128
+ allowExistingDir,
129
+ alternateRenderTargets,
130
+ answers,
131
+ cwd,
132
+ dataStorageMode: resolvedDataStorage,
133
+ externalLayerId: resolvedExternalLayerSelection.externalLayerId,
134
+ externalLayerSource: resolvedExternalLayerSelection.externalLayerSource,
135
+ externalLayerSourceLabel: normalizedExternalLayerSource,
136
+ installDependencies,
137
+ noInstall,
138
+ onProgress,
139
+ packageManager: resolvedPackageManager,
140
+ persistencePolicy: resolvedPersistencePolicy,
141
+ profile: resolvedProfile,
142
+ projectDir,
143
+ templateId: resolvedTemplateId,
144
+ variant,
145
+ withMigrationUi: resolvedWithMigrationUi,
146
+ withTestPreset: resolvedWithTestPreset,
147
+ withWpEnv: resolvedWithWpEnv,
148
+ };
384
149
  const resolvedResult = dryRun
385
- ? await buildScaffoldDryRunPlan({
386
- allowExistingDir,
387
- alternateRenderTargets,
388
- answers,
389
- cwd,
390
- dataStorageMode: resolvedDataStorage,
391
- externalLayerId: resolvedExternalLayerSelection.externalLayerId,
392
- externalLayerSource: resolvedExternalLayerSelection.externalLayerSource,
393
- externalLayerSourceLabel: normalizedExternalLayerSource,
394
- installDependencies,
395
- noInstall,
396
- onProgress,
397
- packageManager: resolvedPackageManager,
398
- persistencePolicy: resolvedPersistencePolicy,
399
- profile: resolvedProfile,
400
- projectDir,
401
- templateId: resolvedTemplateId,
402
- variant,
403
- withMigrationUi: resolvedWithMigrationUi,
404
- withTestPreset: resolvedWithTestPreset,
405
- withWpEnv: resolvedWithWpEnv,
406
- })
150
+ ? await buildScaffoldDryRunPlan(emissionOptions)
407
151
  : {
408
152
  plan: undefined,
409
- result: await scaffoldProject({
410
- alternateRenderTargets,
411
- answers,
412
- allowExistingDir,
413
- cwd,
414
- dataStorageMode: resolvedDataStorage,
415
- externalLayerId: resolvedExternalLayerSelection.externalLayerId,
416
- externalLayerSource: resolvedExternalLayerSelection.externalLayerSource,
417
- externalLayerSourceLabel: normalizedExternalLayerSource,
418
- installDependencies,
419
- noInstall,
420
- onProgress,
421
- packageManager: resolvedPackageManager,
422
- persistencePolicy: resolvedPersistencePolicy,
423
- profile: resolvedProfile,
424
- projectDir,
425
- templateId: resolvedTemplateId,
426
- variant,
427
- withMigrationUi: resolvedWithMigrationUi,
428
- withTestPreset: resolvedWithTestPreset,
429
- withWpEnv: resolvedWithWpEnv,
430
- }),
153
+ result: await emitScaffoldProject(emissionOptions),
431
154
  };
432
- let availableScripts;
433
- if (!dryRun) {
434
- try {
435
- const parsedPackageJson = await readJsonFile(path.join(projectDir, "package.json"), {
436
- context: "generated package manifest",
437
- });
438
- const scripts = parsedPackageJson.scripts &&
439
- typeof parsedPackageJson.scripts === "object" &&
440
- !Array.isArray(parsedPackageJson.scripts)
441
- ? parsedPackageJson.scripts
442
- : {};
443
- availableScripts = Object.entries(scripts)
444
- .filter(([, value]) => typeof value === "string")
445
- .map(([scriptName]) => scriptName);
446
- }
447
- catch {
448
- availableScripts = undefined;
449
- }
450
- }
155
+ const availableScripts = dryRun
156
+ ? undefined
157
+ : await readGeneratedPackageScripts(projectDir);
451
158
  return {
452
159
  dryRun,
453
160
  optionalOnboarding: getOptionalOnboarding({
@@ -40,6 +40,6 @@ export { EXTERNAL_TEMPLATE_CACHE_TTL_DAYS_ENV, pruneExternalTemplateCache, } fro
40
40
  export type { ExternalTemplateCachePruneOptions, ExternalTemplateCachePruneResult, } from "./template-source-cache.js";
41
41
  export { STALE_TEMP_ROOT_MAX_AGE_MS, WP_TYPIA_TEMP_ROOT_PREFIX, cleanupManagedTempRoot, cleanupStaleTempRoots, createManagedTempRoot, getTrackedTempRoots, } from "./temp-roots.js";
42
42
  export { createReadlinePrompt, createDoctorRunSummary, createCliCommandError, createCliDiagnosticCodeError, CliDiagnosticError, CLI_DIAGNOSTIC_CODES, formatCliDiagnosticError, formatAddHelpText, formatDoctorCheckLine, formatDoctorSummaryLine, formatHelpText, formatTemplateDetails, formatTemplateFeatures, formatTemplateSummary, getDoctorChecks, getDoctorExitFailureChecks, getDoctorExitFailureDetailLines, getDoctorFailureDetailLines, getFailingDoctorChecks, getNextSteps, getOptionalOnboarding, getWorkspaceBlockSelectOptions, getWorkspaceBlockSelectOptionsAsync, HOOKED_BLOCK_POSITION_IDS, EDITOR_PLUGIN_SLOT_IDS, isCliDiagnosticError, runAddAdminViewCommand, runAddAbilityCommand, runAddAiFeatureCommand, runAddBindingSourceCommand, runAddBlockCommand, runAddBlockStyleCommand, runAddBlockTransformCommand, runAddCoreVariationCommand, runAddEditorPluginCommand, runAddHookedBlockCommand, runAddPatternCommand, runAddPostMetaCommand, runDoctor, runAddVariationCommand, runScaffoldFlow, } from "./cli-core.js";
43
- export { extractPatternSectionRoleMatches, extractPatternSectionRolesFromAttributes, formatPatternCatalogDiagnostics, PATTERN_CATALOG_SCOPE_IDS, resolvePatternCatalogContentFile, validatePatternCatalog, } from "./pattern-catalog.js";
43
+ export { extractPatternSectionRoleMatches, extractPatternSectionRolesFromAttributes, formatPatternCatalogDiagnostics, PATTERN_CATALOG_SCOPE_IDS, PATTERN_SECTION_ROLE_PATTERN, PATTERN_TAG_PATTERN, resolvePatternCatalogContentFile, validatePatternCatalog, } from "./pattern-catalog.js";
44
44
  export type { CliDiagnosticCode, CliDiagnosticCodeError, CliDiagnosticMessage, DoctorCheck, DoctorCheckScope, DoctorExitPolicy, DoctorFailureSummary, DoctorRunSummary, EditorPluginSlotId, HookedBlockPositionId, ReadlinePrompt, WorkspaceBlockSelectOption, } from "./cli-core.js";
45
45
  export type { PatternCatalogDiagnostic, PatternCatalogDiagnosticCode, PatternCatalogDiagnosticSeverity, PatternCatalogEntry, PatternCatalogScope, PatternCatalogSectionRoleConvention, PatternCatalogSectionRoleMatch, PatternCatalogValidationOptions, PatternCatalogValidationResult, } from "./pattern-catalog.js";