create-zudo-doc 5.18.2 → 5.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.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,20 @@ All notable changes to `create-zudo-doc` are documented in this file.
4
4
 
5
5
  The format is based on Keep a Changelog, and release notes are generated from the changelog MDX pages.
6
6
 
7
+ ## [5.19.0] - 2026-09-07
8
+
9
+ ### Features
10
+
11
+ - The first positional argument is now a **destination path**, not just a project name. `create-zudo-doc sub/ref-doc` creates `sub/ref-doc` and derives the project name from the final segment (`ref-doc`); previously it failed with a project-name validation error because the one argument meant both the directory to create and the name written into the generated `package.json`. Relative (`../ref-doc`) and absolute destinations are accepted; `.`, `..`, and a filesystem root are rejected because they name no final segment. `--name` still takes a bare package name and now acts as an explicit override of the derived name while the positional continues to supply the directory. The resolved destination is followed consistently by dependency install, `git init`, and the printed `cd <dest>` hint. The programmatic `CreateOptions` API is unchanged — there `projectName` still doubles as the directory. (5a928d51e)
12
+
13
+ ### Bug Fixes
14
+
15
+ - The printed `cd` target is quoted when the destination contains whitespace, which a path destination may now legally do, and an empty destination no longer resolves the target directory to the current working directory. (134248eba)
16
+
17
+ ### Other Changes
18
+
19
+ - The EN and JA `create-zudo-doc` reference pages and the package README document the destination argument: an accepted-forms table, both verbatim error messages, the `--name` override, a scaffolding-into-a-subdirectory example, and a note that the programmatic API has no `destination`. (dc83c4615)
20
+
7
21
  ## [5.18.2] - 2026-09-06
8
22
 
9
23
  ### Bug Fixes
package/README.md CHANGED
@@ -42,6 +42,42 @@ pnpm create zudo-doc my-docs \
42
42
  --install
