create-zudo-doc 5.18.2 → 5.19.1
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 +20 -0
- package/README.md +38 -1
- package/dist/api.js +4 -3
- package/dist/cli.d.ts +9 -0
- package/dist/cli.js +26 -8
- 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 +22 -9
- 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,26 @@ 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.1] - 2026-09-08
|
|
8
|
+
|
|
9
|
+
### Other Changes
|
|
10
|
+
|
|
11
|
+
- A generated project's `package.json` now pins `@takazudo/zfb`, `@takazudo/zfb-runtime`, and `@takazudo/zfb-md-wasm` at `2.16.0`, and `@takazudo/zdtp` at `0.5.2` when the Design Token Panel feature is enabled (`47c149385`). Both upstream releases are drop-in for a fresh scaffold — zfb 2.16.0 fixes an `ETXTBSY` crash when first executing a just-written esbuild binary, zdtp 0.5.2 is additive — so no public API, export, config default, or engine requirement moves and a fresh scaffold needs no migration.
|
|
12
|
+
|
|
13
|
+
## [5.19.0] - 2026-09-07
|
|
14
|
+
|
|
15
|
+
### Features
|
|
16
|
+
|
|
17
|
+
- 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)
|
|
18
|
+
|
|
19
|
+
### Bug Fixes
|
|
20
|
+
|
|
21
|
+
- 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)
|
|
22
|
+
|
|
23
|
+
### Other Changes
|
|
24
|
+
|
|
25
|
+
- 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)
|
|
26
|
+
|
|
7
27
|
## [5.18.2] - 2026-09-06
|
|
8
28
|
|
|
9
29
|
### 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
|
-
| `[
|
|
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
|
-
|
|
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/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.1";
|
|
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.1";
|
|
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-
|
|
@@ -759,9 +759,22 @@ function generatePackageJson(choices, localePlan) {
|
|
|
759
759
|
// four shipped wasm binaries are byte-size identical to 2.15.0 (rebuild
|
|
760
760
|
// noise only), and no public API, export, config default, or engine
|
|
761
761
|
// requirement moves, so a fresh scaffold needs no migration.
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
762
|
+
// 2.16.0: one upstream bug fix, plus a documentation-only md-wasm change.
|
|
763
|
+
// zfb's esbuild version-gate spawn (`ensure_binary_verified`) now retries on
|
|
764
|
+
// `ETXTBSY` through zfb-build's existing retry loop instead of a bare
|
|
765
|
+
// `.output()`, so a build no longer dies with `Text file busy (os error 26)`
|
|
766
|
+
// on the first exec of a binary a packaged zfb has only just written to a
|
|
767
|
+
// tempdir. The fix ships in the five platform binary packages, which roll to
|
|
768
|
+
// 2.16.0 with the wrapper. zfb-runtime and zfb-adapter-cloudflare have no
|
|
769
|
+
// package-specific change; md-wasm changes only its README (a digest
|
|
770
|
+
// disclaimer beside the shipped-size table), keeps every artifact at its
|
|
771
|
+
// 2.15.1 byte size, and moves all four wasm SHA-256 digests as every release
|
|
772
|
+
// does — pin by semver, as here, and there is nothing to re-pin. No public
|
|
773
|
+
// API, export, config default, or engine requirement moves, so a fresh
|
|
774
|
+
// scaffold needs no migration.
|
|
775
|
+
"@takazudo/zfb": "2.16.0",
|
|
776
|
+
"@takazudo/zfb-runtime": "2.16.0",
|
|
777
|
+
"@takazudo/zfb-md-wasm": "2.16.0",
|
|
765
778
|
// @takazudo/zudo-doc — published from this monorepo via
|
|
766
779
|
// .github/workflows/publish-zudo-doc.yml. The pin here is bumped in
|
|
767
780
|
// lockstep by scripts/release-create-zudo-doc.sh whenever zudo-doc's
|
|
@@ -869,7 +882,7 @@ function generatePackageJson(choices, localePlan) {
|
|
|
869
882
|
// `@takazudo/zdtp/styles.css` (see features/design-token-panel.ts). Both are
|
|
870
883
|
// no-ops with the feature off, so an OFF project must not carry the dep
|
|
871
884
|
// (#4009 / #4018 — it was unconditional until then, see the `deps` block).
|
|
872
|
-
deps["@takazudo/zdtp"] = "0.5.
|
|
885
|
+
deps["@takazudo/zdtp"] = "0.5.2";
|
|
873
886
|
}
|
|
874
887
|
if (choices.features.includes("docHistory") ||
|
|
875
888
|
choices.features.includes("assetViewer")) {
|
|
@@ -892,7 +905,7 @@ function generatePackageJson(choices, localePlan) {
|
|
|
892
905
|
// `/exclude` at module scope from the always-bundled chrome graph; #3110
|
|
893
906
|
// moved compileExclude into @takazudo/zudo-doc, so projects with both
|
|
894
907
|
// docHistory and assetViewer off no longer need the package at all.
|
|
895
|
-
deps["@takazudo/zudo-doc-history-server"] = "^5.
|
|
908
|
+
deps["@takazudo/zudo-doc-history-server"] = "^5.19.1";
|
|
896
909
|
// tsx is no longer needed here: the relocated package plugin imports the
|
|
897
910
|
// runner directly (no `tsx -e` spawn) since the package ships compiled
|
|
898
911
|
// 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",
|