create-zudo-doc 0.1.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.
Files changed (212) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +146 -0
  3. package/bin/create-zudo-doc.js +2 -0
  4. package/dist/api.d.ts +20 -0
  5. package/dist/api.js +13 -0
  6. package/dist/claude-md-gen.d.ts +2 -0
  7. package/dist/claude-md-gen.js +113 -0
  8. package/dist/cli.d.ts +39 -0
  9. package/dist/cli.js +157 -0
  10. package/dist/compose.d.ts +95 -0
  11. package/dist/compose.js +206 -0
  12. package/dist/constants.d.ts +20 -0
  13. package/dist/constants.js +224 -0
  14. package/dist/features/body-foot-util.d.ts +10 -0
  15. package/dist/features/body-foot-util.js +12 -0
  16. package/dist/features/claude-resources.d.ts +2 -0
  17. package/dist/features/claude-resources.js +6 -0
  18. package/dist/features/design-token-panel.d.ts +14 -0
  19. package/dist/features/design-token-panel.js +27 -0
  20. package/dist/features/doc-history.d.ts +9 -0
  21. package/dist/features/doc-history.js +11 -0
  22. package/dist/features/doc-tags.d.ts +19 -0
  23. package/dist/features/doc-tags.js +33 -0
  24. package/dist/features/footer-taglist.d.ts +14 -0
  25. package/dist/features/footer-taglist.js +17 -0
  26. package/dist/features/footer.d.ts +8 -0
  27. package/dist/features/footer.js +10 -0
  28. package/dist/features/i18n.d.ts +22 -0
  29. package/dist/features/i18n.js +41 -0
  30. package/dist/features/image-enlarge.d.ts +11 -0
  31. package/dist/features/image-enlarge.js +13 -0
  32. package/dist/features/index.d.ts +15 -0
  33. package/dist/features/index.js +53 -0
  34. package/dist/features/llms-txt.d.ts +11 -0
  35. package/dist/features/llms-txt.js +13 -0
  36. package/dist/features/search.d.ts +9 -0
  37. package/dist/features/search.js +11 -0
  38. package/dist/features/sidebar-resizer.d.ts +14 -0
  39. package/dist/features/sidebar-resizer.js +16 -0
  40. package/dist/features/sidebar-toggle.d.ts +13 -0
  41. package/dist/features/sidebar-toggle.js +15 -0
  42. package/dist/features/tag-governance.d.ts +14 -0
  43. package/dist/features/tag-governance.js +16 -0
  44. package/dist/features/tauri-dev.d.ts +2 -0
  45. package/dist/features/tauri-dev.js +25 -0
  46. package/dist/features/tauri.d.ts +11 -0
  47. package/dist/features/tauri.js +52 -0
  48. package/dist/features/versioning.d.ts +27 -0
  49. package/dist/features/versioning.js +43 -0
  50. package/dist/index.d.ts +1 -0
  51. package/dist/index.js +150 -0
  52. package/dist/preset.d.ts +37 -0
  53. package/dist/preset.js +156 -0
  54. package/dist/prompts.d.ts +32 -0
  55. package/dist/prompts.js +248 -0
  56. package/dist/scaffold.d.ts +4 -0
  57. package/dist/scaffold.js +344 -0
  58. package/dist/settings-gen.d.ts +2 -0
  59. package/dist/settings-gen.js +237 -0
  60. package/dist/utils.d.ts +8 -0
  61. package/dist/utils.js +34 -0
  62. package/dist/zfb-config-gen.d.ts +19 -0
  63. package/dist/zfb-config-gen.js +222 -0
  64. package/package.json +65 -0
  65. package/templates/base/.htmlvalidate.json +5 -0
  66. package/templates/base/.zfb/doc-history-meta.json +1 -0
  67. package/templates/base/pages/404.tsx +55 -0
  68. package/templates/base/pages/_data.ts +179 -0
  69. package/templates/base/pages/_mdx-components.ts +249 -0
  70. package/templates/base/pages/docs/[...slug].tsx +448 -0
  71. package/templates/base/pages/index.tsx +158 -0
  72. package/templates/base/pages/lib/_body-end-islands.tsx +201 -0
  73. package/templates/base/pages/lib/_category-nav.tsx +148 -0
  74. package/templates/base/pages/lib/_category-tree-nav.tsx +104 -0
  75. package/templates/base/pages/lib/_compose-meta-title.ts +29 -0
  76. package/templates/base/pages/lib/_details.tsx +30 -0
  77. package/templates/base/pages/lib/_doc-history-area.tsx +178 -0
  78. package/templates/base/pages/lib/_doc-metainfo-area.tsx +100 -0
  79. package/templates/base/pages/lib/_doc-tags-area.tsx +89 -0
  80. package/templates/base/pages/lib/_extract-headings.ts +81 -0
  81. package/templates/base/pages/lib/_footer-with-defaults.tsx +234 -0
  82. package/templates/base/pages/lib/_frontmatter-preview-data.ts +53 -0
  83. package/templates/base/pages/lib/_head-with-defaults.tsx +113 -0
  84. package/templates/base/pages/lib/_header-with-defaults.tsx +386 -0
  85. package/templates/base/pages/lib/_inline-version-switcher.tsx +84 -0
  86. package/templates/base/pages/lib/_math-block.tsx +63 -0
  87. package/templates/base/pages/lib/_nav-source-docs.ts +68 -0
  88. package/templates/base/pages/lib/_preset-generator.tsx +81 -0
  89. package/templates/base/pages/lib/_search-widget-script.ts +388 -0
  90. package/templates/base/pages/lib/_search-widget.tsx +196 -0
  91. package/templates/base/pages/lib/_sidebar-with-defaults.tsx +176 -0
  92. package/templates/base/pages/lib/_site-tree-nav.tsx +128 -0
  93. package/templates/base/pages/lib/locale-merge.ts +58 -0
  94. package/templates/base/pages/lib/route-enumerators.ts +302 -0
  95. package/templates/base/pages/sitemap.xml.tsx +51 -0
  96. package/templates/base/plugins/connect-adapter.mjs +144 -0
  97. package/templates/base/plugins/copy-public-plugin.mjs +50 -0
  98. package/templates/base/plugins/search-index-plugin.mjs +54 -0
  99. package/templates/base/scripts/run-b4push.sh +102 -0
  100. package/templates/base/src/components/ai-chat-modal.tsx +15 -0
  101. package/templates/base/src/components/client-router-bootstrap.tsx +14 -0
  102. package/templates/base/src/components/content/component-map.ts +25 -0
  103. package/templates/base/src/components/content/content-blockquote.tsx +16 -0
  104. package/templates/base/src/components/content/content-code.tsx +117 -0
  105. package/templates/base/src/components/content/content-link.tsx +83 -0
  106. package/templates/base/src/components/content/content-ol.tsx +19 -0
  107. package/templates/base/src/components/content/content-paragraph.tsx +10 -0
  108. package/templates/base/src/components/content/content-strong.tsx +16 -0
  109. package/templates/base/src/components/content/content-table.tsx +18 -0
  110. package/templates/base/src/components/content/content-ul.tsx +18 -0
  111. package/templates/base/src/components/content/heading-h2.tsx +26 -0
  112. package/templates/base/src/components/content/heading-h3.tsx +26 -0
  113. package/templates/base/src/components/content/heading-h4.tsx +26 -0
  114. package/templates/base/src/components/design-token-panel-bootstrap.tsx +15 -0
  115. package/templates/base/src/components/desktop-sidebar-toggle.tsx +15 -0
  116. package/templates/base/src/components/doc-history.tsx +18 -0
  117. package/templates/base/src/components/html-preview/highlighted-code.tsx +74 -0
  118. package/templates/base/src/components/html-preview/html-preview.tsx +108 -0
  119. package/templates/base/src/components/html-preview/preflight.ts +112 -0
  120. package/templates/base/src/components/html-preview/preview-base.tsx +159 -0
  121. package/templates/base/src/components/image-enlarge.tsx +19 -0
  122. package/templates/base/src/components/mobile-toc.tsx +94 -0
  123. package/templates/base/src/components/preset-generator.tsx +14 -0
  124. package/templates/base/src/components/sidebar-toggle.tsx +98 -0
  125. package/templates/base/src/components/sidebar-tree.tsx +543 -0
  126. package/templates/base/src/components/site-tree-nav.tsx +233 -0
  127. package/templates/base/src/components/theme-toggle.tsx +93 -0
  128. package/templates/base/src/components/toc.tsx +63 -0
  129. package/templates/base/src/components/tree-nav-shared.tsx +71 -0
  130. package/templates/base/src/config/color-scheme-utils.ts +182 -0
  131. package/templates/base/src/config/color-schemes.ts +128 -0
  132. package/templates/base/src/config/frontmatter-preview-defaults.ts +24 -0
  133. package/templates/base/src/config/frontmatter-preview-renderers.tsx +46 -0
  134. package/templates/base/src/config/i18n.ts +225 -0
  135. package/templates/base/src/config/settings-types.ts +162 -0
  136. package/templates/base/src/config/sidebars.ts +66 -0
  137. package/templates/base/src/config/tag-vocabulary-types.ts +39 -0
  138. package/templates/base/src/config/tag-vocabulary.ts +20 -0
  139. package/templates/base/src/hooks/use-active-heading.ts +133 -0
  140. package/templates/base/src/plugins/docs-source-map.ts +103 -0
  141. package/templates/base/src/plugins/hast-utils.ts +10 -0
  142. package/templates/base/src/plugins/rehype-code-title.ts +50 -0
  143. package/templates/base/src/plugins/rehype-heading-links.ts +53 -0
  144. package/templates/base/src/plugins/rehype-image-enlarge.ts +113 -0
  145. package/templates/base/src/plugins/rehype-mermaid.ts +41 -0
  146. package/templates/base/src/plugins/rehype-strip-md-extension.ts +58 -0
  147. package/templates/base/src/plugins/remark-admonitions.ts +99 -0
  148. package/templates/base/src/plugins/remark-resolve-markdown-links.ts +127 -0
  149. package/templates/base/src/plugins/url-utils.ts +4 -0
  150. package/templates/base/src/styles/global.css +1066 -0
  151. package/templates/base/src/types/docs-entry.ts +39 -0
  152. package/templates/base/src/types/heading.ts +5 -0
  153. package/templates/base/src/types/locale.ts +10 -0
  154. package/templates/base/src/utils/base.ts +139 -0
  155. package/templates/base/src/utils/content-files.ts +106 -0
  156. package/templates/base/src/utils/dedent.ts +24 -0
  157. package/templates/base/src/utils/docs.ts +335 -0
  158. package/templates/base/src/utils/git-info.ts +70 -0
  159. package/templates/base/src/utils/github.ts +19 -0
  160. package/templates/base/src/utils/header-right-items.ts +38 -0
  161. package/templates/base/src/utils/nav-scope.ts +63 -0
  162. package/templates/base/src/utils/sidebar.ts +104 -0
  163. package/templates/base/src/utils/slug.ts +10 -0
  164. package/templates/base/src/utils/smart-break.tsx +126 -0
  165. package/templates/base/src/utils/tags.ts +126 -0
  166. package/templates/base/tsconfig.json +36 -0
  167. package/templates/features/bodyFootUtil/files/src/utils/github.ts +19 -0
  168. package/templates/features/claudeResources/files/plugins/claude-resources-plugin.mjs +137 -0
  169. package/templates/features/claudeResources/files/src/integrations/claude-resources/__tests__/escape-for-mdx.test.ts +34 -0
  170. package/templates/features/claudeResources/files/src/integrations/claude-resources/__tests__/generate.test.ts +376 -0
  171. package/templates/features/claudeResources/files/src/integrations/claude-resources/escape-for-mdx.ts +93 -0
  172. package/templates/features/claudeResources/files/src/integrations/claude-resources/generate.ts +586 -0
  173. package/templates/features/designTokenPanel/files/src/components/design-token-panel-bootstrap.tsx +15 -0
  174. package/templates/features/designTokenPanel/files/src/config/design-token-panel-config.ts +99 -0
  175. package/templates/features/designTokenPanel/files/src/config/design-tokens-manifest.ts +177 -0
  176. package/templates/features/designTokenPanel/files/src/lib/design-token-panel-bootstrap.ts +50 -0
  177. package/templates/features/docHistory/files/plugins/doc-history-plugin.mjs +99 -0
  178. package/templates/features/docHistory/files/src/components/doc-history.tsx +598 -0
  179. package/templates/features/docHistory/files/src/types/doc-history.ts +23 -0
  180. package/templates/features/docHistory/files/src/utils/doc-history.ts +180 -0
  181. package/templates/features/docTags/files/pages/[locale]/docs/tags/[tag].tsx +116 -0
  182. package/templates/features/docTags/files/pages/[locale]/docs/tags/index.tsx +99 -0
  183. package/templates/features/docTags/files/pages/docs/tags/[tag].tsx +101 -0
  184. package/templates/features/docTags/files/pages/docs/tags/index.tsx +86 -0
  185. package/templates/features/i18n/files/pages/[locale]/docs/[...slug].tsx +467 -0
  186. package/templates/features/i18n/files/pages/[locale]/index.tsx +213 -0
  187. package/templates/features/imageEnlarge/files/src/components/image-enlarge.tsx +248 -0
  188. package/templates/features/llmsTxt/files/plugins/llms-txt-plugin.mjs +74 -0
  189. package/templates/features/sidebarResizer/files/src/scripts/sidebar-resizer.ts +185 -0
  190. package/templates/features/sidebarToggle/files/src/components/desktop-sidebar-toggle.tsx +126 -0
  191. package/templates/features/tagGovernance/files/scripts/tags-audit.ts +576 -0
  192. package/templates/features/tagGovernance/files/scripts/tags-suggest.ts +428 -0
  193. package/templates/features/tauri/files/src/components/find-bar.tsx +122 -0
  194. package/templates/features/tauri/files/src/components/find-in-page-init.tsx +53 -0
  195. package/templates/features/tauri/files/src/utils/find-in-page.ts +175 -0
  196. package/templates/features/tauri/files/src-tauri/Cargo.toml +14 -0
  197. package/templates/features/tauri/files/src-tauri/build.rs +3 -0
  198. package/templates/features/tauri/files/src-tauri/capabilities/default.json +11 -0
  199. package/templates/features/tauri/files/src-tauri/src/main.rs +250 -0
  200. package/templates/features/tauri/files/src-tauri/tauri.conf.json +25 -0
  201. package/templates/features/tauriDev/files/src-tauri-dev/Cargo.toml +15 -0
  202. package/templates/features/tauriDev/files/src-tauri-dev/build.rs +3 -0
  203. package/templates/features/tauriDev/files/src-tauri-dev/capabilities/default.json +7 -0
  204. package/templates/features/tauriDev/files/src-tauri-dev/frontend/index.html +187 -0
  205. package/templates/features/tauriDev/files/src-tauri-dev/icons/icon.png +0 -0
  206. package/templates/features/tauriDev/files/src-tauri-dev/src/main.rs +995 -0
  207. package/templates/features/tauriDev/files/src-tauri-dev/tauri.conf.json +22 -0
  208. package/templates/features/tauriDev/files/src-tauri-dev/test-launch.sh +65 -0
  209. package/templates/features/versioning/files/pages/[locale]/docs/versions.tsx +100 -0
  210. package/templates/features/versioning/files/pages/docs/versions.tsx +78 -0
  211. package/templates/features/versioning/files/pages/v/[version]/docs/[...slug].tsx +451 -0
  212. package/templates/features/versioning/files/pages/v/[version]/ja/docs/[...slug].tsx +490 -0