43
43
  ```
44
44
 
45
+ ### Destination paths
46
+
47
+ The positional argument is the **destination** — the directory to scaffold
48
+ into, not just a bare name. It may be a path (relative, `../`-prefixed, or
49
+ absolute), and its **last segment** becomes the project name written to the
50
+ generated `package.json`:
51
+
52
+ ```bash
53
+ # Creates ./sub/ref-doc with the package name "ref-doc"
54
+ pnpm create zudo-doc sub/ref-doc --yes
55
+ ```
56
+
57
+ Only that last segment has to satisfy the project-name grammar (starts with a
58
+ lowercase letter or digit; lowercase letters, digits, dots, underscores, and
59
+ hyphens only; 214 characters max) — the directories leading up to it are just a
60
+ path. `.`, `..`, and a filesystem root are rejected, because they name no last
61
+ segment to derive the project name from.
62
+
63
+ Use `--name` to override the derived name while the positional argument keeps
64
+ supplying the directory:
65
+
66
+ ```bash
67
+ # Creates ./sub/My-Docs with the package name "ref-doc"
68
+ pnpm create zudo-doc sub/My-Docs --name ref-doc --yes
69
+ ```
70
+
71
+ This makes side-by-side scaffolding straightforward — for example, comparing
72
+ two template versions by generating both into one scratch directory and
73
+ diffing them:
74
+
75
+ ```bash
76
+ pnpm create zudo-doc@5.17.0 /tmp/tpl-check/v5.17 --yes
77
+ pnpm create zudo-doc@5.18.0 /tmp/tpl-check/v5.18 --yes
78
+ diff -r /tmp/tpl-check/v5.17 /tmp/tpl-check/v5.18
79
+ ```
80
+
45
81
  ### Locales and translations
46
82
 
47
83
  `--lang` selects the primary locale. Its pages use the unprefixed
@@ -84,7 +120,8 @@ Built-in UI translations resolve in this order:
84
120
 
85
121
  | Flag | Description | Default |
86
122
  |------|-------------|---------|
87
- | `[project-name]` | Project name (positional arg or `--name`) | prompted |
123
+ | `[destination]` | Directory to scaffold into; may be a path whose last segment becomes the project name | prompted |
124
+ | `--name <name>` | Project name written to `package.json`; overrides the name derived from the destination | destination's last segment |
88
125
  | `--lang <code>` | Default language: `en`, `ja`, `zh-cn`, `zh-tw`, `ko`, `es`, `fr`, `de`, `pt` | `en` |
89
126
  | `--additional-langs <a,b>` | Ordered additional locale codes; implies i18n and replaces a preset list | none |
90
127
  | `--pm <manager>` | Package manager: `pnpm`, `npm`, `yarn`, `bun` | detected |
package/dist/api.js CHANGED
@@ -1,8 +1,7 @@
1
- import path from "path";
2
1
  import { SINGLE_SCHEMES, THEME_PACKS } from "./constants.js";
3
2
  import { parseChangelogPackages, validateChangelogPackages, validateHeaderRightItems, validateMetaTags, } from "./preset.js";
4
3
  import { scaffold } from "./scaffold.js";
5
- import { initGitRepo, installDependencies, validateProjectName } from "./utils.js";
4
+ import { initGitRepo, installDependencies, resolveTargetDir, validateProjectName, } from "./utils.js";
6
5
  import { resolveLocalePlan } from "./locale-plan.js";
7
6
  export { resolveLocalePlan, } from "./locale-plan.js";
8
7
  export async function createZudoDoc(options) {
@@ -69,7 +68,9 @@ export async function createZudoDoc(options) {
69
68
  changelogPackages,
70
69
  };
71
70
  await scaffold(choices);
72
- const targetDir = path.resolve(process.cwd(), choices.projectName);
71
+ // No `destination` on CreateOptions — the programmatic API keeps the
72
+ // project name doubling as the directory, so the resolver falls back to it.
73
+ const targetDir = resolveTargetDir(choices);
73
74
  if (install) {
74
75
  installDependencies(targetDir, choices.packageManager);
75
76
  }
package/dist/cli.d.ts CHANGED
@@ -1,5 +1,14 @@
1
1
  export interface CliArgs {
2
+ /**
3
+ * The `--name` flag: a bare package name. Kept separate from `destination`
4
+ * so the locked project-name grammar still rejects a path here.
5
+ */
2
6
  name?: string;
7
+ /**
8
+ * The first positional argument: the directory to scaffold into. May be a
9
+ * path; its final segment becomes the project name unless `--name` overrides.
10
+ */
11
+ destination?: string;
3
12
  lang?: string;
4
13
  additionalLangs?: string[];
5
14
  colorSchemeMode?: "single" | "light-dark";
package/dist/cli.js CHANGED
@@ -2,7 +2,7 @@ import minimist from "minimist";
2
2
  import pc from "picocolors";
3
3
  import { FEATURES, SINGLE_SCHEMES, SUPPORTED_LANGS, THEME_PACKS } from "./constants.js";
4
4
  import { parseChangelogPackages, validateChangelogPackages, } from "./preset.js";
5
- import { validateProjectName } from "./utils.js";
5
+ import { normalizeDestination, splitDestination, validateProjectName, } from "./utils.js";
6
6
  import { resolveLocalePlan } from "./locale-plan.js";
7
7
  /** Presets are the base layer; an explicitly supplied CLI list replaces it. */
8
8
  export function layerAdditionalLangs(presetValue, cliValue) {
@@ -39,12 +39,12 @@ export function parseArgs(argv = process.argv.slice(2)) {
39
39
  return argv.some((a) => a === `--${flag}` || a === `--no-${flag}`);
40
40
  }
41
41
  const args = {};
42
- // Project name: first positional arg or --name
43
- if (raw.name) {
42
+ // Destination: first positional arg. Project name: --name (a bare name), or
43
+ // the destination's final segment when --name is absent.
44
+ if (raw.name)
44
45
  args.name = raw.name;
45
- }
46
- else if (raw._.length > 0 && typeof raw._[0] === "string") {
47
- args.name = raw._[0];
46
+ if (raw._.length > 0 && typeof raw._[0] === "string") {
47
+ args.destination = raw._[0];
48
48
  }
49
49
  if (raw.lang)
50
50
  args.lang = raw.lang;
@@ -100,10 +100,15 @@ export function printHelp() {
100
100
  const themePackList = THEME_PACKS.map((t) => t.slug).join(", ");
101
101
  const featureHelp = FEATURES.map((f) => ` --[no-]${f.cliFlag.padEnd(22)} ${f.hint}`).join("\n");
102
102
  console.log(`
103
- ${pc.bold("Usage:")} create-zudo-doc [project-name] [options]
103
+ ${pc.bold("Usage:")} create-zudo-doc [destination] [options]
104
+
105
+ ${pc.dim(" destination")} Directory to create the project in; may be a path
106
+ (e.g. sub/my-docs). Its last segment becomes the
107
+ project name. Default: my-docs
104
108
 
