create-zudo-doc 5.18.1 → 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 +25 -0
- package/README.md +44 -6
- package/dist/api.js +4 -3
- package/dist/cli.d.ts +9 -0
- package/dist/cli.js +26 -8
- package/dist/constants.js +6 -6
- package/dist/index.js +20 -6
- package/dist/prompts.d.ts +3 -0
- package/dist/prompts.js +1 -0
- package/dist/scaffold.d.ts +1 -1
- package/dist/scaffold.js +28 -25
- package/dist/utils.d.ts +57 -0
- package/dist/utils.js +82 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,31 @@ 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
|
+
|
|
21
|
+
## [5.18.2] - 2026-09-06
|
|
22
|
+
|
|
23
|
+
### Bug Fixes
|
|
24
|
+
|
|
25
|
+
- `@takazudo/zdtp` is added to a generated project only when the Design Token Panel is enabled. It had been an unconditional direct dependency of every scaffold — which is how generated projects avoided the `@takazudo/zudo-doc` bundling bug fixed in this same release — so every project without the panel installed and carried a package it never used. With that bug fixed upstream, the dependency is now gated on `designTokenPanel`. (a93ff2293)
|
|
26
|
+
- Six feature flags now default on, matching the FEATURES catalog: `--sidebar-resizer`, `--sidebar-toggle`, `--toc-toggle`, `--doc-history`, `--llms-txt`, and `--asset-viewer`. A scaffold created without explicit flags now includes these features; pass the matching `--no-` flag to opt out. (6d0d3bfb9)
|
|
27
|
+
|
|
28
|
+
### Other Changes
|
|
29
|
+
|
|
30
|
+
- The CLI reference table was contradicted by the change above and has been corrected: the default column now matches the catalog, and the previously-missing `--[no-]asset-viewer` row was added in catalog position. The package README's feature list was corrected the same way. (c908f7f9a, f006d4ada)
|
|
31
|
+
|
|
7
32
|
## [5.18.1] - 2026-09-06
|
|
8
33
|
|
|
9
34
|
### Other Changes
|
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
|
-
| `[
|
|
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 |
|
|
@@ -113,18 +150,19 @@ Each feature has a `--[no-]<flag>` form. Passing `--feature` enables it; `--no-f
|
|
|
113
150
|
| `--[no-]search` | Full-text search | on |
|
|
114
151
|
| `--[no-]sidebar-filter` | Real-time sidebar filter | on |
|
|
115
152
|
| `--[no-]image-enlarge` | Click-to-enlarge for oversized images | on |
|
|
116
|
-
| `--[no-]asset-viewer` | Viewer pages for files under `public/assets/` |
|
|
153
|
+
| `--[no-]asset-viewer` | Viewer pages for files under `public/assets/` | on |
|
|
117
154
|
| `--[no-]tag-governance` | Vocabulary-aware tag audit + suggest scripts | off |
|
|
118
155
|
| `--[no-]claude-resources` | Auto-generate Claude Code docs from `.claude/` | off |
|
|
119
156
|
| `--[no-]codex-resources` | Auto-generate Codex docs from `.codex/` + `AGENTS.md` | off |
|
|
120
157
|
| `--[no-]claude-skills` | Ship zudo-doc Claude Code skills (design-system, translate, version-bump) | off |
|
|
121
158
|
| `--[no-]design-token-panel` | Interactive panel for tweaking spacing, font, color tokens | off |
|
|
122
|
-
| `--[no-]sidebar-resizer` | Draggable sidebar width handle |
|
|
123
|
-
| `--[no-]sidebar-toggle` | Show/hide desktop sidebar button |
|
|
159
|
+
| `--[no-]sidebar-resizer` | Draggable sidebar width handle | on |
|
|
160
|
+
| `--[no-]sidebar-toggle` | Show/hide desktop sidebar button | on |
|
|
161
|
+
| `--[no-]toc-toggle` | Show/hide the desktop table of contents (edge chevron, xl screens) | on |
|
|
124
162
|
| `--[no-]versioning` | Multi-version documentation support | off |
|
|
125
|
-
| `--[no-]doc-history` | Show per-page document edit history |
|
|
163
|
+
| `--[no-]doc-history` | Show per-page document edit history | on |
|
|
126
164
|
| `--[no-]body-foot-util` | Right-aligned strip below each doc (history trigger + GitHub source link) | off |
|
|
127
|
-
| `--[no-]llms-txt` | Generate `llms.txt` for LLM consumption |
|
|
165
|
+
| `--[no-]llms-txt` | Generate `llms.txt` for LLM consumption | on |
|
|
128
166
|
| `--[no-]skill-symlinker` | Symlink documentation skills into `.claude/` | off |
|
|
129
167
|
| `--[no-]tauri` | Tauri desktop app — Mode 1 offline reader | off |
|
|
130
168
|
| `--[no-]tauri-dev` | Tauri dev wrapper — Mode 2 configurable dev wrapper | off |
|
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
|
-
|
|
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
|
-
//
|
|
43
|
-
|
|
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
|
-
|
|
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 [
|
|
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
|
|
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/constants.js
CHANGED
|
@@ -251,21 +251,21 @@ export const FEATURES = [
|
|
|
251
251
|
value: "sidebarResizer",
|
|
252
252
|
label: "Sidebar resizer",
|
|
253
253
|
hint: "Draggable sidebar width",
|
|
254
|
-
default:
|
|
254
|
+
default: true,
|
|
255
255
|
cliFlag: "sidebar-resizer",
|
|
256
256
|
},
|
|
257
257
|
{
|
|
258
258
|
value: "sidebarToggle",
|
|
259
259
|
label: "Sidebar toggle",
|
|
260
260
|
hint: "Show/hide desktop sidebar",
|
|
261
|
-
default:
|
|
261
|
+
default: true,
|
|
262
262
|
cliFlag: "sidebar-toggle",
|
|
263
263
|
},
|
|
264
264
|
{
|
|
265
265
|
value: "tocToggle",
|
|
266
266
|
label: "TOC toggle",
|
|
267
267
|
hint: "Show/hide the desktop table of contents",
|
|
268
|
-
default:
|
|
268
|
+
default: true,
|
|
269
269
|
cliFlag: "toc-toggle",
|
|
270
270
|
},
|
|
271
271
|
{
|
|
@@ -279,7 +279,7 @@ export const FEATURES = [
|
|
|
279
279
|
value: "docHistory",
|
|
280
280
|
label: "Document history",
|
|
281
281
|
hint: "Show document edit history",
|
|
282
|
-
default:
|
|
282
|
+
default: true,
|
|
283
283
|
cliFlag: "doc-history",
|
|
284
284
|
},
|
|
285
285
|
{
|
|
@@ -293,7 +293,7 @@ export const FEATURES = [
|
|
|
293
293
|
value: "llmsTxt",
|
|
294
294
|
label: "llms.txt",
|
|
295
295
|
hint: "Generate llms.txt for LLM consumption",
|
|
296
|
-
default:
|
|
296
|
+
default: true,
|
|
297
297
|
cliFlag: "llms-txt",
|
|
298
298
|
},
|
|
299
299
|
{
|
|
@@ -335,7 +335,7 @@ export const FEATURES = [
|
|
|
335
335
|
value: "assetViewer",
|
|
336
336
|
label: "Asset viewer",
|
|
337
337
|
hint: "Viewer pages for files under public/assets",
|
|
338
|
-
default:
|
|
338
|
+
default: true,
|
|
339
339
|
cliFlag: "asset-viewer",
|
|
340
340
|
},
|
|
341
341
|
{
|
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 {
|
|
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 =
|
|
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 ${
|
|
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(
|
|
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 ${
|
|
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
|
package/dist/scaffold.d.ts
CHANGED
|
@@ -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.
|
|
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.
|
|
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 =
|
|
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
|
|
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-
|
|
@@ -821,26 +821,20 @@ function generatePackageJson(choices, localePlan) {
|
|
|
821
821
|
// missing copy produces no `pnpm install` warning — which is why this gap
|
|
822
822
|
// shipped silently and only surfaced at build time.
|
|
823
823
|
diff: "^8.0.3",
|
|
824
|
-
// @takazudo/zdtp
|
|
825
|
-
//
|
|
826
|
-
//
|
|
827
|
-
//
|
|
828
|
-
//
|
|
829
|
-
//
|
|
830
|
-
//
|
|
831
|
-
//
|
|
832
|
-
//
|
|
833
|
-
//
|
|
834
|
-
//
|
|
835
|
-
//
|
|
836
|
-
//
|
|
837
|
-
//
|
|
838
|
-
// "Could not resolve '@takazudo/zdtp'" even on a fully barebone project.
|
|
839
|
-
// `preact ^10.29.1` (see the floor comment above) is required for the
|
|
840
|
-
// same reason. This is the ACCEPTED, permanent contract per #2668 — see
|
|
841
|
-
// the "@takazudo/zdtp dep implication" note in
|
|
842
|
-
// packages/zudo-doc/docs/adr/route-injection-seam.md.
|
|
843
|
-
"@takazudo/zdtp": "0.5.1",
|
|
824
|
+
// (@takazudo/zdtp is NOT here — it is gated on the designTokenPanel
|
|
825
|
+
// feature, see the block below. It WAS unconditional from #2660 through
|
|
826
|
+
// #4018: `chrome/derive.tsx`'s `deriveBodyEndIslands` statically imports
|
|
827
|
+
// the REAL `DesignTokenPanelBootstrap` for EVERY `createChrome` consumer
|
|
828
|
+
// (zero host wiring when the setting is on), and that component used to
|
|
829
|
+
// import `@takazudo/zdtp/constants` at module scope — so a barebone
|
|
830
|
+
// designTokenPanel:OFF project still died with "Could not resolve
|
|
831
|
+
// '@takazudo/zdtp/constants'". #4018 vendored those constants into
|
|
832
|
+
// @takazudo/zudo-doc, leaving only a rejection-handled
|
|
833
|
+
// `import("@takazudo/zdtp")` that esbuild tolerates when the package is
|
|
834
|
+
// absent, so the dep is now genuinely conditional (#4009).
|
|
835
|
+
// `preact` stays unconditional (the app needs it regardless); its
|
|
836
|
+
// ^10.29.1 floor is still chosen so a designTokenPanel-ON project shares
|
|
837
|
+
// one preact instance with zdtp — see the floor comment above.)
|
|
844
838
|
// (@takazudo/zudo-doc-history-server is NOT here — it is gated on the
|
|
845
839
|
// docHistory or assetViewer features, see the block below. It was briefly unconditional
|
|
846
840
|
// (#3080) to work around doc-history-area importing its `/exclude` subpath
|
|
@@ -868,6 +862,15 @@ function generatePackageJson(choices, localePlan) {
|
|
|
868
862
|
// search ships as @takazudo/zudo-doc's own self-contained generated
|
|
869
863
|
// search-widget script (custom word-match scorer) — no third-party search
|
|
870
864
|
// engine dependency is needed here. Do not re-add minisearch/pagefind.
|
|
865
|
+
if (choices.features.includes("designTokenPanel")) {
|
|
866
|
+
// Only the design-token-panel feature graph reaches @takazudo/zdtp: the
|
|
867
|
+
// panel payload loads through a rejection-handled `import("@takazudo/zdtp")`
|
|
868
|
+
// at first toggle, and this feature's global.css injection is what pulls in
|
|
869
|
+
// `@takazudo/zdtp/styles.css` (see features/design-token-panel.ts). Both are
|
|
870
|
+
// no-ops with the feature off, so an OFF project must not carry the dep
|
|
871
|
+
// (#4009 / #4018 — it was unconditional until then, see the `deps` block).
|
|
872
|
+
deps["@takazudo/zdtp"] = "0.5.1";
|
|
873
|
+
}
|
|
871
874
|
if (choices.features.includes("docHistory") ||
|
|
872
875
|
choices.features.includes("assetViewer")) {
|
|
873
876
|
// (`diff` remains an unconditional base dep — see the `deps` block above:
|
|
@@ -889,7 +892,7 @@ function generatePackageJson(choices, localePlan) {
|
|
|
889
892
|
// `/exclude` at module scope from the always-bundled chrome graph; #3110
|
|
890
893
|
// moved compileExclude into @takazudo/zudo-doc, so projects with both
|
|
891
894
|
// docHistory and assetViewer off no longer need the package at all.
|
|
892
|
-
deps["@takazudo/zudo-doc-history-server"] = "^5.
|
|
895
|
+
deps["@takazudo/zudo-doc-history-server"] = "^5.19.0";
|
|
893
896
|
// tsx is no longer needed here: the relocated package plugin imports the
|
|
894
897
|
// runner directly (no `tsx -e` spawn) since the package ships compiled
|
|
895
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
|
|
7
|
-
//
|
|
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",
|