@@ -0,0 +1,344 @@
1
+ import fs from "fs-extra";
2
+ import path from "path";
3
+ import { fileURLToPath } from "url";
4
+ import { generateSettingsFile } from "./settings-gen.js";
5
+ import { generateZfbConfig } from "./zfb-config-gen.js";
6
+ import { generateCLAUDEFile } from "./claude-md-gen.js";
7
+ import { composeFeatures } from "./compose.js";
8
+ import { featureModules } from "./features/index.js";
9
+ import { capitalize, getSecondaryLang } from "./utils.js";
10
+ export { getSecondaryLang };
11
+ /**
12
+ * Files in `templates/base/**` that must never be copied into a generated
13
+ * project. Each entry is matched against the path relative to `templates/base/`
14
+ * (POSIX-style, forward slashes).
15
+ *
16
+ * W2 spec-lock Decision 5 (#1728) — `pages/api/**` is worker-only SSR
17
+ * (uses `@takazudo/zfb-adapter-cloudflare`, `prerender = false`) and is
18
+ * intentionally absent from `templates/base/pages/` already. This list
19
+ * is the explicit policy: future upstream-sync helpers that mirror more
20
+ * of `pages/` into `templates/base/` MUST honour these patterns.
21
+ */
22
+ const EXCLUDE_FROM_MIRROR = [
23
+ /^pages\/api(\/|$)/,
24
+ ];
25
+ /**
26
+ * `fs.copy` filter for the `templates/base/` → target-dir copy. Returns
27
+ * `false` for any path matching {@link EXCLUDE_FROM_MIRROR}. Directories
28
+ * matching an exclusion are skipped wholesale (fs.copy honours filter on
29
+ * directories).
30
+ */
31
+ function shouldCopyBaseFile(srcAbs, baseDir) {
32
+ const rel = path.relative(baseDir, srcAbs).split(path.sep).join("/");
33
+ if (rel === "")
34
+ return true; // root — always include
35
+ for (const pattern of EXCLUDE_FROM_MIRROR) {
36
+ if (pattern.test(rel))
37
+ return false;
38
+ }
39
+ return true;
40
+ }
41
+ const STARTER_CONTENT_EN = (siteName) => `---
42
+ title: Welcome
43
+ sidebar_position: 1
44
+ ---
45
+
46
+ # Welcome to ${siteName}
47
+
48
+ This documentation site was created with [zudo-doc](https://github.com/zudolab/zudo-doc).
49
+
50
+ ## Getting Started
51
+
52
+ Edit the files in \`src/content/docs/\` to add your documentation.
53
+ `;
54
+ const STARTER_CONTENT_JA = () => `---
55
+ title: ようこそ
56
+ sidebar_position: 1
57
+ ---
58
+
59
+ # ようこそ
60
+
61
+ このドキュメントサイトは [zudo-doc](https://github.com/zudolab/zudo-doc) で作成されました。
62
+ `;
63
+ const CHANGELOG_CONTENT_EN = () => `---
64
+ title: Changelog
65
+ sidebar_position: 99
66
+ ---
67
+
68
+ # Changelog
69
+
70
+ ## Unreleased
71
+
72
+ - Initial release
73
+ `;
74
+ const CHANGELOG_CONTENT_JA = () => `---
75
+ title: 変更履歴
76
+ sidebar_position: 99
77
+ ---
78
+
79
+ # 変更履歴
80
+
81
+ ## 未リリース
82
+
83
+ - 初回リリース
84
+ `;
85
+ export async function scaffold(choices) {
86
+ const targetDir = path.resolve(process.cwd(), choices.projectName);
87
+ if (await fs.pathExists(targetDir)) {
88
+ const contents = await fs.readdir(targetDir);
89
+ if (contents.length > 0) {
90
+ throw new Error(`Directory "${choices.projectName}" already exists and is not empty`);
91
+ }
92
+ }
93
+ // body-foot-util-area.astro ships the DocHistory component inline (byte-
94
+ // identical to main/src/components/body-foot-util-area.astro). Selecting
95
+ // bodyFootUtil without docHistory would leave an unresolved import, so we
96
+ // silently co-enable docHistory.
97
+ if (choices.features.includes("bodyFootUtil") &&
98
+ !choices.features.includes("docHistory")) {
99
+ choices.features = [...choices.features, "docHistory"];
100
+ }
101
+ // Resolve template directories
102
+ const pkgRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
103
+ const templatesDir = path.join(pkgRoot, "templates");
104
+ const baseDir = path.join(templatesDir, "base");
105
+ const featuresDir = path.join(templatesDir, "features");
106
+ // For skillSymlinker, we still need the monorepo root for the script
107
+ const monorepoRoot = path.resolve(pkgRoot, "../..");
108
+ await fs.ensureDir(targetDir);
109
+ // 1. Copy base template
110
+ // Honour EXCLUDE_FROM_MIRROR so paths like `pages/api/**` (worker-only SSR
111
+ // endpoints) are never emitted into a generated project — see W2 spec-lock
112
+ // Decision 5 (#1728). Today templates/base/ does not contain any excluded
113
+ // paths, but the filter documents the policy in code that runs.
114
+ await fs.copy(baseDir, targetDir, {
115
+ filter: (src) => shouldCopyBaseFile(src, baseDir),
116
+ });
117
+ // 2. Copy skill symlinker script when enabled
118
+ if (choices.features.includes("skillSymlinker")) {
119
+ const scriptSrc = path.join(monorepoRoot, "scripts/setup-doc-skill.sh");
120
+ const scriptDest = path.join(targetDir, "scripts/setup-doc-skill.sh");
121
+ if (await fs.pathExists(scriptSrc)) {
122
+ await fs.copy(scriptSrc, scriptDest);
123
+ }
124
+ }
125
+ // 2b. Copy user-facing Claude Code skills when enabled
126
+ // Ships the curated zudo-doc-* skills (design-system, translate, version-bump)
127
+ // from the monorepo's .claude/skills/ into the user's .claude/skills/.
128
+ if (choices.features.includes("claudeSkills")) {
129
+ const userFacingSkills = [
130
+ "zudo-doc-design-system",
131
+ "zudo-doc-translate",
132
+ "zudo-doc-version-bump",
133
+ ];
134
+ for (const skill of userFacingSkills) {
135
+ const skillSrc = path.join(monorepoRoot, ".claude/skills", skill);
136
+ const skillDest = path.join(targetDir, ".claude/skills", skill);
137
+ if (await fs.pathExists(skillSrc)) {
138
+ await fs.copy(skillSrc, skillDest);
139
+ }
140
+ }
141
+ }
142
+ const defaultLang = choices.defaultLang;
143
+ const escapedName = capitalize(choices.projectName.replace(/-/g, " "));
144
+ // Place primary content in src/content/docs/
145
+ const primaryContent = defaultLang === "ja"
146
+ ? STARTER_CONTENT_JA()
147
+ : STARTER_CONTENT_EN(escapedName);
148
+ await fs.outputFile(path.join(targetDir, "src/content/docs/getting-started/index.mdx"), primaryContent);
149
+ // When i18n is ON, place secondary language content
150
+ if (choices.features.includes("i18n")) {
151
+ const secondaryLang = getSecondaryLang(defaultLang);
152
+ const secondaryDir = `src/content/docs-${secondaryLang}`;
153
+ await fs.ensureDir(path.join(targetDir, secondaryDir));
154
+ const secondaryContent = secondaryLang === "ja"
155
+ ? STARTER_CONTENT_JA()
156
+ : STARTER_CONTENT_EN(escapedName);
157
+ await fs.outputFile(path.join(targetDir, `${secondaryDir}/getting-started/index.mdx`), secondaryContent);
158
+ }
159
+ // When changelog is ON, create a starter changelog page
160
+ if (choices.features.includes("changelog")) {
161
+ const changelogContent = defaultLang === "ja" ? CHANGELOG_CONTENT_JA() : CHANGELOG_CONTENT_EN();
162
+ await fs.outputFile(path.join(targetDir, "src/content/docs/changelog/index.mdx"), changelogContent);
163
+ if (choices.features.includes("i18n")) {
164
+ const secondaryLang = getSecondaryLang(defaultLang);
165
+ const secondaryChangelogContent = secondaryLang === "ja"
166
+ ? CHANGELOG_CONTENT_JA()
167
+ : CHANGELOG_CONTENT_EN();
168
+ await fs.outputFile(path.join(targetDir, `src/content/docs-${secondaryLang}/changelog/index.mdx`), secondaryChangelogContent);
169
+ }
170
+ }
171
+ // 3. Generate programmatic files
172
+ const settingsContent = generateSettingsFile(choices);
173
+ await fs.outputFile(path.join(targetDir, "src/config/settings.ts"), settingsContent);
174
+ const zfbConfigContent = generateZfbConfig(choices);
175
+ await fs.outputFile(path.join(targetDir, "zfb.config.ts"), zfbConfigContent);
176
+ const pkg = generatePackageJson(choices);
177
+ await fs.outputFile(path.join(targetDir, "package.json"), JSON.stringify(pkg, null, 2) + "\n");
178
+ await fs.outputFile(path.join(targetDir, ".gitignore"), [
179
+ "# Build output",
180
+ "node_modules",
181
+ "dist",
182
+ ".zfb",
183
+ "",
184
+ "# macOS",
185
+ ".DS_Store",
186
+ "",
187
+ "# Environment",
188
+ ".env",
189
+ ".env.local",
190
+ ".env.*.local",
191
+ "",
192
+ "# Logs",
193
+ "*.log",
194
+ "npm-debug.log*",
195
+ "yarn-debug.log*",
196
+ "pnpm-debug.log*",
197
+ "",
198
+ "# Cloudflare Wrangler",
199
+ ".wrangler/",
200
+ "",
201
+ ].join("\n"));
202
+ const claudeContent = generateCLAUDEFile(choices);
203
+ await fs.outputFile(path.join(targetDir, "CLAUDE.md"), claudeContent);
204
+ // 4. Compose features (copy feature files + inject into shared files)
205
+ await composeFeatures(targetDir, choices, featureModules, featuresDir);
206
+ // Ensure content directories exist
207
+ await fs.ensureDir(path.join(targetDir, "src/content/docs"));
208
+ }
209
+ function generatePackageJson(choices) {
210
+ // Intentionally absent from scaffolded deps:
211
+ // @takazudo/zudo-doc-md-plugins — zero references in generator templates/source
212
+ // @takazudo/zfb-adapter-cloudflare — zero references in generator templates/source
213
+ const deps = {
214
+ // zfb engine — replaces astro/@astrojs/* now that the cutover (#500 S5)
215
+ // has retired the legacy Astro pipeline. Distributed as published npm
216
+ // packages (the prebuilt binary ships via an optionalDependency of
217
+ // @takazudo/zfb); pinned to the pre-release the scaffold targets.
218
+ // The two literals below must match root package.json's
219
+ // dependencies["@takazudo/zfb"] / ["@takazudo/zfb-runtime"] —
220
+ // enforced by scripts/check-pin-parity.mjs (W4A — #1732).
221
+ "@takazudo/zfb": "0.1.0-next.6",
222
+ "@takazudo/zfb-runtime": "0.1.0-next.6",
223
+ // @takazudo/zudo-doc — published from this monorepo via
224
+ // .github/workflows/publish-zudo-doc.yml. The pin here is bumped in
225
+ // lockstep by scripts/release-create-zudo-doc.sh whenever zudo-doc's
226
+ // version moves, so a fresh scaffold pulls the version we just published.
227
+ "@takazudo/zudo-doc": "^0.1.0",
228
+ // zod — used by the generated zfb.config.ts. zfb-config-gen emits
229
+ // `import { z } from "zod"` for the content-collection schema +
230
+ // `z.toJSONSchema(...)` conversion. Without this dep, the consumer
231
+ // fails at `zfb build` with esbuild "Could not resolve 'zod'" before
232
+ // any page compiles. The Astro→zfb retarget (3f0042f7) added the
233
+ // import without the runtime dep; W6B (#1735) consumer-build
234
+ // verification was the first to actually exercise it.
235
+ zod: "^4.0.0",
236
+ // ^10.29.1 floor satisfies @takazudo/zdtp's preact peer range so the app
237
+ // and zdtp resolve a single preact instance — a lower floor can split into
238
+ // two copies and crash hook-using SSR islands with "undefined reading __H".
239
+ preact: "^10.29.1",
240
+ // preact-render-to-string — zfb's emitted entry.mjs imports
241
+ // `renderToString` from this package as `__zfb_renderToString` to
242
+ // SSR each page. Without it, esbuild fails at the bundler step with
243
+ // "Could not resolve 'preact-render-to-string'" before any page
244
+ // compiles. Same pin as host. Caught by W6B (#1735) consumer-build
245
+ // verification.
246
+ "preact-render-to-string": "^6.6.6",
247
+ shiki: "^4.0.2",
248
+ "@shikijs/transformers": "^4.0.0",
249
+ clsx: "^2.1.0",
250
+ "gray-matter": "^4.0.0",
251
+ "github-slugger": "^2.0.0",
252
+ mermaid: "^11.12.3",
253
+ "remark-cjk-friendly": "^2.0.1",
254
+ "remark-directive": "^3.0.0",
255
+ "unist-util-visit": "^5.1.0",
256
+ // katex — server-side LaTeX renderer used by the always-on
257
+ // pages/lib/_math-block.tsx (called from pages/_mdx-components.ts
258
+ // for `$…$` and `$$…$$` math nodes). Caught by W6B (#1735)
259
+ // consumer-build verification — the import lives in the mirrored
260
+ // pages, not behind any feature gate. Same pin as host.
261
+ katex: "^0.16.38",
262
+ };
263
+ const devDeps = {
264
+ "@tailwindcss/vite": "^4.2.0",
265
+ tailwindcss: "^4.2.0",
266
+ typescript: "^5.9.0",
267
+ "@types/hast": "^3.0.4",
268
+ "@types/mdast": "^4.0.4",
269
+ "@types/node": "^22.0.0",
270
+ "@types/react": "^19.2.0", // needed for preact/compat type resolution
271
+ "html-validate": "^10.0.0",
272
+ };
273
+ if (choices.features.includes("search")) {
274
+ deps["minisearch"] = "^7.2.0";
275
+ devDeps["pagefind"] = "^1.4.0";
276
+ }
277
+ if (choices.features.includes("docHistory")) {
278
+ deps["diff"] = "^8.0.3";
279
+ // W7A (#1736): doc-history-plugin.mjs spawns `tsx -e <inline-script>` to
280
+ // run the v2 runtime in a TS-aware Node subprocess; without tsx the
281
+ // plugin's preBuild step exits with ENOENT before zfb finishes config
282
+ // load.
283
+ devDeps["tsx"] = "^4.21.0";
284
+ }
285
+ if (choices.features.includes("claudeResources")) {
286
+ // W7A (#1736): claude-resources-plugin.mjs spawns `tsx -e <inline-script>`
287
+ // for the same reason as doc-history (TS-aware Node subprocess wrapping
288
+ // the v2 runner).
289
+ devDeps["tsx"] = "^4.21.0";
290
+ }
291
+ if (choices.features.includes("designTokenPanel")) {
292
+ deps["@takazudo/zdtp"] = "0.1.0-next.1";
293
+ }
294
+ if (choices.features.includes("tagGovernance")) {
295
+ // gray-matter is already in `deps` unconditionally (base template uses it),
296
+ // so we only add the tooling deps specific to tags:audit / tags:suggest.
297
+ devDeps["string-similarity"] = "^4.0.4";
298
+ devDeps["@types/string-similarity"] = "^4.0.2";
299
+ devDeps["pluralize"] = "^8.0.0";
300
+ devDeps["@types/pluralize"] = "^0.0.33";
301
+ devDeps["picocolors"] = "^1.1.1";
302
+ devDeps["@inquirer/prompts"] = "^8.4.2";
303
+ devDeps["tsx"] = "^4.21.0";
304
+ }
305
+ const scripts = {
306
+ dev: "zfb dev",
307
+ build: "zfb build",
308
+ preview: "zfb preview",
309
+ check: "zfb check",
310
+ "check:html": "html-validate \"dist/**/*.html\"",
311
+ };
312
+ if (choices.features.includes("tagGovernance")) {
313
+ scripts["tags:audit"] = "tsx scripts/tags-audit.ts";
314
+ scripts["tags:suggest"] = "tsx scripts/tags-suggest.ts";
315
+ }
316
+ if (choices.features.includes("skillSymlinker")) {
317
+ scripts["setup:doc-skill"] = "bash scripts/setup-doc-skill.sh";
318
+ }
319
+ const runCmd = choices.packageManager === "npm" || choices.packageManager === "bun" ? `${choices.packageManager} run` : choices.packageManager;
320
+ // claudeSkills ships the zudo-doc-version-bump skill, whose release workflow
321
+ // calls `<pm> b4push`. Emit a minimal stub so the skill does not hit a
322
+ // "script not found" error on freshly scaffolded projects. Consumers are
323
+ // free to expand this into a richer pre-push pipeline later.
324
+ if (choices.features.includes("claudeSkills")) {
325
+ scripts["b4push"] = `${runCmd} check && ${runCmd} build`;
326
+ }
327
+ if (choices.features.includes("tauri")) {
328
+ scripts["dev:tauri"] = "cargo tauri dev";
329
+ scripts["build:tauri"] = `${runCmd} build && cargo tauri build`;
330
+ }
331
+ if (choices.features.includes("tauriDev")) {
332
+ scripts["dev:tauri-dev"] = "cd src-tauri-dev && cargo tauri dev";
333
+ scripts["build:tauri-dev"] = "cd src-tauri-dev && cargo tauri build";
334
+ }
335
+ return {
336
+ name: choices.projectName,
337
+ version: "0.0.1",
338
+ private: true,
339
+ type: "module",
340
+ scripts,
341
+ dependencies: deps,
342
+ devDependencies: devDeps,
343
+ };
344
+ }
@@ -0,0 +1,2 @@
1
+ import type { UserChoices } from "./prompts.js";
2
+ export declare function generateSettingsFile(choices: UserChoices): string;
@@ -0,0 +1,237 @@
1
+ import { capitalize, getLangLabel, getSecondaryLang } from "./utils.js";
2
+ export function generateSettingsFile(choices) {
3
+ const lines = [];
4
+ // Import types from settings-types (copied from template src/config/)
5
+ lines.push(`export type {`);
6
+ lines.push(` HeaderNavChildItem,`);
7
+ lines.push(` HeaderNavItem,`);
8
+ lines.push(` HeaderRightItem,`);
9
+ lines.push(` ColorModeConfig,`);
10
+ lines.push(` HtmlPreviewConfig,`);
11
+ lines.push(` FrontmatterPreviewConfig,`);
12
+ lines.push(` LocaleConfig,`);
13
+ lines.push(` VersionConfig,`);
14
+ lines.push(` FooterConfig,`);
15
+ lines.push(` BodyFootUtilAreaConfig,`);
16
+ lines.push(` TagPlacement,`);
17
+ lines.push(` TagGovernanceMode,`);
18
+ lines.push(` TagVocabularyEntry,`);
19
+ lines.push(`} from "./settings-types";`);
20
+ lines.push(`import type {`);
21
+ lines.push(` HeaderNavItem,`);
22
+ lines.push(` HeaderRightItem,`);
23
+ lines.push(` ColorModeConfig,`);
24
+ lines.push(` HtmlPreviewConfig,`);
25
+ lines.push(` FrontmatterPreviewConfig,`);
26
+ lines.push(` LocaleConfig,`);
27
+ lines.push(` VersionConfig,`);
28
+ lines.push(` FooterConfig,`);
29
+ lines.push(` BodyFootUtilAreaConfig,`);
30
+ lines.push(` TagPlacement,`);
31
+ lines.push(` TagGovernanceMode,`);
32
+ lines.push(`} from "./settings-types";`);
33
+ lines.push(``);
34
+ lines.push(`export const settings = {`);
35
+ if (choices.colorSchemeMode === "single") {
36
+ lines.push(` colorScheme: ${JSON.stringify(choices.singleScheme ?? "Dracula")},`);
37
+ lines.push(` colorMode: false as ColorModeConfig | false,`);
38
+ }
39
+ else {
40
+ lines.push(` colorScheme: ${JSON.stringify(choices.darkScheme ?? "GitHub Dark")},`);
41
+ lines.push(` colorMode: {`);
42
+ lines.push(` defaultMode: ${JSON.stringify(choices.defaultMode ?? "dark")},`);
43
+ lines.push(` lightScheme: ${JSON.stringify(choices.lightScheme ?? "GitHub Light")},`);
44
+ lines.push(` darkScheme: ${JSON.stringify(choices.darkScheme ?? "GitHub Dark")},`);
45
+ lines.push(` respectPrefersColorScheme: ${choices.respectPrefersColorScheme ?? true},`);
46
+ lines.push(` } satisfies ColorModeConfig,`);
47
+ }
48
+ lines.push(` siteName: ${JSON.stringify(capitalize(choices.projectName.replace(/-/g, " ")))},`);
49
+ lines.push(` siteDescription: "" as string,`);
50
+ lines.push(` base: "/",`);
51
+ lines.push(` trailingSlash: false as boolean,`);
52
+ lines.push(` noindex: false as boolean,`);
53
+ lines.push(` editUrl: false as string | false,`);
54
+ const rawGithubUrl = (choices.githubUrl ?? "").trim();
55
+ if (rawGithubUrl) {
56
+ lines.push(` githubUrl: ${JSON.stringify(rawGithubUrl)} as string | false,`);
57
+ }
58
+ else {
59
+ lines.push(` githubUrl: false as string | false,`);
60
+ }
61
+ lines.push(` siteUrl: "" as string,`);
62
+ lines.push(` docsDir: "src/content/docs",`);
63
+ lines.push(` defaultLocale: ${JSON.stringify(choices.defaultLang ?? "en")} as string,`);
64
+ if (choices.features.includes("i18n")) {
65
+ const secondaryLang = getSecondaryLang(choices.defaultLang);
66
+ const secondaryLabel = getLangLabel(secondaryLang);
67
+ lines.push(` locales: {`);
68
+ lines.push(` ${secondaryLang}: { label: ${JSON.stringify(secondaryLabel)}, dir: "src/content/docs-${secondaryLang}" },`);
69
+ lines.push(` } as Record<string, LocaleConfig>,`);
70
+ }
71
+ else {
72
+ lines.push(` locales: {} as Record<string, LocaleConfig>,`);
73
+ }
74
+ lines.push(` mermaid: true,`);
75
+ lines.push(` sitemap: false,`);
76
+ lines.push(` docMetainfo: false,`);
77
+ lines.push(` docTags: false,`);
78
+ lines.push(` tagPlacement: "after-title" as TagPlacement,`);
79
+ if (choices.features.includes("tagGovernance")) {
80
+ lines.push(` tagGovernance: "warn" as TagGovernanceMode,`);
81
+ lines.push(` tagVocabulary: true as boolean,`);
82
+ }
83
+ else {
84
+ lines.push(` tagGovernance: "off" as TagGovernanceMode,`);
85
+ lines.push(` tagVocabulary: false as boolean,`);
86
+ }
87
+ lines.push(` frontmatterPreview: false as FrontmatterPreviewConfig | false,`);
88
+ if (choices.features.includes("llmsTxt")) {
89
+ lines.push(` llmsTxt: true,`);
90
+ }
91
+ else {
92
+ lines.push(` llmsTxt: false,`);
93
+ }
94
+ lines.push(` math: false,`);
95
+ lines.push(` cjkFriendly: ${choices.cjkFriendly ?? false} as boolean,`);
96
+ lines.push(` onBrokenMarkdownLinks: "warn" as "warn" | "error" | "ignore",`);
97
+ lines.push(` aiAssistant: false as boolean,`);
98
+ if (choices.features.includes("docHistory")) {
99
+ lines.push(` docHistory: true,`);
100
+ }
101
+ else {
102
+ lines.push(` docHistory: false,`);
103
+ }
104
+ if (choices.features.includes("bodyFootUtil")) {
105
+ lines.push(` bodyFootUtilArea: {`);
106
+ lines.push(` docHistory: ${choices.features.includes("docHistory")},`);
107
+ lines.push(` viewSourceLink: ${Boolean(rawGithubUrl)},`);
108
+ lines.push(` } satisfies BodyFootUtilAreaConfig as BodyFootUtilAreaConfig | false,`);
109
+ }
110
+ else {
111
+ lines.push(` bodyFootUtilArea: false as BodyFootUtilAreaConfig | false,`);
112
+ }
113
+ if (choices.features.includes("designTokenPanel")) {
114
+ lines.push(` designTokenPanel: true as boolean,`);
115
+ }
116
+ else {
117
+ lines.push(` designTokenPanel: false as boolean,`);
118
+ }
119
+ // Deprecated alias — kept for one release so existing user projects keep
120
+ // working. Prefer `designTokenPanel` above; when this alias is unset, only
121
+ // the new flag is consulted.
122
+ lines.push(` colorTweakPanel: undefined as boolean | undefined,`);
123
+ if (choices.features.includes("sidebarResizer")) {
124
+ lines.push(` sidebarResizer: true as boolean,`);
125
+ }
126
+ else {
127
+ lines.push(` sidebarResizer: false as boolean,`);
128
+ }
129
+ if (choices.features.includes("sidebarToggle")) {
130
+ lines.push(` sidebarToggle: true as boolean,`);
131
+ }
132
+ else {
133
+ lines.push(` sidebarToggle: false as boolean,`);
134
+ }
135
+ if (choices.features.includes("imageEnlarge")) {
136
+ lines.push(` imageEnlarge: true as boolean,`);
137
+ }
138
+ else {
139
+ lines.push(` imageEnlarge: false as boolean,`);
140
+ }
141
+ lines.push(` htmlPreview: undefined as HtmlPreviewConfig | undefined,`);
142
+ if (choices.features.includes("versioning")) {
143
+ lines.push(` versions: [] as VersionConfig[],`);
144
+ }
145
+ else {
146
+ lines.push(` versions: false as VersionConfig[] | false,`);
147
+ }
148
+ if (choices.features.includes("claudeResources")) {
149
+ lines.push(` claudeResources: {`);
150
+ lines.push(` claudeDir: ".claude",`);
151
+ lines.push(` } as { claudeDir: string; projectRoot?: string } | false,`);
152
+ }
153
+ else {
154
+ lines.push(` claudeResources: false as { claudeDir: string; projectRoot?: string } | false,`);
155
+ }
156
+ if (choices.features.includes("claudeResources")) {
157
+ lines.push(` defaultLocaleOnlyPrefixes: [`);
158
+ lines.push(` "/docs/claude-md/",`);
159
+ lines.push(` "/docs/claude-skills/",`);
160
+ lines.push(` "/docs/claude-agents/",`);
161
+ lines.push(` "/docs/claude-commands/",`);
162
+ lines.push(` ] as string[],`);
163
+ }
164
+ else {
165
+ lines.push(` defaultLocaleOnlyPrefixes: [] as string[],`);
166
+ }
167
+ if (choices.features.includes("footerNavGroup") ||
168
+ choices.features.includes("footerCopyright") ||
169
+ choices.features.includes("footerTaglist")) {
170
+ lines.push(` footer: {`);
171
+ if (choices.features.includes("footerNavGroup")) {
172
+ lines.push(` links: [`);
173
+ lines.push(` {`);
174
+ lines.push(` title: "Docs",`);
175
+ lines.push(` items: [`);
176
+ lines.push(` { label: "Getting Started", href: "/docs/getting-started" },`);
177
+ lines.push(` ],`);
178
+ lines.push(` },`);
179
+ lines.push(` ],`);
180
+ }
181
+ else {
182
+ lines.push(` links: [],`);
183
+ }
184
+ if (choices.features.includes("footerCopyright")) {
185
+ lines.push(` copyright: "Copyright © ${new Date().getFullYear()} Your Name. Built with zudo-doc.",`);
186
+ }
187
+ if (choices.features.includes("footerTaglist")) {
188
+ lines.push(` taglist: {`);
189
+ lines.push(` enabled: true,`);
190
+ lines.push(` groupBy: "group",`);
191
+ lines.push(` },`);
192
+ }
193
+ lines.push(` } satisfies FooterConfig as FooterConfig | false,`);
194
+ }
195
+ else {
196
+ lines.push(` footer: false as FooterConfig | false,`);
197
+ }
198
+ lines.push(` headerNav: [`);
199
+ lines.push(` { label: "Getting Started", path: "/docs/getting-started", categoryMatch: "getting-started" },`);
200
+ if (choices.features.includes("changelog")) {
201
+ lines.push(` { label: "Changelog", path: "/docs/changelog", categoryMatch: "changelog" },`);
202
+ }
203
+ lines.push(` ] as HeaderNavItem[],`);
204
+ lines.push(` headerRightItems: [`);
205
+ if (choices.headerRightItems !== undefined) {
206
+ // User-supplied override (including empty array): emit each entry verbatim,
207
+ // in the chosen order. An empty array means "no header-right items" — honor it.
208
+ // filterHeaderRightItems (src/utils/header-right-items.ts) handles runtime
209
+ // hiding of items whose feature is disabled — no need to gate emission
210
+ // here on choices.features.
211
+ for (const item of choices.headerRightItems) {
212
+ if (item.type === "trigger") {
213
+ lines.push(` { type: "trigger", trigger: ${JSON.stringify(item.trigger)} },`);
214
+ }
215
+ else {
216
+ lines.push(` { type: "component", component: ${JSON.stringify(item.component)} },`);
217
+ }
218
+ }
219
+ }
220
+ else {
221
+ // Default fallback: hardcoded order, gated on selected features.
222
+ if (choices.features.includes("designTokenPanel")) {
223
+ lines.push(` { type: "trigger", trigger: "design-token-panel" },`);
224
+ }
225
+ if (choices.features.includes("versioning")) {
226
+ lines.push(` { type: "component", component: "version-switcher" },`);
227
+ }
228
+ lines.push(` { type: "component", component: "github-link" },`);
229
+ lines.push(` { type: "component", component: "theme-toggle" },`);
230
+ if (choices.features.includes("i18n")) {
231
+ lines.push(` { type: "component", component: "language-switcher" },`);
232
+ }
233
+ }
234
+ lines.push(` ] as HeaderRightItem[],`);
235
+ lines.push(`};`);
236
+ return lines.join("\n") + "\n";
237
+ }
@@ -0,0 +1,8 @@
1
+ export declare function installDependencies(dir: string, pm: string): void;
2
+ export declare function capitalize(str: string): string;
3
+ /** Get a short uppercase label for a language code (e.g. "en" → "EN", "zh-cn" → "ZH-CN"). */
4
+ export declare function getLangLabel(langCode: string): string;
5
+ /** Determine the secondary language code when i18n is enabled. */
6
+ export declare function getSecondaryLang(defaultLang: string): string;
7
+ /** Apply a list of regex replacements to a file (if it exists). */
8
+ export declare function patchFile(filePath: string, replacements: [RegExp, string][]): Promise<void>;
package/dist/utils.js ADDED
@@ -0,0 +1,34 @@
1
+ import { execSync } from "child_process";
2
+ import fs from "fs-extra";
3
+ export function installDependencies(dir, pm) {
4
+ const commands = {
5
+ pnpm: "pnpm install",
6
+ npm: "npm install",
7
+ yarn: "yarn",
8
+ bun: "bun install",
9
+ };
10
+ const cmd = commands[pm] || "npm install";
11
+ // Use pipe to avoid garbled output when used alongside spinner
12
+ execSync(cmd, { cwd: dir, stdio: "pipe" });
13
+ }
14
+ export function capitalize(str) {
15
+ return str.replace(/\b\w/g, (c) => c.toUpperCase());
16
+ }
17
+ /** Get a short uppercase label for a language code (e.g. "en" → "EN", "zh-cn" → "ZH-CN"). */
18
+ export function getLangLabel(langCode) {
19
+ return langCode.toUpperCase();
20
+ }
21
+ /** Determine the secondary language code when i18n is enabled. */
22
+ export function getSecondaryLang(defaultLang) {
23
+ return defaultLang === "en" ? "ja" : "en";
24
+ }
25
+ /** Apply a list of regex replacements to a file (if it exists). */
26
+ export async function patchFile(filePath, replacements) {
27
+ if (!(await fs.pathExists(filePath)))
28
+ return;
29
+ let content = await fs.readFile(filePath, "utf-8");
30
+ for (const [pattern, replacement] of replacements) {
31
+ content = content.replace(pattern, replacement);
32
+ }
33
+ await fs.writeFile(filePath, content);
34
+ }
@@ -0,0 +1,19 @@
1
+ import type { UserChoices } from "./prompts.js";
2
+ /**
3
+ * Programmatically generate zfb.config.ts from user choices.
4
+ *
5
+ * W7A (#1736): emits zfb plugins in the host's INLINE-OBJECT shape —
6
+ * `{ name: "./plugins/<plugin>.mjs", options: {...} }` — not the
7
+ * pre-cutover factory-import pattern (`import { fooPlugin } from
8
+ * "./src/integrations/foo"`). Inline functions are not supported by zfb's
9
+ * plugin runtime (see `@takazudo/zfb/plugins` source); plugins MUST be
10
+ * authored as standalone `.mjs` modules referenced from `zfb.config.ts`
11
+ * by `name`. The plugin source files are shipped by the base/feature
12
+ * templates under `plugins/<plugin>.mjs` and `templates/features/<feature>/
13
+ * files/plugins/<plugin>.mjs`.
14
+ *
15
+ * Replaces the former astro-config-gen.ts + content-config-gen.ts pair.
16
+ * In the zfb world, content-collection schemas live inside zfb.config.ts
17
+ * itself — there is no separate content.config.ts.
18
+ */
19
+ export declare function generateZfbConfig(choices: UserChoices): string;