105
109
  ${pc.bold("Options:")}
106
- --name <name> Project name (or first positional arg)
110
+ --name <name> Project name written to package.json. Overrides
111
+ the name derived from the destination's last segment
107
112
  --lang <code> Default language (${langList})
108
113
  Default: en
109
114
  --additional-langs <a,b> Additional locale codes (ordered; implies i18n)
@@ -137,6 +142,9 @@ ${pc.bold("Examples:")}
137
142
  ${pc.dim("# Fully specified")}
138
143
  create-zudo-doc my-docs --lang ja --scheme "Default Dark" --no-i18n --pm pnpm --install
139
144
 
145
+ ${pc.dim("# Scaffold into a subdirectory (package name: ref-doc)")}
146
+ create-zudo-doc sub/ref-doc --yes
147
+
140
148
  ${pc.dim("# Per-package changelog pages")}
141
149
  create-zudo-doc my-docs --changelog-packages core,cli --yes
142
150
  `);
@@ -203,5 +211,15 @@ export function validateArgs(args) {
203
211
  if (nameError)
204
212
  return nameError;
205
213
  }
214
+ if (args.destination !== undefined) {
215
+ // With --name present the derived segment is overridden, so the final
216
+ // segment only has to be a legal *directory* name — but a destination that
217
+ // names no final segment at all (".", "..", "/") is still refused.
218
+ const result = args.name
219
+ ? normalizeDestination(args.destination)
220
+ : splitDestination(args.destination);
221
+ if (!result.ok)
222
+ return result.error;
223
+ }
206
224
  return null;
207
225
  }
package/dist/index.js CHANGED
@@ -1,4 +1,3 @@
1
- import path from "path";
2
1
  import * as p from "@clack/prompts";
3
2
  import pc from "picocolors";
4
3
  import { layerAdditionalLangs, parseArgs, printHelp, validateArgs, } from "./cli.js";
@@ -6,7 +5,7 @@ import { FEATURES } from "./constants.js";
6
5
  import { loadPreset } from "./preset.js";
7
6
  import { runPrompts } from "./prompts.js";
8
7
  import { scaffold } from "./scaffold.js";
9
- import { installDependencies, initGitRepo, pmRunCommand } from "./utils.js";
8
+ import { destinationLabel, initGitRepo, installDependencies, normalizeDestination, pmRunCommand, resolveTargetDir, } from "./utils.js";
10
9
  async function main() {
11
10
  const args = parseArgs();
12
11
  // Handle --help
@@ -36,6 +35,17 @@ async function main() {
36
35
  }
37
36
  }
38
37
  // CLI args override preset values
38
+ if (args.destination !== undefined) {
39
+ // validateArgs() already rejected a destination with no usable final
40
+ // segment, so this only fails on an input that never reaches here.
41
+ const dest = normalizeDestination(args.destination);
42
+ if (dest.ok) {
43
+ prefilled.destination = dest.destination;
44
+ prefilled.projectName = dest.finalSegment;
45
+ }
46
+ }
47
+ // --name is a deliberate override of the name derived from the destination's
48
+ // last segment; the positional keeps supplying the directory either way.
39
49
  if (args.name)
40
50
  prefilled.projectName = args.name;
41
51
  if (args.lang)
@@ -111,7 +121,11 @@ async function main() {
111
121
  prefilled.features = { ...featureDefaults, ...prefilled.features };
112
122
  }
113
123
  const choices = await runPrompts(prefilled);
114
- const targetDir = path.resolve(process.cwd(), choices.projectName);
124
+ const targetDir = resolveTargetDir(choices);
125
+ const destination = destinationLabel(choices);
126
+ // A destination may now contain spaces (only its LAST segment goes through
127
+ // the project-name grammar), so the `cd` we print has to stay copy-pastable.
128
+ const cdTarget = /\s/.test(destination) ? `"${destination}"` : destination;
115
129
  const s = p.spinner();
116
130
  s.start("Scaffolding project...");
117
131
  try {
@@ -138,7 +152,7 @@ async function main() {
138
152
  initialValue: true,
139
153
  });
140
154
  if (p.isCancel(result)) {
141
- p.outro(`Done! cd ${choices.projectName} and install dependencies manually.`);
155
+ p.outro(`Done! cd ${cdTarget} and install dependencies manually.`);
142
156
  return;
143
157
  }
144
158
  shouldInstall = result;
@@ -179,10 +193,10 @@ async function main() {
179
193
  break;
180
194
  }
181
195
  }
182
- p.outro(`${pc.green("Done!")} Your project is ready at ${pc.cyan(choices.projectName)}`);
196
+ p.outro(`${pc.green("Done!")} Your project is ready at ${pc.cyan(destination)}`);
183
197
  console.log();
184
198
  console.log(` ${pc.bold("Next steps:")}`);
185
- console.log(` cd ${choices.projectName}`);
199
+ console.log(` cd ${cdTarget}`);
186
200
  console.log(` ${pmRunCommand(choices.packageManager, "dev")}`);
187
201
  console.log();
188
202
  }
package/dist/prompts.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { type PresetHeaderRightItem, type PresetMetaTagsConfig } from "./preset.js";
2
2
  export interface UserChoices {
3
3
  projectName: string;
4
+ destination?: string;
4
5
  defaultLang: string;
5
6
  additionalLangs?: string[];
6
7
  colorSchemeMode: "single" | "light-dark";
@@ -22,6 +23,8 @@ export interface UserChoices {
22
23
  }
23
24
  export interface PartialChoices {
24
25
  projectName?: string;
26
+ /** See `UserChoices.destination` — CLI-supplied, never prompted for. */
27
+ destination?: string;
25
28
  defaultLang?: string;
26
29
  additionalLangs?: string[];
27
30
  colorSchemeMode?: "single" | "light-dark";
package/dist/prompts.js CHANGED
@@ -240,6 +240,7 @@ export async function runPrompts(prefilled = {}) {
240
240
  : features.filter((feature) => feature !== "i18n");
241
241
  return {
242
242
  projectName,
243
+ destination: prefilled.destination,
243
244
  defaultLang: localePlan.defaultLang,
244
245
  additionalLangs: prefilled.additionalLangs === undefined
245
246
  ? undefined
@@ -31,5 +31,5 @@ export declare function deriveDocSkillName(projectName: string): string;
31
31
  *
32
32
  * Bumped in lockstep by scripts/release-create-zudo-doc.sh.
33
33
  */
34
- export declare const ZUDO_DOC_PIN = "^5.18.2";
34
+ export declare const ZUDO_DOC_PIN = "^5.19.0";
35
35
  export declare function scaffold(choices: UserChoices): Promise<void>;
package/dist/scaffold.js CHANGED
@@ -5,7 +5,7 @@ import { generateZfbConfig } from "./zfb-config-gen.js";
5
5
  import { generateCLAUDEFile } from "./claude-md-gen.js";
6
6
  import { composeFeatures } from "./compose.js";
7
7
  import { featureModules } from "./features/index.js";
8
- import { capitalize, hasAncestorPnpmWorkspace, pmRunCommand, } from "./utils.js";
8
+ import { capitalize, destinationLabel, hasAncestorPnpmWorkspace, pmRunCommand, resolveTargetDir, } from "./utils.js";
9
9
  import { resolveLocalePlan, } from "./locale-plan.js";
10
10
  // Kept as a compatibility export from the pre-list API. Internal scaffold
11
11
  // emitters consume the resolved LocalePlan directly; see utils.ts for the
@@ -47,7 +47,7 @@ export function deriveDocSkillName(projectName) {
47
47
  *
48
48
  * Bumped in lockstep by scripts/release-create-zudo-doc.sh.
49
49
  */
50
- export const ZUDO_DOC_PIN = "^5.18.2";
50
+ export const ZUDO_DOC_PIN = "^5.19.0";
51
51
  /**
52
52
  * Files in `templates/base/**` that must not be copied by the unconditional
53
53
  * base mirror. Each entry is matched against the path relative to
@@ -262,11 +262,11 @@ export async function scaffold(choices) {
262
262
  if (localePlan.overridesExplicitDisable) {
263
263
  console.warn("additional-langs requires i18n; enabling it despite --no-i18n");
264
264
  }
265
- const targetDir = path.resolve(process.cwd(), choices.projectName);
265
+ const targetDir = resolveTargetDir(choices);
266
266
  if (await fs.pathExists(targetDir)) {
267
267
  const contents = await fs.readdir(targetDir);
268
268
  if (contents.length > 0) {
269
- throw new Error(`Directory "${choices.projectName}" already exists and is not empty`);
269
+ throw new Error(`Directory "${destinationLabel(choices)}" already exists and is not empty`);
270
270
  }
271
271
  }
272
272
  // body-foot-util-area.tsx ships the DocHistory component inline (byte-
@@ -892,7 +892,7 @@ function generatePackageJson(choices, localePlan) {
892
892
  // `/exclude` at module scope from the always-bundled chrome graph; #3110
893
893
  // moved compileExclude into @takazudo/zudo-doc, so projects with both
894
894
  // docHistory and assetViewer off no longer need the package at all.
895
- deps["@takazudo/zudo-doc-history-server"] = "^5.18.2";
895
+ deps["@takazudo/zudo-doc-history-server"] = "^5.19.0";
896
896
  // tsx is no longer needed here: the relocated package plugin imports the
897
897
  // runner directly (no `tsx -e` spawn) since the package ships compiled
898
898
  // dist/ — package-first migration #2321 (#2337).
package/dist/utils.d.ts CHANGED
@@ -6,6 +6,63 @@
6
6
  * programmatic API.
7
7
  */
