create-zudo-doc 5.14.0 → 5.16.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 +18 -0
- package/README.md +40 -2
- package/dist/api.d.ts +3 -0
- package/dist/api.js +12 -1
- package/dist/claude-md-gen.d.ts +2 -1
- package/dist/claude-md-gen.js +37 -14
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js +27 -0
- package/dist/compose.d.ts +5 -4
- package/dist/compose.js +6 -6
- package/dist/constants.js +1 -1
- package/dist/features/claude-resources.d.ts +3 -2
- package/dist/features/claude-resources.js +3 -2
- package/dist/features/codex-resources.d.ts +3 -2
- package/dist/features/codex-resources.js +3 -2
- package/dist/features/i18n.d.ts +2 -2
- package/dist/features/i18n.js +2 -2
- package/dist/features/tag-governance.js +18 -9
- package/dist/index.js +2 -1
- package/dist/locale-plan.d.ts +20 -0
- package/dist/locale-plan.js +58 -0
- package/dist/preset.d.ts +1 -0
- package/dist/preset.js +20 -0
- package/dist/prompts.d.ts +2 -0
- package/dist/prompts.js +14 -1
- package/dist/scaffold.d.ts +2 -3
- package/dist/scaffold.js +76 -51
- package/dist/utils.d.ts +7 -1
- package/dist/utils.js +9 -2
- package/dist/zfb-config-gen.d.ts +2 -1
- package/dist/zfb-config-gen.js +19 -17
- package/package.json +1 -1
- package/templates/base/scripts/setup-doc-skill.sh +359 -21
- package/templates/features/claudeSkills/files/.claude/skills/zudo-doc-translate/SKILL.md +76 -53
- package/templates/features/claudeSkills/files/.claude/skills/zudo-doc-version-bump/SKILL.md +60 -36
- package/templates/features/claudeSkillsWriting/files/.claude/skills/zudo-doc-writing/SKILL.md +13 -3
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,24 @@ 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.16.0] - 2026-09-02
|
|
8
|
+
|
|
9
|
+
- No package-specific changes.
|
|
10
|
+
|
|
11
|
+
## [5.15.0] - 2026-09-01
|
|
12
|
+
|
|
13
|
+
### Features
|
|
14
|
+
|
|
15
|
+
- Added one ordered locale-plan contract across the API, CLI, presets, prompts, scaffold output, and generated guidance, allowing new projects to select and emit any supported set of locales. (`10e34ca0`, `5a990248`, `c9543772`)
|
|
16
|
+
|
|
17
|
+
### Bug Fixes
|
|
18
|
+
|
|
19
|
+
- Newly generated multi-locale projects now localize generated Claude/Codex resource routes by default instead of keeping them limited to the default locale. (`26ecad11`)
|
|
20
|
+
|
|
21
|
+
### Other Changes
|
|
22
|
+
|
|
23
|
+
- Updated newly generated projects to use the zfb 2.14.2 package family. (`1c1f8f26`)
|
|
24
|
+
|
|
7
25
|
## [5.14.0] - 2026-08-31
|
|
8
26
|
|
|
9
27
|
### Features
|
package/README.md
CHANGED
|
@@ -35,13 +35,49 @@ pnpm create zudo-doc my-docs --yes
|
|
|
35
35
|
# Fully specified, non-interactive
|
|
36
36
|
pnpm create zudo-doc my-docs \
|
|
37
37
|
--lang ja \
|
|
38
|
+
--additional-langs en,de \
|
|
38
39
|
--scheme "Default Dark" \
|
|
39
|
-
--no-i18n \
|
|
40
40
|
--search \
|
|
41
41
|
--pm pnpm \
|
|
42
42
|
--install
|
|
43
43
|
```
|
|
44
44
|
|
|
45
|
+
### Locales and translations
|
|
46
|
+
|
|
47
|
+
`--lang` selects the primary locale. Its pages use the unprefixed
|
|
48
|
+
`/docs/...` routes and `src/content/docs/`. Add any number of additional
|
|
49
|
+
locales, in the order shown by the language switcher, with
|
|
50
|
+
`--additional-langs <code,...>`:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
pnpm create zudo-doc my-docs \
|
|
54
|
+
--lang en \
|
|
55
|
+
--additional-langs ja,de \
|
|
56
|
+
--yes
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
An omitted or blank list creates a single-locale project. The same rule
|
|
60
|
+
applies to a preset that omits `additionalLangs`; an explicit non-empty list
|
|
61
|
+
is normalized to lowercase, validates each code for safe URL/path use, rejects
|
|
62
|
+
duplicates and the primary code, and creates `src/content/docs-<code>/` plus
|
|
63
|
+
the corresponding `/<code>/docs/...` routes. A legacy preset containing only
|
|
64
|
+
`i18n: true` keeps compatibility inference (`ja` for primary `en`, otherwise
|
|
65
|
+
`en`).
|
|
66
|
+
|
|
67
|
+
CLI locale flags replace the preset list rather than merging with it. An
|
|
68
|
+
explicit `--additional-langs` also enables i18n; if it overrides
|
|
69
|
+
`--no-i18n`, the CLI prints a warning so the precedence is visible.
|
|
70
|
+
|
|
71
|
+
The generated starter uses Japanese prose for `ja` and English placeholder
|
|
72
|
+
prose for every other additional locale. Translate those pages before
|
|
73
|
+
publishing. Labels are configuration-driven: the switcher uses each locale's
|
|
74
|
+
configured `label` and map order, so custom codes and labels are not tied to
|
|
75
|
+
hard-coded `JP` or `JA` links.
|
|
76
|
+
|
|
77
|
+
Built-in UI translations resolve in this order:
|
|
78
|
+
|
|
79
|
+
`requested locale → configured default locale → package English → raw UI-string key`
|
|
80
|
+
|
|
45
81
|
## Options
|
|
46
82
|
|
|
47
83
|
### Project basics
|
|
@@ -50,6 +86,7 @@ pnpm create zudo-doc my-docs \
|
|
|
50
86
|
|------|-------------|---------|
|
|
51
87
|
| `[project-name]` | Project name (positional arg or `--name`) | prompted |
|
|
52
88
|
| `--lang <code>` | Default language: `en`, `ja`, `zh-cn`, `zh-tw`, `ko`, `es`, `fr`, `de`, `pt` | `en` |
|
|
89
|
+
| `--additional-langs <a,b>` | Ordered additional locale codes; implies i18n and replaces a preset list | none |
|
|
53
90
|
| `--pm <manager>` | Package manager: `pnpm`, `npm`, `yarn`, `bun` | detected |
|
|
54
91
|
| `--[no-]install` | Install dependencies after scaffolding | prompted |
|
|
55
92
|
| `-y, --yes` | Use defaults for all unspecified options, skip prompts | — |
|
|
@@ -72,7 +109,7 @@ Each feature has a `--[no-]<flag>` form. Passing `--feature` enables it; `--no-f
|
|
|
72
109
|
|
|
73
110
|
| Flag | Description | Default |
|
|
74
111
|
|------|-------------|---------|
|
|
75
|
-
| `--[no-]i18n` |
|
|
112
|
+
| `--[no-]i18n` | Legacy multi-language toggle; with no explicit list, infers one additional locale | off |
|
|
76
113
|
| `--[no-]search` | Full-text search | on |
|
|
77
114
|
| `--[no-]sidebar-filter` | Real-time sidebar filter | on |
|
|
78
115
|
| `--[no-]image-enlarge` | Click-to-enlarge for oversized images | on |
|
|
@@ -140,6 +177,7 @@ import { createZudoDoc } from "create-zudo-doc";
|
|
|
140
177
|
await createZudoDoc({
|
|
141
178
|
projectName: "my-docs",
|
|
142
179
|
defaultLang: "en",
|
|
180
|
+
additionalLangs: ["ja", "de"],
|
|
143
181
|
colorSchemeMode: "single",
|
|
144
182
|
singleScheme: "Default Dark",
|
|
145
183
|
features: ["search", "sidebarFilter", "tagGovernance"],
|
package/dist/api.d.ts
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import type { PresetHeaderRightItem, PresetMetaTagsConfig } from "./preset.js";
|
|
2
|
+
export { resolveLocalePlan, type LocalePlan, type LocalePlanInput, } from "./locale-plan.js";
|
|
2
3
|
export type { UserChoices } from "./prompts.js";
|
|
3
4
|
export interface CreateOptions {
|
|
4
5
|
projectName: string;
|
|
5
6
|
/** Default language code (default: "en") */
|
|
6
7
|
defaultLang?: string;
|
|
8
|
+
/** Ordered additional locale codes. A non-empty list implies i18n. */
|
|
9
|
+
additionalLangs?: string[];
|
|
7
10
|
colorSchemeMode: "single" | "light-dark";
|
|
8
11
|
singleScheme?: string;
|
|
9
12
|
lightScheme?: string;
|
package/dist/api.js
CHANGED
|
@@ -3,6 +3,8 @@ import { SINGLE_SCHEMES, THEME_PACKS } from "./constants.js";
|
|
|
3
3
|
import { parseChangelogPackages, validateChangelogPackages, validateHeaderRightItems, validateMetaTags, } from "./preset.js";
|
|
4
4
|
import { scaffold } from "./scaffold.js";
|
|
5
5
|
import { initGitRepo, installDependencies, validateProjectName } from "./utils.js";
|
|
6
|
+
import { resolveLocalePlan } from "./locale-plan.js";
|
|
7
|
+
export { resolveLocalePlan, } from "./locale-plan.js";
|
|
6
8
|
export async function createZudoDoc(options) {
|
|
7
9
|
const { install = false, git = false, ...rest } = options;
|
|
8
10
|
const nameError = validateProjectName(rest.projectName);
|
|
@@ -52,9 +54,18 @@ export async function createZudoDoc(options) {
|
|
|
52
54
|
throw new Error(err);
|
|
53
55
|
changelogPackages = parseChangelogPackages(changelogPackages);
|
|
54
56
|
}
|
|
57
|
+
const localePlan = resolveLocalePlan({
|
|
58
|
+
defaultLang: rest.defaultLang ?? "en",
|
|
59
|
+
additionalLangs: rest.additionalLangs,
|
|
60
|
+
i18n: rest.features.includes("i18n"),
|
|
61
|
+
});
|
|
55
62
|
const choices = {
|
|
56
63
|
...rest,
|
|
57
|
-
defaultLang:
|
|
64
|
+
defaultLang: localePlan.defaultLang,
|
|
65
|
+
additionalLangs: rest.additionalLangs === undefined ? undefined : localePlan.additionalLangs,
|
|
66
|
+
features: localePlan.i18n
|
|
67
|
+
? [...new Set([...rest.features, "i18n"])]
|
|
68
|
+
: rest.features.filter((feature) => feature !== "i18n"),
|
|
58
69
|
changelogPackages,
|
|
59
70
|
};
|
|
60
71
|
await scaffold(choices);
|
package/dist/claude-md-gen.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { UserChoices } from "./prompts.js";
|
|
2
|
+
import type { LocalePlan } from "./locale-plan.js";
|
|
2
3
|
/**
|
|
3
4
|
* Generate the per-project `CLAUDE.md` (minimal-scaffold shape, epic
|
|
4
5
|
* zudolab/zudo-doc#2651, Wave 6 #2660). Rewritten from scratch — the old
|
|
@@ -7,4 +8,4 @@ import type { UserChoices } from "./prompts.js";
|
|
|
7
8
|
* longer exists. The scaffolded project is now ~13 files; almost
|
|
8
9
|
* everything referenced here lives in `node_modules/@takazudo/zudo-doc`.
|
|
9
10
|
*/
|
|
10
|
-
export declare function generateCLAUDEFile(choices: UserChoices): string;
|
|
11
|
+
export declare function generateCLAUDEFile(choices: UserChoices, localePlan?: LocalePlan): string;
|
package/dist/claude-md-gen.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { resolveLocalePlan } from "./locale-plan.js";
|
|
2
|
+
import { capitalize, getLangLabel, pmRunCommand } from "./utils.js";
|
|
2
3
|
/**
|
|
3
4
|
* Generate the per-project `CLAUDE.md` (minimal-scaffold shape, epic
|
|
4
5
|
* zudolab/zudo-doc#2651, Wave 6 #2660). Rewritten from scratch — the old
|
|
@@ -7,9 +8,30 @@ import { capitalize, pmRunCommand } from "./utils.js";
|
|
|
7
8
|
* longer exists. The scaffolded project is now ~13 files; almost
|
|
8
9
|
* everything referenced here lives in `node_modules/@takazudo/zudo-doc`.
|
|
9
10
|
*/
|
|
10
|
-
export function generateCLAUDEFile(choices
|
|
11
|
+
export function generateCLAUDEFile(choices, localePlan = resolveLocalePlan({
|
|
12
|
+
defaultLang: choices.defaultLang,
|
|
13
|
+
additionalLangs: choices.additionalLangs,
|
|
14
|
+
i18n: choices.features.includes("i18n"),
|
|
15
|
+
i18nExplicitlyDisabled: choices.explicitlyDisabledFeatures?.includes("i18n"),
|
|
16
|
+
})) {
|
|
11
17
|
const siteName = capitalize(choices.projectName.replace(/-/g, " "));
|
|
12
18
|
const lines = [];
|
|
19
|
+
const localeDisplayName = (locale) => {
|
|
20
|
+
if (locale === "en")
|
|
21
|
+
return "English";
|
|
22
|
+
if (locale === "ja")
|
|
23
|
+
return "Japanese";
|
|
24
|
+
return getLangLabel(locale);
|
|
25
|
+
};
|
|
26
|
+
const localeStarterNote = (locale) => {
|
|
27
|
+
if (locale === "ja")
|
|
28
|
+
return "Japanese starter prose";
|
|
29
|
+
if (locale === "en")
|
|
30
|
+
return "English starter prose";
|
|
31
|
+
return "English placeholder prose pending translation";
|
|
32
|
+
};
|
|
33
|
+
const defaultLocaleLabel = localeDisplayName(localePlan.defaultLang);
|
|
34
|
+
const additionalLocales = localePlan.i18n ? localePlan.additionalLangs : [];
|
|
13
35
|
lines.push(`# ${siteName}`);
|
|
14
36
|
lines.push(``);
|
|
15
37
|
lines.push(`Documentation site built with [zudo-doc](https://github.com/zudolab/zudo-doc) — a zfb-based documentation framework with MDX, Tailwind CSS v4, and Preact islands. This project is intentionally minimal: one config file (\`zfb.config.ts\`) plus markdown content — layout, chrome, and islands all ship from \`@takazudo/zudo-doc\` in \`node_modules\`.`);
|
|
@@ -49,16 +71,17 @@ export function generateCLAUDEFile(choices) {
|
|
|
49
71
|
lines.push(`pages/`);
|
|
50
72
|
lines.push(`├── index.tsx # 1-line re-export of the package home route`);
|
|
51
73
|
lines.push(`└── docs/[[...slug]].tsx # self-contained doc-route stub (required for \`${pm} dev\`)`);
|
|
52
|
-
if (
|
|
74
|
+
if (localePlan.i18n) {
|
|
53
75
|
lines.push(` [locale]/docs/[[...slug]].tsx # same, for non-default locales`);
|
|
54
76
|
}
|
|
55
77
|
lines.push(`src/`);
|
|
56
78
|
lines.push(`├── chrome-bindings.tsx # optional typed primary chrome / named header / MDX bindings`);
|
|
57
79
|
lines.push(`├── content/`);
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
80
|
+
const defaultBranch = additionalLocales.length === 0 ? "└──" : "├──";
|
|
81
|
+
lines.push(`│ ${defaultBranch} docs/ # ${defaultLocaleLabel} (default) MDX content (routes at /docs/; ${localeStarterNote(localePlan.defaultLang)})`);
|
|
82
|
+
for (const [index, locale] of additionalLocales.entries()) {
|
|
83
|
+
const branch = index === additionalLocales.length - 1 ? "└──" : "├──";
|
|
84
|
+
lines.push(`│ ${branch} docs-${locale}/ # ${localeDisplayName(locale)} MDX content (routes at /${locale}/docs/; ${localeStarterNote(locale)})`);
|
|
62
85
|
}
|
|
63
86
|
lines.push(`└── styles/`);
|
|
64
87
|
lines.push(` └── global.css # @import chain + a token-override slot — that's it`);
|
|
@@ -105,15 +128,15 @@ export function generateCLAUDEFile(choices) {
|
|
|
105
128
|
lines.push(`Admonitions (above), tabbed content (\`<Tabs>\` / \`<TabItem>\`, \`<CodeGroup>\`), and block math (\`<MathBlock>\`) work the same way — no import. Full reference: https://zudo-doc.takazudomodular.com/docs/components/`);
|
|
106
129
|
lines.push(``);
|
|
107
130
|
// i18n section
|
|
108
|
-
if (
|
|
109
|
-
const secondaryLang = choices.defaultLang === "ja" ? "en" : "ja";
|
|
110
|
-
const defaultLabel = choices.defaultLang === "ja" ? "Japanese" : "English";
|
|
111
|
-
const secondaryLabel = secondaryLang === "ja" ? "Japanese" : "English";
|
|
131
|
+
if (localePlan.i18n) {
|
|
112
132
|
lines.push(`## i18n`);
|
|
113
133
|
lines.push(``);
|
|
114
|
-
lines.push(`- ${
|
|
115
|
-
|
|
116
|
-
|
|
134
|
+
lines.push(`- ${defaultLocaleLabel} (default, \`${localePlan.defaultLang}\`): \`/docs/...\` — content in \`src/content/docs/\` (${localeStarterNote(localePlan.defaultLang)})`);
|
|
135
|
+
for (const locale of additionalLocales) {
|
|
136
|
+
lines.push(`- ${localeDisplayName(locale)} (\`${locale}\`): \`/${locale}/docs/...\` — content in \`src/content/docs-${locale}/\` (${localeStarterNote(locale)})`);
|
|
137
|
+
}
|
|
138
|
+
lines.push(`- Every additional-locale directory should mirror the default directory structure`);
|
|
139
|
+
lines.push(`- The \`ja\` locale, when configured, receives Japanese starter prose and uses Japanese translation conventions. Other non-EN locale directories currently receive English placeholder prose pending translation; do not assume they are already translated.`);
|
|
117
140
|
lines.push(`- Both \`pages/docs/[[...slug]].tsx\` and \`pages/[locale]/docs/[[...slug]].tsx\` are self-contained doc-route stubs shipped by the generator as explicit host-owned seams. zfb 2.13.1 also renders package-injected dynamic routes in dev; keep these files so the generated project retains route ownership and customization.`);
|
|
118
141
|
lines.push(``);
|
|
119
142
|
}
|
package/dist/cli.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export interface CliArgs {
|
|
2
2
|
name?: string;
|
|
3
3
|
lang?: string;
|
|
4
|
+
additionalLangs?: string[];
|
|
4
5
|
colorSchemeMode?: "single" | "light-dark";
|
|
5
6
|
scheme?: string;
|
|
6
7
|
lightScheme?: string;
|
|
@@ -43,6 +44,8 @@ export interface CliArgs {
|
|
|
43
44
|
yes?: boolean;
|
|
44
45
|
help?: boolean;
|
|
45
46
|
}
|
|
47
|
+
/** Presets are the base layer; an explicitly supplied CLI list replaces it. */
|
|
48
|
+
export declare function layerAdditionalLangs(presetValue: string[] | undefined, cliValue: string[] | undefined): string[] | undefined;
|
|
46
49
|
export declare function parseArgs(argv?: string[]): CliArgs;
|
|
47
50
|
export declare function printHelp(): void;
|
|
48
51
|
export declare function validateArgs(args: CliArgs): string | null;
|
package/dist/cli.js
CHANGED
|
@@ -3,11 +3,17 @@ 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
5
|
import { validateProjectName } from "./utils.js";
|
|
6
|
+
import { resolveLocalePlan } from "./locale-plan.js";
|
|
7
|
+
/** Presets are the base layer; an explicitly supplied CLI list replaces it. */
|
|
8
|
+
export function layerAdditionalLangs(presetValue, cliValue) {
|
|
9
|
+
return cliValue === undefined ? presetValue : cliValue;
|
|
10
|
+
}
|
|
6
11
|
export function parseArgs(argv = process.argv.slice(2)) {
|
|
7
12
|
const raw = minimist(argv, {
|
|
8
13
|
string: [
|
|
9
14
|
"name",
|
|
10
15
|
"lang",
|
|
16
|
+
"additional-langs",
|
|
11
17
|
"color-scheme-mode",
|
|
12
18
|
"scheme",
|
|
13
19
|
"light-scheme",
|
|
@@ -42,6 +48,10 @@ export function parseArgs(argv = process.argv.slice(2)) {
|
|
|
42
48
|
}
|
|
43
49
|
if (raw.lang)
|
|
44
50
|
args.lang = raw.lang;
|
|
51
|
+
if (raw["additional-langs"] !== undefined) {
|
|
52
|
+
const value = raw["additional-langs"];
|
|
53
|
+
args.additionalLangs = (typeof value === "string" ? value : String(value)).split(",");
|
|
54
|
+
}
|
|
45
55
|
if (raw["color-scheme-mode"])
|
|
46
56
|
args.colorSchemeMode = raw["color-scheme-mode"];
|
|
47
57
|
if (raw.scheme)
|
|
@@ -96,6 +106,7 @@ ${pc.bold("Options:")}
|
|
|
96
106
|
--name <name> Project name (or first positional arg)
|
|
97
107
|
--lang <code> Default language (${langList})
|
|
98
108
|
Default: en
|
|
109
|
+
--additional-langs <a,b> Additional locale codes (ordered; implies i18n)
|
|
99
110
|
--color-scheme-mode <mode> single | light-dark
|
|
100
111
|
--scheme <name> Color scheme (single mode)
|
|
101
112
|
--light-scheme <name> Light scheme (light-dark mode)
|
|
@@ -137,6 +148,22 @@ export function validateArgs(args) {
|
|
|
137
148
|
return `Invalid language "${args.lang}". Supported: ${validLangs.join(", ")}`;
|
|
138
149
|
}
|
|
139
150
|
}
|
|
151
|
+
// A preset may supply the primary locale, so defer cross-field validation
|
|
152
|
+
// until the layered choices reach runPrompts() when --lang is omitted.
|
|
153
|
+
if (args.additionalLangs !== undefined &&
|
|
154
|
+
(args.preset === undefined || args.lang !== undefined)) {
|
|
155
|
+
try {
|
|
156
|
+
resolveLocalePlan({
|
|
157
|
+
defaultLang: args.lang ?? "en",
|
|
158
|
+
additionalLangs: args.additionalLangs,
|
|
159
|
+
i18n: args.i18n ?? false,
|
|
160
|
+
i18nExplicitlyDisabled: args.i18n === false,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
catch (error) {
|
|
164
|
+
return error instanceof Error ? error.message : String(error);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
140
167
|
if (args.colorSchemeMode && !["single", "light-dark"].includes(args.colorSchemeMode)) {
|
|
141
168
|
return `Invalid color-scheme-mode "${args.colorSchemeMode}". Must be "single" or "light-dark"`;
|
|
142
169
|
}
|
package/dist/compose.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { UserChoices } from "./prompts.js";
|
|
2
|
+
import type { LocalePlan } from "./locale-plan.js";
|
|
2
3
|
/** A single injection into a shared file at an anchor point. */
|
|
3
4
|
export interface Injection {
|
|
4
5
|
/** Target file path relative to project root */
|
|
@@ -29,13 +30,13 @@ export interface FeatureDefinition {
|
|
|
29
30
|
* Post-processing hook for complex transformations that cannot be expressed
|
|
30
31
|
* as simple file copies or anchor injections (e.g. i18n page patching).
|
|
31
32
|
*/
|
|
32
|
-
postProcess?: (targetDir: string, choices: UserChoices) => Promise<void>;
|
|
33
|
+
postProcess?: (targetDir: string, choices: UserChoices, localePlan?: LocalePlan) => Promise<void>;
|
|
33
34
|
}
|
|
34
35
|
/**
|
|
35
36
|
* A function that returns a FeatureDefinition based on user choices.
|
|
36
37
|
* This allows injections to be conditional on other choices.
|
|
37
38
|
*/
|
|
38
|
-
export type FeatureModule = (choices: UserChoices) => FeatureDefinition;
|
|
39
|
+
export type FeatureModule = (choices: UserChoices, localePlan?: LocalePlan) => FeatureDefinition;
|
|
39
40
|
/**
|
|
40
41
|
* Apply a list of injections to files in the target directory.
|
|
41
42
|
*
|
|
@@ -62,7 +63,7 @@ export declare function copyFeatureFiles(featureFilesDir: string, targetDir: str
|
|
|
62
63
|
* Resolve which features are selected based on UserChoices.
|
|
63
64
|
* Handles special cases like the footer pseudo-feature.
|
|
64
65
|
*/
|
|
65
|
-
export declare function resolveSelectedFeatures(choices: UserChoices, featureModules: Record<string, FeatureModule
|
|
66
|
+
export declare function resolveSelectedFeatures(choices: UserChoices, featureModules: Record<string, FeatureModule>, localePlan?: LocalePlan): FeatureDefinition[];
|
|
66
67
|
/**
|
|
67
68
|
* Validate that all feature dependencies are satisfied.
|
|
68
69
|
* Throws if a selected feature depends on one that isn't selected.
|
|
@@ -96,4 +97,4 @@ export declare const ANCHOR_FILES: string[];
|
|
|
96
97
|
* 5. Run post-processing hooks
|
|
97
98
|
* 6. Clean up unused anchors
|
|
98
99
|
*/
|
|
99
|
-
export declare function composeFeatures(targetDir: string, choices: UserChoices, featureModules: Record<string, FeatureModule>, featuresDir: string): Promise<void>;
|
|
100
|
+
export declare function composeFeatures(targetDir: string, choices: UserChoices, featureModules: Record<string, FeatureModule>, featuresDir: string, localePlan?: LocalePlan): Promise<void>;
|
package/dist/compose.js
CHANGED
|
@@ -120,7 +120,7 @@ export async function copyFeatureFiles(featureFilesDir, targetDir) {
|
|
|
120
120
|
* Resolve which features are selected based on UserChoices.
|
|
121
121
|
* Handles special cases like the footer pseudo-feature.
|
|
122
122
|
*/
|
|
123
|
-
export function resolveSelectedFeatures(choices, featureModules) {
|
|
123
|
+
export function resolveSelectedFeatures(choices, featureModules, localePlan) {
|
|
124
124
|
const selected = [];
|
|
125
125
|
for (const [name, moduleFn] of Object.entries(featureModules)) {
|
|
126
126
|
// Special case: footer is activated by footerNavGroup, footerCopyright,
|
|
@@ -129,12 +129,12 @@ export function resolveSelectedFeatures(choices, featureModules) {
|
|
|
129
129
|
if (choices.features.includes("footerNavGroup") ||
|
|
130
130
|
choices.features.includes("footerCopyright") ||
|
|
131
131
|
choices.features.includes("footerTaglist")) {
|
|
132
|
-
selected.push(moduleFn(choices));
|
|
132
|
+
selected.push(moduleFn(choices, localePlan));
|
|
133
133
|
}
|
|
134
134
|
continue;
|
|
135
135
|
}
|
|
136
136
|
if (choices.features.includes(name)) {
|
|
137
|
-
selected.push(moduleFn(choices));
|
|
137
|
+
selected.push(moduleFn(choices, localePlan));
|
|
138
138
|
}
|
|
139
139
|
}
|
|
140
140
|
return selected;
|
|
@@ -182,9 +182,9 @@ export const ANCHOR_FILES = [];
|
|
|
182
182
|
* 5. Run post-processing hooks
|
|
183
183
|
* 6. Clean up unused anchors
|
|
184
184
|
*/
|
|
185
|
-
export async function composeFeatures(targetDir, choices, featureModules, featuresDir) {
|
|
185
|
+
export async function composeFeatures(targetDir, choices, featureModules, featuresDir, localePlan) {
|
|
186
186
|
// 1. Resolve
|
|
187
|
-
const features = resolveSelectedFeatures(choices, featureModules);
|
|
187
|
+
const features = resolveSelectedFeatures(choices, featureModules, localePlan);
|
|
188
188
|
const selectedNames = new Set(features.map((f) => f.name));
|
|
189
189
|
// 2. Validate
|
|
190
190
|
validateDependencies(features, selectedNames);
|
|
@@ -202,7 +202,7 @@ export async function composeFeatures(targetDir, choices, featureModules, featur
|
|
|
202
202
|
// 5. Post-processing
|
|
203
203
|
for (const feature of features) {
|
|
204
204
|
if (feature.postProcess) {
|
|
205
|
-
await feature.postProcess(targetDir, choices);
|
|
205
|
+
await feature.postProcess(targetDir, choices, localePlan);
|
|
206
206
|
}
|
|
207
207
|
}
|
|
208
208
|
// 6. Clean up unused anchors
|
package/dist/constants.js
CHANGED
|
@@ -5,7 +5,8 @@ import type { FeatureModule } from "../compose.js";
|
|
|
5
5
|
* Fully plugin-owned (`@takazudo/zudo-doc/plugins/claude-resources`,
|
|
6
6
|
* `zudoDocPreset()` wires it whenever `settings.claudeResources` is
|
|
7
7
|
* truthy). Generation is package-owned. This feature's touch points are now
|
|
8
|
-
* just
|
|
9
|
-
*
|
|
8
|
+
* just the `claudeResources` field (`zfb-config-gen.ts`). Resource routes are
|
|
9
|
+
* localized by default; projects can opt selected paths out through the
|
|
10
|
+
* general-purpose `defaultLocaleOnlyPrefixes` setting.
|
|
10
11
|
*/
|
|
11
12
|
export declare const claudeResourcesFeature: FeatureModule;
|
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
* Fully plugin-owned (`@takazudo/zudo-doc/plugins/claude-resources`,
|
|
5
5
|
* `zudoDocPreset()` wires it whenever `settings.claudeResources` is
|
|
6
6
|
* truthy). Generation is package-owned. This feature's touch points are now
|
|
7
|
-
* just
|
|
8
|
-
*
|
|
7
|
+
* just the `claudeResources` field (`zfb-config-gen.ts`). Resource routes are
|
|
8
|
+
* localized by default; projects can opt selected paths out through the
|
|
9
|
+
* general-purpose `defaultLocaleOnlyPrefixes` setting.
|
|
9
10
|
*/
|
|
10
11
|
export const claudeResourcesFeature = () => ({
|
|
11
12
|
name: "claudeResources",
|
|
@@ -5,7 +5,8 @@ import type { FeatureModule } from "../compose.js";
|
|
|
5
5
|
* Fully plugin-owned (`@takazudo/zudo-doc/plugins/codex-resources`,
|
|
6
6
|
* `zudoDocPreset()` wires it whenever `settings.codexResources` is
|
|
7
7
|
* truthy). Generation is package-owned. This feature's touch points are now
|
|
8
|
-
* just
|
|
9
|
-
*
|
|
8
|
+
* just the `codexResources` field (`zfb-config-gen.ts`). Resource routes are
|
|
9
|
+
* localized by default; projects can opt selected paths out through the
|
|
10
|
+
* general-purpose `defaultLocaleOnlyPrefixes` setting.
|
|
10
11
|
*/
|
|
11
12
|
export declare const codexResourcesFeature: FeatureModule;
|
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
* Fully plugin-owned (`@takazudo/zudo-doc/plugins/codex-resources`,
|
|
5
5
|
* `zudoDocPreset()` wires it whenever `settings.codexResources` is
|
|
6
6
|
* truthy). Generation is package-owned. This feature's touch points are now
|
|
7
|
-
* just
|
|
8
|
-
*
|
|
7
|
+
* just the `codexResources` field (`zfb-config-gen.ts`). Resource routes are
|
|
8
|
+
* localized by default; projects can opt selected paths out through the
|
|
9
|
+
* general-purpose `defaultLocaleOnlyPrefixes` setting.
|
|
9
10
|
*/
|
|
10
11
|
export const codexResourcesFeature = () => ({
|
|
11
12
|
name: "codexResources",
|
package/dist/features/i18n.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { FeatureModule } from "../compose.js";
|
|
|
3
3
|
* i18n feature — gates the locale-prefixed page set.
|
|
4
4
|
*
|
|
5
5
|
* Locked manifest (#2653 Decision 4, i18n addendum): "i18n ON adds
|
|
6
|
-
* `pages/[locale]/docs/[[...slug]].tsx` (
|
|
6
|
+
* `pages/[locale]/docs/[[...slug]].tsx` (the additional-locale doc route stub)
|
|
7
7
|
* … No other pages." That ONE self-contained stub — retained as an explicit
|
|
8
8
|
* host-owned route seam; zfb 2.13.1 also serves injected dynamic routes in dev —
|
|
9
9
|
* is shipped under `templates/features/i18n/files/pages/[locale]/docs/`
|
|
@@ -15,7 +15,7 @@ import type { FeatureModule } from "../compose.js";
|
|
|
15
15
|
*
|
|
16
16
|
* No injections: the stub iterates `settings.locales` at build/request
|
|
17
17
|
* time, so no postProcess regex patching is required for non-default
|
|
18
|
-
*
|
|
18
|
+
* locales. Additional-locale content mirrors under
|
|
19
19
|
* `src/content/docs-<lang>/` are seeded by `scaffold.ts`.
|
|
20
20
|
*
|
|
21
21
|
* Loud-failure check: per spec-lock Decision 8 (#1737), abort scaffolding
|
package/dist/features/i18n.js
CHANGED
|
@@ -5,7 +5,7 @@ import { fileURLToPath } from "url";
|
|
|
5
5
|
* i18n feature — gates the locale-prefixed page set.
|
|
6
6
|
*
|
|
7
7
|
* Locked manifest (#2653 Decision 4, i18n addendum): "i18n ON adds
|
|
8
|
-
* `pages/[locale]/docs/[[...slug]].tsx` (
|
|
8
|
+
* `pages/[locale]/docs/[[...slug]].tsx` (the additional-locale doc route stub)
|
|
9
9
|
* … No other pages." That ONE self-contained stub — retained as an explicit
|
|
10
10
|
* host-owned route seam; zfb 2.13.1 also serves injected dynamic routes in dev —
|
|
11
11
|
* is shipped under `templates/features/i18n/files/pages/[locale]/docs/`
|
|
@@ -17,7 +17,7 @@ import { fileURLToPath } from "url";
|
|
|
17
17
|
*
|
|
18
18
|
* No injections: the stub iterates `settings.locales` at build/request
|
|
19
19
|
* time, so no postProcess regex patching is required for non-default
|
|
20
|
-
*
|
|
20
|
+
* locales. Additional-locale content mirrors under
|
|
21
21
|
* `src/content/docs-<lang>/` are seeded by `scaffold.ts`.
|
|
22
22
|
*
|
|
23
23
|
* Loud-failure check: per spec-lock Decision 8 (#1737), abort scaffolding
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import fs from "fs-extra";
|
|
2
2
|
import path from "path";
|
|
3
|
-
import {
|
|
3
|
+
import { resolveLocalePlan } from "../locale-plan.js";
|
|
4
4
|
/**
|
|
5
5
|
* Tag governance feature.
|
|
6
6
|
*
|
|
@@ -8,18 +8,27 @@ import { getSecondaryLang } from "../utils.js";
|
|
|
8
8
|
* feeds zfb while its default TagCliConfig export feeds both package-owned
|
|
9
9
|
* bins through an explicit `--config` package-script argument.
|
|
10
10
|
*/
|
|
11
|
-
export const tagGovernanceFeature = (choices) => ({
|
|
11
|
+
export const tagGovernanceFeature = (choices, localePlan) => ({
|
|
12
12
|
name: "tagGovernance",
|
|
13
13
|
injections: [],
|
|
14
14
|
postProcess: async (targetDir) => {
|
|
15
15
|
const vocabPath = path.join(targetDir, "src/config/tag-vocabulary.ts");
|
|
16
16
|
if (!(await fs.pathExists(vocabPath))) {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
17
|
+
// `scaffold()` resolves the locale plan once and threads it through
|
|
18
|
+
// feature composition. Keep the fallback for direct FeatureModule
|
|
19
|
+
// callers (e.g. package consumers/tests) while ensuring normalization
|
|
20
|
+
// and legacy inference still have one canonical implementation.
|
|
21
|
+
const plan = localePlan ??
|
|
22
|
+
resolveLocalePlan({
|
|
23
|
+
defaultLang: choices.defaultLang,
|
|
24
|
+
additionalLangs: choices.additionalLangs,
|
|
25
|
+
i18n: choices.features.includes("i18n"),
|
|
26
|
+
i18nExplicitlyDisabled: choices.explicitlyDisabledFeatures?.includes("i18n"),
|
|
27
|
+
});
|
|
28
|
+
const contentDirs = [
|
|
29
|
+
"src/content/docs",
|
|
30
|
+
...plan.additionalLangs.map((locale) => `src/content/docs-${locale}`),
|
|
31
|
+
];
|
|
23
32
|
await fs.outputFile(vocabPath, `import type { TagCliConfig } from "@takazudo/zudo-doc/tags-audit";
|
|
24
33
|
import type { TagVocabularyEntry } from "@takazudo/zudo-doc/settings";
|
|
25
34
|
|
|
@@ -31,7 +40,7 @@ export const tagVocabulary: TagVocabularyEntry[] = [];
|
|
|
31
40
|
// Package scripts pass this module to the package-owned audit/suggest bins.
|
|
32
41
|
// Paths are resolved from the project root.
|
|
33
42
|
const tagCliConfig = {
|
|
34
|
-
contentDirs: ${contentDirs},
|
|
43
|
+
contentDirs: ${JSON.stringify(contentDirs, null, 2)},
|
|
35
44
|
vocabulary: tagVocabulary,
|
|
36
45
|
governance: "warn",
|
|
37
46
|
vocabularyActive: true,
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import path from "path";
|
|
2
2
|
import * as p from "@clack/prompts";
|
|
3
3
|
import pc from "picocolors";
|
|
4
|
-
import { parseArgs, printHelp, validateArgs } from "./cli.js";
|
|
4
|
+
import { layerAdditionalLangs, parseArgs, printHelp, validateArgs, } from "./cli.js";
|
|
5
5
|
import { FEATURES } from "./constants.js";
|
|
6
6
|
import { loadPreset } from "./preset.js";
|
|
7
7
|
import { runPrompts } from "./prompts.js";
|
|
@@ -40,6 +40,7 @@ async function main() {
|
|
|
40
40
|
prefilled.projectName = args.name;
|
|
41
41
|
if (args.lang)
|
|
42
42
|
prefilled.defaultLang = args.lang;
|
|
43
|
+
prefilled.additionalLangs = layerAdditionalLangs(prefilled.additionalLangs, args.additionalLangs);
|
|
43
44
|
if (args.colorSchemeMode)
|
|
44
45
|
prefilled.colorSchemeMode = args.colorSchemeMode;
|
|
45
46
|
if (args.scheme) {
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export interface LocalePlanInput {
|
|
2
|
+
defaultLang: string;
|
|
3
|
+
additionalLangs?: readonly string[];
|
|
4
|
+
i18n: boolean;
|
|
5
|
+
/** Whether the original input explicitly contained `--no-i18n`. */
|
|
6
|
+
i18nExplicitlyDisabled?: boolean;
|
|
7
|
+
}
|
|
8
|
+
export interface LocalePlan {
|
|
9
|
+
defaultLang: string;
|
|
10
|
+
additionalLangs: string[];
|
|
11
|
+
i18n: boolean;
|
|
12
|
+
/** A non-empty explicit list won over an explicit `--no-i18n`. */
|
|
13
|
+
overridesExplicitDisable: boolean;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Resolve every locale input path to one canonical, filesystem-safe plan.
|
|
17
|
+
* This function is pure and is the sole owner of locale normalization,
|
|
18
|
+
* validation, legacy inference, and explicit-list precedence.
|
|
19
|
+
*/
|
|
20
|
+
export declare function resolveLocalePlan(input: LocalePlanInput): LocalePlan;
|