8
8
  export declare function validateProjectName(name: string): string | null;
9
+ export type DestinationPath = {
10
+ ok: true;
11
+ destination: string;
12
+ finalSegment: string;
13
+ } | {
14
+ ok: false;
15
+ error: string;
16
+ };
17
+ export type DestinationSplit = {
18
+ ok: true;
19
+ destination: string;
20
+ projectName: string;
21
+ } | {
22
+ ok: false;
23
+ error: string;
24
+ };
25
+ /**
26
+ * Normalize a destination path and expose its final segment.
27
+ *
28
+ * Upward traversal (`../ref-doc`) is **allowed** — `..` is simply a relative
29
+ * way to name a directory outside the cwd, and absolute destinations are
30
+ * accepted too, so rejecting it would be inconsistent. `.`, `..`, and a
31
+ * filesystem root are rejected because they name no final segment.
32
+ *
33
+ * `path.resolve()` is deliberately NOT applied before extracting the segment:
34
+ * resolving `"."` first would turn the *current* directory's basename into an
35
+ * apparently valid project name and scaffold into the cwd.
36
+ */
37
+ export declare function normalizeDestination(raw: string): DestinationPath;
38
+ /**
39
+ * Split a CLI destination argument into the directory to scaffold into and the
40
+ * package name written to the generated `package.json`.
41
+ *
42
+ * The final path segment becomes the project name and goes through the
43
+ * unchanged `validateProjectName`; everything before it is just a directory
44
+ * path and is deliberately not validated as a package name. Callers that
45
+ * supply the name separately (`--name`) use `normalizeDestination` instead, so
46
+ * a directory segment that is not a legal package name stays acceptable.
47
+ */
48
+ export declare function splitDestination(raw: string): DestinationSplit;
49
+ /** The two destination-bearing fields every target-dir consumer needs. */
50
+ export interface DestinationChoices {
51
+ projectName: string;
52
+ /**
53
+ * Directory to scaffold into, as given on the CLI. Absent on the
54
+ * programmatic/preset paths, where the project name is also the directory.
55
+ */
56
+ destination?: string;
57
+ }
58
+ /** The destination as the user expressed it — for messages and `cd` output. */
59
+ export declare function destinationLabel(choices: DestinationChoices): string;
60
+ /**
61
+ * The single source of truth for the absolute directory a scaffold is written
62
+ * to. Every caller that needs it (scaffold.ts, api.ts, index.ts) must use this
63
+ * rather than re-deriving it — that is how the three sites used to drift.
64
+ */
65
+ export declare function resolveTargetDir(choices: DestinationChoices): string;
9
66
  export declare function installDependencies(dir: string, pm: string): void;
10
67
  export type GitInitResult = {
11
68
  status: "ok";
package/dist/utils.js CHANGED
@@ -3,8 +3,13 @@ import fs from "fs-extra";
3
3
  import path from "path";
4
4
  import { resolveLocalePlan } from "./locale-plan.js";
5
5
  // Project-name grammar (locked by F4 — S4 #2013):
6
- // /^[a-z0-9][a-z0-9._-]*$/, max 214 chars, unscoped, used as both directory
7
- // name and package name. Mirrors npm's unscoped-name rules + max path safety.
6
+ // /^[a-z0-9][a-z0-9._-]*$/, max 214 chars, unscoped. Mirrors npm's
7
+ // unscoped-name rules + max path safety.
8
+ //
9
+ // This grammar covers the *package name* only — the value written to the
10
+ // generated package.json's `name`. The directory the project is written to is
11
+ // a separate concept (`UserChoices.destination`, see `splitDestination` below)
12
+ // and may be any path; only its final segment is checked against this grammar.
8
13
  const PROJECT_NAME_RE = /^[a-z0-9][a-z0-9._-]*$/;
9
14
  const PROJECT_NAME_MAX = 214;
10
15
  /**
@@ -27,6 +32,81 @@ export function validateProjectName(name) {
27
32
  }
28
33
  return null;
29
34
  }
35
+ /** Trailing path separators, platform-aware (Windows accepts both). */
36
+ const TRAILING_SEPARATORS = path.sep === "\\" ? /[\\/]+$/ : /\/+$/;
37
+ /**
38
+ * Normalize a destination path and expose its final segment.
39
+ *
40
+ * Upward traversal (`../ref-doc`) is **allowed** — `..` is simply a relative
41
+ * way to name a directory outside the cwd, and absolute destinations are
42
+ * accepted too, so rejecting it would be inconsistent. `.`, `..`, and a
43
+ * filesystem root are rejected because they name no final segment.
44
+ *
45
+ * `path.resolve()` is deliberately NOT applied before extracting the segment:
46
+ * resolving `"."` first would turn the *current* directory's basename into an
47
+ * apparently valid project name and scaffold into the cwd.
48
+ */
49
+ export function normalizeDestination(raw) {
50
+ const given = raw.trim();
51
+ if (given === "") {
52
+ return { ok: false, error: "Destination path is required" };
53
+ }
54
+ // `sub/ref-doc/` and `sub/ref-doc` name the same directory; keep the rest of
55
+ // the string as the user typed it so messages and `cd` output echo it back.
56
+ const destination = given.replace(TRAILING_SEPARATORS, "") || given;
57
+ const finalSegment = path.basename(path.normalize(destination));
58
+ if (finalSegment === "" || finalSegment === "." || finalSegment === "..") {
59
+ return {
60
+ ok: false,
61
+ error: `Destination "${given}" has no final path segment to name the project. ` +
62
+ `Pass a destination whose last segment names the project, e.g. "sub/my-docs"`,
63
+ };
64
+ }
65
+ return { ok: true, destination, finalSegment };
66
+ }
67
+ /**
68
+ * Split a CLI destination argument into the directory to scaffold into and the
69
+ * package name written to the generated `package.json`.
70
+ *
71
+ * The final path segment becomes the project name and goes through the
72
+ * unchanged `validateProjectName`; everything before it is just a directory
73
+ * path and is deliberately not validated as a package name. Callers that
74
+ * supply the name separately (`--name`) use `normalizeDestination` instead, so
75
+ * a directory segment that is not a legal package name stays acceptable.
76
+ */
77
+ export function splitDestination(raw) {
78
+ const normalized = normalizeDestination(raw);
79
+ if (!normalized.ok)
80
+ return normalized;
81
+ const nameError = validateProjectName(normalized.finalSegment);
82
+ if (nameError) {
83
+ return {
84
+ ok: false,
85
+ error: `Invalid project name "${normalized.finalSegment}" — the last segment of ` +
86
+ `destination "${raw.trim()}" is used as the package name. ${nameError}`,
87
+ };
88
+ }
89
+ return {
90
+ ok: true,
91
+ destination: normalized.destination,
92
+ projectName: normalized.finalSegment,
93
+ };
94
+ }
95
+ /** The destination as the user expressed it — for messages and `cd` output. */
96
+ export function destinationLabel(choices) {
97
+ // `||`, not `??`: an empty destination is as absent as an undefined one, and
98
+ // letting `""` through would resolve the target directory to the cwd and
99
+ // scaffold on top of it.
100
+ return choices.destination || choices.projectName;
101
+ }
102
+ /**
103
+ * The single source of truth for the absolute directory a scaffold is written
104
+ * to. Every caller that needs it (scaffold.ts, api.ts, index.ts) must use this
105
+ * rather than re-deriving it — that is how the three sites used to drift.
106
+ */
107
+ export function resolveTargetDir(choices) {
108
+ return path.resolve(process.cwd(), destinationLabel(choices));
109
+ }
30
110
  export function installDependencies(dir, pm) {
31
111
  const commands = {
32
112
  pnpm: "pnpm install",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-zudo-doc",
3
- "version": "5.18.2",
3
+ "version": "5.19.0",
4
4
  "description": "Create a new zudo-doc documentation site",
5
5
  "license": "MIT",
6
6
  "author": "Takeshi Takatsudo",