create-zudo-doc 3.2.0 → 4.0.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 (137) hide show
  1. package/README.md +3 -2
  2. package/dist/api.d.ts +2 -0
  3. package/dist/api.js +9 -1
  4. package/dist/claude-md-gen.d.ts +8 -0
  5. package/dist/claude-md-gen.js +44 -26
  6. package/dist/cli.d.ts +2 -0
  7. package/dist/cli.js +11 -1
  8. package/dist/compose.d.ts +14 -20
  9. package/dist/compose.js +15 -25
  10. package/dist/constants.d.ts +6 -0
  11. package/dist/constants.js +123 -1
  12. package/dist/features/body-foot-util.d.ts +8 -4
  13. package/dist/features/body-foot-util.js +8 -4
  14. package/dist/features/claude-resources.d.ts +9 -0
  15. package/dist/features/claude-resources.js +10 -6
  16. package/dist/features/design-token-panel.d.ts +0 -12
  17. package/dist/features/design-token-panel.js +32 -93
  18. package/dist/features/doc-history.d.ts +19 -3
  19. package/dist/features/doc-history.js +49 -60
  20. package/dist/features/doc-tags.d.ts +9 -13
  21. package/dist/features/doc-tags.js +10 -26
  22. package/dist/features/dynamic-page-transition.d.ts +19 -30
  23. package/dist/features/dynamic-page-transition.js +21 -209
  24. package/dist/features/footer-taglist.d.ts +1 -1
  25. package/dist/features/footer-taglist.js +1 -1
  26. package/dist/features/footer.d.ts +3 -2
  27. package/dist/features/footer.js +3 -2
  28. package/dist/features/i18n.d.ts +13 -8
  29. package/dist/features/i18n.js +14 -9
  30. package/dist/features/image-enlarge.d.ts +7 -26
  31. package/dist/features/image-enlarge.js +7 -26
  32. package/dist/features/index.js +2 -0
  33. package/dist/features/llms-txt.d.ts +3 -5
  34. package/dist/features/llms-txt.js +3 -5
  35. package/dist/features/search.d.ts +7 -3
  36. package/dist/features/search.js +7 -3
  37. package/dist/features/sidebar-resizer.d.ts +4 -8
  38. package/dist/features/sidebar-resizer.js +4 -8
  39. package/dist/features/sidebar-toggle.d.ts +7 -7
  40. package/dist/features/sidebar-toggle.js +7 -7
  41. package/dist/features/tag-governance.d.ts +3 -8
  42. package/dist/features/tag-governance.js +37 -9
  43. package/dist/features/tauri.d.ts +13 -10
  44. package/dist/features/tauri.js +16 -52
  45. package/dist/features/theme-pack-switcher.d.ts +11 -0
  46. package/dist/features/theme-pack-switcher.js +13 -0
  47. package/dist/features/versioning.d.ts +12 -24
  48. package/dist/features/versioning.js +13 -39
  49. package/dist/index.js +5 -3
  50. package/dist/preset.d.ts +2 -0
  51. package/dist/preset.js +12 -1
  52. package/dist/prompts.d.ts +2 -0
  53. package/dist/prompts.js +22 -1
  54. package/dist/scaffold.d.ts +13 -6
  55. package/dist/scaffold.js +97 -78
  56. package/dist/utils.d.ts +10 -0
  57. package/dist/utils.js +14 -0
  58. package/dist/zfb-config-gen.d.ts +32 -20
  59. package/dist/zfb-config-gen.js +410 -53
  60. package/package.json +2 -2
  61. package/templates/base/pages/docs/[[...slug]].tsx +64 -0
  62. package/templates/base/pages/index.tsx +6 -41
  63. package/templates/base/src/styles/global.css +15 -340
  64. package/templates/base/tsconfig.json +3 -26
  65. package/templates/features/i18n/files/pages/[locale]/docs/[[...slug]].tsx +92 -0
  66. package/dist/settings-gen.d.ts +0 -2
  67. package/dist/settings-gen.js +0 -320
  68. package/templates/base/.htmlvalidate.json +0 -5
  69. package/templates/base/.zfb/doc-history-meta.json +0 -1
  70. package/templates/base/pages/_data.ts +0 -160
  71. package/templates/base/pages/lib/_body-end-islands.tsx +0 -165
  72. package/templates/base/pages/lib/_chrome.ts +0 -167
  73. package/templates/base/pages/lib/_details.tsx +0 -29
  74. package/templates/base/pages/lib/_doc-route-entries.ts +0 -10
  75. package/templates/base/pages/lib/_extract-headings.ts +0 -37
  76. package/templates/base/pages/lib/_frontmatter-preview-data.ts +0 -27
  77. package/templates/base/pages/lib/_nav-source-cache.ts +0 -100
  78. package/templates/base/pages/lib/_nav-source-docs.ts +0 -17
  79. package/templates/base/pages/lib/_preset-generator.tsx +0 -81
  80. package/templates/base/pages/lib/_route-context.ts +0 -32
  81. package/templates/base/pages/lib/_search-widget.tsx +0 -17
  82. package/templates/base/pages/lib/doc-page-props.ts +0 -30
  83. package/templates/base/pages/lib/locale-merge.ts +0 -59
  84. package/templates/base/scripts/run-b4push.sh +0 -102
  85. package/templates/base/src/components/ai-chat-modal.tsx +0 -18
  86. package/templates/base/src/components/content/code-group.tsx +0 -3
  87. package/templates/base/src/components/content/content-admonition.tsx +0 -4
  88. package/templates/base/src/components/desktop-sidebar-toggle.tsx +0 -15
  89. package/templates/base/src/components/doc-history.tsx +0 -21
  90. package/templates/base/src/components/image-enlarge.tsx +0 -24
  91. package/templates/base/src/components/preset-generator.tsx +0 -14
  92. package/templates/base/src/components/sidebar-toggle.tsx +0 -6
  93. package/templates/base/src/components/sidebar-tree.tsx +0 -6
  94. package/templates/base/src/config/color-scheme-utils.ts +0 -69
  95. package/templates/base/src/config/color-schemes.ts +0 -165
  96. package/templates/base/src/config/docs-schema.ts +0 -95
  97. package/templates/base/src/config/frontmatter-preview-defaults.ts +0 -27
  98. package/templates/base/src/config/frontmatter-preview-renderers.tsx +0 -46
  99. package/templates/base/src/config/i18n.ts +0 -239
  100. package/templates/base/src/config/settings-types.ts +0 -45
  101. package/templates/base/src/config/sidebars.ts +0 -66
  102. package/templates/base/src/config/tag-vocabulary-types.ts +0 -4
  103. package/templates/base/src/config/tag-vocabulary.ts +0 -20
  104. package/templates/base/src/config/z-index-tokens.ts +0 -128
  105. package/templates/base/src/types/docs-entry.ts +0 -28
  106. package/templates/base/src/types/heading.ts +0 -5
  107. package/templates/base/src/types/locale.ts +0 -10
  108. package/templates/base/src/utils/base.ts +0 -164
  109. package/templates/base/src/utils/docs.ts +0 -446
  110. package/templates/base/src/utils/git-info.ts +0 -70
  111. package/templates/base/src/utils/github.ts +0 -22
  112. package/templates/base/src/utils/nav-scope.ts +0 -34
  113. package/templates/base/src/utils/sidebar.ts +0 -36
  114. package/templates/base/src/utils/slug.ts +0 -10
  115. package/templates/base/src/utils/smart-break.tsx +0 -12
  116. package/templates/base/src/utils/tags.ts +0 -83
  117. package/templates/base/zfb-shim.d.ts +0 -183
  118. package/templates/features/bodyFootUtil/files/src/utils/github.ts +0 -22
  119. package/templates/features/claudeResources/files/src/integrations/claude-resources/__tests__/escape-for-mdx.test.ts +0 -42
  120. package/templates/features/claudeResources/files/src/integrations/claude-resources/__tests__/generate.test.ts +0 -752
  121. package/templates/features/claudeResources/files/src/integrations/claude-resources/escape-for-mdx.ts +0 -97
  122. package/templates/features/claudeResources/files/src/integrations/claude-resources/generate.ts +0 -735
  123. package/templates/features/designTokenPanel/files/src/components/design-token-panel-bootstrap.tsx +0 -15
  124. package/templates/features/designTokenPanel/files/src/config/design-token-panel-config.ts +0 -435
  125. package/templates/features/designTokenPanel/files/src/config/design-tokens-manifest.ts +0 -174
  126. package/templates/features/designTokenPanel/files/src/lib/design-token-panel-bootstrap.ts +0 -30
  127. package/templates/features/docHistory/files/src/components/doc-history.tsx +0 -10
  128. package/templates/features/docHistory/files/src/types/doc-history.ts +0 -7
  129. package/templates/features/dynamicPageTransition/files/src/components/client-router-bootstrap.tsx +0 -72
  130. package/templates/features/i18n/files/pages/[locale]/index.tsx +0 -72
  131. package/templates/features/imageEnlarge/files/src/components/image-enlarge.tsx +0 -11
  132. package/templates/features/sidebarToggle/files/src/components/desktop-sidebar-toggle.tsx +0 -6
  133. package/templates/features/tagGovernance/files/scripts/tags-audit.ts +0 -131
  134. package/templates/features/tagGovernance/files/scripts/tags-suggest.ts +0 -428
  135. package/templates/features/tauri/files/src/components/find-bar.tsx +0 -122
  136. package/templates/features/tauri/files/src/components/find-in-page-init.tsx +0 -59
  137. package/templates/features/tauri/files/src/utils/find-in-page.ts +0 -175
@@ -1,62 +1,419 @@
1
+ import { capitalize, getSecondaryLang, getLangLabel } from "./utils.js";
1
2
  /**
2
- * Programmatically generate zfb.config.ts from user choices.
3
+ * Programmatically generate the ONE `zfb.config.ts` a scaffolded project
4
+ * ships (epic zudolab/zudo-doc#2651, minimal-scaffold Wave 6 #2660).
3
5
  *
4
- * S5b (#2329): collapsed to the thin preset-based shape that mirrors the
5
- * showcase `zfb.config.ts` after S5a. All collection wiring, plugin
6
- * descriptors, markdown features, codeHighlight, resolveMarkdownLinks,
7
- * trailingSlash, and minifyHtml are now owned by `zudoDocPreset()` in
8
- * `@takazudo/zudo-doc/preset`. The generated config spreads the preset
9
- * result into `defineConfig` and keeps only the project-owned shell fields
10
- * (`framework`, `port`, `tailwind`, `base`).
6
+ * Replaces the former `settings-gen.ts` + `zfb-config-gen.ts` pair: there is
7
+ * no more `src/config/settings.ts` — every user choice becomes a field
8
+ * passed straight into `zudoDoc({ ... })` (`@takazudo/zudo-doc/config`),
9
+ * which merges it over the package's own documented defaults and returns a
10
+ * complete `ZfbConfig`.
11
11
  *
12
- * Replaces the former astro-config-gen.ts + content-config-gen.ts pair.
13
- * In the zfb world, content-collection schemas live inside zfb.config.ts
14
- * itself — there is no separate content.config.ts.
15
- *
16
- * `_choices` is intentionally unused: post-S5b the emitted config is a
17
- * CONSTANT. All feature variation is driven by `settings.*` (read at
18
- * zfb-load time inside `zudoDocPreset()`), so the generated file is byte
19
- * identical for every feature combination. The parameter is retained only
20
- * for call-site compatibility (`scaffold.ts` passes `choices`). Do NOT add
21
- * feature-gated branches here — wire new feature behaviour into
22
- * `packages/zudo-doc/src/preset.ts` and the project's `settings.ts` instead.
12
+ * DIFF-FROM-DEFAULTS (locked, #2653 Decision 2): only fields whose resolved
13
+ * value differs from the matching `ZudoDocConfig` `@default` are emitted —
14
+ * `siteName` is the one field always emitted. `DEFAULT_MIRROR` below is a
15
+ * hand-kept copy of `packages/zudo-doc/src/config.ts`'s `DEFAULT_SETTINGS`
16
+ * for exactly the fields this generator ever sets. It has to be a local
17
+ * mirror (not an import) because `create-zudo-doc` does not depend on
18
+ * `@takazudo/zudo-doc` at generator-build time — the package is only ever a
19
+ * runtime dependency of the SCAFFOLDED project, never of the generator
20
+ * itself. Keep this mirror in sync by hand whenever `DEFAULT_SETTINGS`
21
+ * changes for one of the fields listed here.
23
22
  */
24
- export function generateZfbConfig(_choices) {
23
+ // ---------------------------------------------------------------------------
24
+ // DEFAULT_MIRROR — see the file header. Only fields this generator can ever
25
+ // set need an entry; anything else is simply never emitted.
26
+ // ---------------------------------------------------------------------------
27
+ export const DEFAULT_MIRROR = {
28
+ colorScheme: "Default Dark",
29
+ colorMode: {
30
+ defaultMode: "dark",
31
+ lightScheme: "Default Light",
32
+ darkScheme: "Default Dark",
33
+ respectPrefersColorScheme: true,
34
+ },
35
+ themePack: "default",
36
+ themePackSwitcher: false,
37
+ themePacks: undefined,
38
+ siteName: "Docs",
39
+ minifyHtml: true,
40
+ defaultLocale: "en",
41
+ locales: {},
42
+ noindex: false,
43
+ githubUrl: false,
44
+ metaTags: {
45
+ description: true,
46
+ keywords: false,
47
+ ogImage: false,
48
+ ogSiteName: true,
49
+ twitterCard: false,
50
+ },
51
+ docTags: false,
52
+ tagGovernance: "off",
53
+ tagVocabulary: false,
54
+ llmsTxt: false,
55
+ cjkFriendly: false,
56
+ designTokenPanel: false,
57
+ sidebarResizer: false,
58
+ sidebarToggle: false,
59
+ imageEnlarge: false,
60
+ findInPage: false,
61
+ dynamicPageTransition: false,
62
+ docHistory: false,
63
+ bodyFootUtilArea: false,
64
+ versions: false,
65
+ claudeResources: false,
66
+ defaultLocaleOnlyPrefixes: [],
67
+ footer: false,
68
+ headerNav: [],
69
+ headerRightItems: [{ type: "component", component: "theme-toggle" }],
70
+ };
71
+ function raw(code) {
72
+ return { __rawCode: code };
73
+ }
74
+ function isRawCode(value) {
75
+ return (typeof value === "object" &&
76
+ value !== null &&
77
+ "__rawCode" in value);
78
+ }
79
+ function deepEqual(a, b) {
80
+ if (a === b)
81
+ return true;
82
+ if (typeof a !== typeof b)
83
+ return false;
84
+ if (Array.isArray(a) || Array.isArray(b)) {
85
+ if (!Array.isArray(a) || !Array.isArray(b))
86
+ return false;
87
+ if (a.length !== b.length)
88
+ return false;
89
+ return a.every((v, i) => deepEqual(v, b[i]));
90
+ }
91
+ if (a && b && typeof a === "object" && typeof b === "object") {
92
+ const ak = Object.keys(a);
93
+ const bk = Object.keys(b);
94
+ if (ak.length !== bk.length)
95
+ return false;
96
+ return ak.every((k) => deepEqual(a[k], b[k]));
97
+ }
98
+ return false;
99
+ }
100
+ const IDENT_RE = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
101
+ function serializeValue(value, indent) {
102
+ const pad = " ".repeat(indent);
103
+ const padInner = " ".repeat(indent + 1);
104
+ if (isRawCode(value))
105
+ return value.__rawCode;
106
+ if (value === null)
107
+ return "null";
108
+ if (typeof value === "string")
109
+ return JSON.stringify(value);
110
+ if (typeof value === "number" || typeof value === "boolean") {
111
+ return String(value);
112
+ }
113
+ if (Array.isArray(value)) {
114
+ if (value.length === 0)
115
+ return "[]";
116
+ const items = value
117
+ .map((v) => `${padInner}${serializeValue(v, indent + 1)}`)
118
+ .join(",\n");
119
+ return `[\n${items},\n${pad}]`;
120
+ }
121
+ if (typeof value === "object") {
122
+ const entries = Object.entries(value).filter(([, v]) => v !== undefined);
123
+ if (entries.length === 0)
124
+ return "{}";
125
+ const items = entries
126
+ .map(([k, v]) => {
127
+ const key = IDENT_RE.test(k) ? k : JSON.stringify(k);
128
+ return `${padInner}${key}: ${serializeValue(v, indent + 1)}`;
129
+ })
130
+ .join(",\n");
131
+ return `{\n${items},\n${pad}}`;
132
+ }
133
+ throw new Error(`zfb-config-gen: cannot serialize value ${String(value)}`);
134
+ }
135
+ // ---------------------------------------------------------------------------
136
+ // Desired-config builder — mirrors every user-choice → settings mapping the
137
+ // old settings-gen.ts had, but as a plain object instead of emitted lines.
138
+ // ---------------------------------------------------------------------------
139
+ function buildDesiredConfig(choices) {
140
+ const desired = {};
141
+ // siteName is ALWAYS emitted (locked spec — the one field you almost
142
+ // always set, and the clearest anchor in a near-empty config file).
143
+ desired.siteName = capitalize(choices.projectName.replace(/-/g, " "));
144
+ // ── Color scheme ──────────────────────────────────────────────────────
145
+ if (choices.colorSchemeMode === "single") {
146
+ desired.colorScheme = choices.singleScheme ?? "Default Dark";
147
+ desired.colorMode = false;
148
+ }
149
+ else {
150
+ desired.colorScheme = choices.darkScheme ?? "Default Dark";
151
+ desired.colorMode = {
152
+ defaultMode: choices.defaultMode ?? "dark",
153
+ lightScheme: choices.lightScheme ?? "Default Light",
154
+ darkScheme: choices.darkScheme ?? "Default Dark",
155
+ respectPrefersColorScheme: choices.respectPrefersColorScheme ?? true,
156
+ };
157
+ }
158
+ // ── Theme pack (ADR #2818 Decision 7) ────────────────────────────────
159
+ // themePacks (the enabled-slugs allowlist) is intentionally NOT set here —
160
+ // it is an advanced, hand-edited-only field with no CLI/prompt surface
161
+ // (locked spec, #2823).
162
+ desired.themePack = choices.themePack ?? "default";
163
+ desired.themePackSwitcher = choices.features.includes("themePackSwitcher");
164
+ // ── i18n ──────────────────────────────────────────────────────────────
165
+ desired.defaultLocale = choices.defaultLang ?? "en";
166
+ if (choices.features.includes("i18n")) {
167
+ const secondaryLang = getSecondaryLang(choices.defaultLang);
168
+ desired.locales = {
169
+ [secondaryLang]: {
170
+ label: getLangLabel(secondaryLang),
171
+ dir: `src/content/docs-${secondaryLang}`,
172
+ },
173
+ };
174
+ }
175
+ else {
176
+ desired.locales = {};
177
+ }
178
+ // ── Misc site fields ──────────────────────────────────────────────────
179
+ desired.minifyHtml = choices.minifyHtml ?? true;
180
+ desired.noindex = choices.features.includes("noindex");
181
+ const rawGithubUrl = typeof choices.githubUrl === "string" ? choices.githubUrl.trim() : "";
182
+ desired.githubUrl = rawGithubUrl ? rawGithubUrl : false;
183
+ desired.cjkFriendly = choices.cjkFriendly ?? false;
184
+ // ── Meta tags ─────────────────────────────────────────────────────────
185
+ if (choices.metaTags) {
186
+ const mt = choices.metaTags;
187
+ desired.metaTags = {
188
+ description: mt.description !== undefined ? mt.description : true,
189
+ keywords: mt.keywords !== undefined ? mt.keywords : false,
190
+ ogImage: mt.ogImage !== undefined ? mt.ogImage : false,
191
+ ogSiteName: mt.ogSiteName !== undefined ? mt.ogSiteName : true,
192
+ ...(mt.twitterCard
193
+ ? {
194
+ twitterCard: mt.twitterCard,
195
+ ...(mt.twitterSite ? { twitterSite: mt.twitterSite } : {}),
196
+ ...(mt.twitterCreator
197
+ ? { twitterCreator: mt.twitterCreator }
198
+ : {}),
199
+ }
200
+ : { twitterCard: false }),
201
+ };
202
+ }
203
+ // ── Tags / docs ───────────────────────────────────────────────────────
204
+ desired.docTags = choices.features.includes("docTags");
205
+ if (choices.features.includes("tagGovernance")) {
206
+ // The explicit tag CLI config is also the zfb source of truth, so the
207
+ // package-owned bins and runtime settings cannot drift.
208
+ desired.tagGovernance = raw("tagCliConfig.governance");
209
+ desired.tagVocabulary = raw("tagCliConfig.vocabularyActive");
210
+ desired.tagVocabularyEntries = raw("tagCliConfig.vocabulary");
211
+ }
212
+ else {
213
+ desired.tagGovernance = "off";
214
+ desired.tagVocabulary = false;
215
+ }
216
+ desired.llmsTxt = choices.features.includes("llmsTxt");
217
+ // ── Feature toggles ───────────────────────────────────────────────────
218
+ desired.designTokenPanel = choices.features.includes("designTokenPanel");
219
+ desired.sidebarResizer = choices.features.includes("sidebarResizer");
220
+ desired.sidebarToggle = choices.features.includes("sidebarToggle");
221
+ desired.imageEnlarge = choices.features.includes("imageEnlarge");
222
+ // findInPage rides the existing tauri feature (#2690) — the Cmd/Ctrl+F find
223
+ // bar only makes sense inside the Tauri desktop shell, so it has no CLI
224
+ // flag or prompt of its own; there is no separate "findInPage" feature
225
+ // module.
226
+ desired.findInPage = choices.features.includes("tauri");
227
+ desired.dynamicPageTransition = choices.features.includes("dynamicPageTransition");
228
+ desired.docHistory = choices.features.includes("docHistory");
229
+ if (choices.features.includes("bodyFootUtil")) {
230
+ desired.bodyFootUtilArea = {
231
+ docHistory: choices.features.includes("docHistory"),
232
+ viewSourceLink: Boolean(rawGithubUrl),
233
+ };
234
+ }
235
+ else {
236
+ desired.bodyFootUtilArea = false;
237
+ }
238
+ desired.versions = choices.features.includes("versioning") ? [] : false;
239
+ if (choices.features.includes("claudeResources")) {
240
+ desired.claudeResources = { claudeDir: ".claude" };
241
+ desired.defaultLocaleOnlyPrefixes = [
242
+ "/docs/claude-md/",
243
+ "/docs/claude-skills/",
244
+ "/docs/claude-agents/",
245
+ "/docs/claude-commands/",
246
+ ];
247
+ }
248
+ else {
249
+ desired.claudeResources = false;
250
+ desired.defaultLocaleOnlyPrefixes = [];
251
+ }
252
+ // ── Footer ────────────────────────────────────────────────────────────
253
+ if (choices.features.includes("footerNavGroup") ||
254
+ choices.features.includes("footerCopyright") ||
255
+ choices.features.includes("footerTaglist")) {
256
+ const footer = {
257
+ links: choices.features.includes("footerNavGroup")
258
+ ? [
259
+ {
260
+ title: "Docs",
261
+ items: [
262
+ { label: "Getting Started", href: "/docs/getting-started" },
263
+ ],
264
+ },
265
+ ]
266
+ : [],
267
+ };
268
+ if (choices.features.includes("footerCopyright")) {
269
+ footer.copyright = `Copyright © ${new Date().getFullYear()} Your Name. Built with zudo-doc.`;
270
+ }
271
+ if (choices.features.includes("footerTaglist")) {
272
+ footer.taglist = { enabled: true, groupBy: "group" };
273
+ }
274
+ desired.footer = footer;
275
+ }
276
+ else {
277
+ desired.footer = false;
278
+ }
279
+ // ── Header nav / header-right ────────────────────────────────────────
280
+ const headerNav = [
281
+ {
282
+ label: "Getting Started",
283
+ path: "/docs/getting-started",
284
+ categoryMatch: "getting-started",
285
+ },
286
+ ];
287
+ // The "claude" categoryMatch is load-bearing beyond the header link — see
288
+ // the old settings-gen.ts note (preserved): getCategoryOrder() derives the
289
+ // satellite-grouping prefixes from headerNav.
290
+ if (choices.features.includes("claudeResources")) {
291
+ headerNav.push({
292
+ label: "Claude",
293
+ path: "/docs/claude",
294
+ categoryMatch: "claude",
295
+ });
296
+ }
297
+ if (choices.features.includes("changelog")) {
298
+ headerNav.push({
299
+ label: "Changelog",
300
+ path: "/docs/changelog",
301
+ categoryMatch: "changelog",
302
+ });
303
+ }
304
+ desired.headerNav = headerNav;
305
+ if (choices.headerRightItems !== undefined) {
306
+ // User-supplied override (including empty array) — emit verbatim.
307
+ desired.headerRightItems = choices.headerRightItems;
308
+ }
309
+ else {
310
+ const items = [];
311
+ if (choices.features.includes("designTokenPanel")) {
312
+ items.push({ type: "trigger", trigger: "design-token-panel" });
313
+ }
314
+ if (choices.features.includes("versioning")) {
315
+ items.push({ type: "component", component: "version-switcher" });
316
+ }
317
+ if (rawGithubUrl) {
318
+ items.push({ type: "component", component: "github-link" });
319
+ }
320
+ items.push({ type: "component", component: "theme-toggle" });
321
+ if (choices.features.includes("search")) {
322
+ items.push({ type: "component", component: "search" });
323
+ }
324
+ if (choices.features.includes("i18n")) {
325
+ items.push({ type: "component", component: "language-switcher" });
326
+ }
327
+ desired.headerRightItems = items;
328
+ }
329
+ return desired;
330
+ }
331
+ // ---------------------------------------------------------------------------
332
+ // Field order — purely cosmetic (mirrors ZudoDocConfig's declaration order
333
+ // in packages/zudo-doc/src/config.ts so the emitted file reads like the
334
+ // documented reference). Any key produced by buildDesiredConfig() that is
335
+ // missing here is appended AFTER the ordered ones in ALPHABETICAL order (see
336
+ // orderDesiredKeys) — defensive: a newly-added desired key someone forgot to
337
+ // list here still emits deterministically instead of being silently dropped.
338
+ // ---------------------------------------------------------------------------
339
+ const FIELD_ORDER = [
340
+ "colorScheme",
341
+ "colorMode",
342
+ "themePack",
343
+ "themePackSwitcher",
344
+ "themePacks",
345
+ "siteName",
346
+ "defaultLocale",
347
+ "locales",
348
+ "noindex",
349
+ "githubUrl",
350
+ "metaTags",
351
+ "docTags",
352
+ "tagGovernance",
353
+ "tagVocabulary",
354
+ "tagVocabularyEntries",
355
+ "llmsTxt",
356
+ "cjkFriendly",
357
+ "designTokenPanel",
358
+ "sidebarResizer",
359
+ "sidebarToggle",
360
+ "imageEnlarge",
361
+ "findInPage",
362
+ "dynamicPageTransition",
363
+ "docHistory",
364
+ "bodyFootUtilArea",
365
+ "versions",
366
+ "claudeResources",
367
+ "defaultLocaleOnlyPrefixes",
368
+ "footer",
369
+ "headerNav",
370
+ "headerRightItems",
371
+ "minifyHtml",
372
+ ];
373
+ /**
374
+ * Deterministic emission order for the keys of a `desired` config object:
375
+ * the keys present in `FIELD_ORDER` first (cosmetic reference order), then any
376
+ * leftover keys NOT in `FIELD_ORDER` appended in ALPHABETICAL order. The
377
+ * leftover branch exists so a key `buildDesiredConfig()` produces but nobody
378
+ * added to `FIELD_ORDER` is still emitted (deterministically) rather than
379
+ * silently dropped — matching what the `FIELD_ORDER` comment promises.
380
+ */
381
+ export function orderDesiredKeys(desiredKeys) {
382
+ const inOrder = FIELD_ORDER.filter((k) => desiredKeys.includes(k));
383
+ const leftover = desiredKeys
384
+ .filter((k) => !FIELD_ORDER.includes(k))
385
+ .sort();
386
+ return [...inOrder, ...leftover];
387
+ }
388
+ /**
389
+ * Generate the full `zfb.config.ts` source: `defineConfig(zudoDoc({ ... }))`
390
+ * with only the diff-from-defaults fields.
391
+ */
392
+ export function generateZfbConfig(choices) {
393
+ const desired = buildDesiredConfig(choices);
394
+ const emittedEntries = [];
395
+ for (const key of orderDesiredKeys(Object.keys(desired))) {
396
+ const value = desired[key];
397
+ // siteName always emitted (locked spec); everything else diff-from-default.
398
+ // A leftover key not in DEFAULT_MIRROR has `DEFAULT_MIRROR[key] ===
399
+ // undefined`, so it never deep-equals a real value and always emits.
400
+ if (key !== "siteName" && deepEqual(value, DEFAULT_MIRROR[key]))
401
+ continue;
402
+ emittedEntries.push([key, value]);
403
+ }
25
404
  const lines = [];
26
- // --- Imports ---
27
405
  lines.push(`import { defineConfig } from "zfb/config";`);
28
- lines.push(`import { zudoDocPreset } from "@takazudo/zudo-doc/preset";`);
29
- lines.push(`import { settings } from "./src/config/settings";`);
30
- lines.push(`import { buildDocsSchema } from "./src/config/docs-schema";`);
31
- lines.push(`import { translations } from "./src/config/i18n";`);
32
- lines.push(`import { colorSchemes } from "./src/config/color-schemes";`);
33
- lines.push(``);
34
- // --- Directive vocabulary ---
35
- // The seven canonical directives registered in pages/_mdx-components.ts.
36
- // "details" routes to DetailsWrapper — a collapsible, NOT an admonition.
37
- lines.push(`const directiveVocabulary = {`);
38
- lines.push(` note: "Note",`);
39
- lines.push(` tip: "Tip",`);
40
- lines.push(` info: "Info",`);
41
- lines.push(` warning: "Warning",`);
42
- lines.push(` danger: "Danger",`);
43
- lines.push(` caution: "Caution",`);
44
- lines.push(` details: "Details",`);
45
- lines.push(`};`);
46
- lines.push(``);
47
- // --- Export ---
48
- lines.push(`export default defineConfig({`);
49
- lines.push(` // ── Host-owned shell fields ──────────────────────────────────────────────`);
50
- lines.push(` framework: "preact",`);
51
- lines.push(` // Pin the dev/preview port — zfb defaults to 3000, but the generated`);
52
- lines.push(` // CLAUDE.md and the Tauri dev wrappers assume 4321.`);
53
- lines.push(` port: 4321,`);
54
- lines.push(` tailwind: { enabled: true },`);
55
- lines.push(` // Public URL prefix for <link rel="stylesheet"> and <script> tags.`);
56
- lines.push(` base: settings.base,`);
406
+ lines.push(`import { zudoDoc } from "@takazudo/zudo-doc/config";`);
407
+ if (choices.features.includes("tagGovernance")) {
408
+ lines.push(`import tagCliConfig from "./src/config/tag-vocabulary";`);
409
+ }
57
410
  lines.push(``);
58
- lines.push(` // ── Preset-owned fields (content collections, plugins, markdown, …) ────────`);
59
- lines.push(` ...zudoDocPreset({ settings, buildDocsSchema, directiveVocabulary, translations, colorSchemes }),`);
60
- lines.push(`});`);
411
+ lines.push(`export default defineConfig(`);
412
+ lines.push(` zudoDoc({`);
413
+ for (const [key, value] of emittedEntries) {
414
+ lines.push(` ${key}: ${serializeValue(value, 2)},`);
415
+ }
416
+ lines.push(` }),`);
417
+ lines.push(`);`);
61
418
  return lines.join("\n") + "\n";
62
419
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-zudo-doc",
3
- "version": "3.2.0",
3
+ "version": "4.0.0",
4
4
  "description": "Create a new zudo-doc documentation site",
5
5
  "license": "MIT",
6
6
  "author": "Takeshi Takatsudo",
@@ -22,7 +22,7 @@
22
22
  "cli"
23
23
  ],
24
24
  "engines": {
25
- "node": ">=20"
25
+ "node": ">=22"
26
26
  },
27
27
  "publishConfig": {
28
28
  "access": "public",
@@ -0,0 +1,64 @@
1
+ /** @jsxRuntime automatic */
2
+ /** @jsxImportSource preact */
3
+ // Locked manifest (#2653 Decision 4): a SELF-CONTAINED doc-route stub —
4
+ // REQUIRED because the injected DYNAMIC `/docs/[[...slug]]` route 404s in
5
+ // `zfb dev` (real pre-existing gap in zfb's dev-mode dynamic-route rendering,
6
+ // distinct from the `/`-injection gap zfb#1227; empirically confirmed on
7
+ // #2653). This stub reconstructs the doc route from scratch using ONLY the
8
+ // sanctioned package entrypoints — no `pages/lib`, no `@/config`:
9
+ // 1. the `virtual:zudo-doc-route-context` virtual module (serializable
10
+ // settings/translations/tagVocabulary/colorSchemes payload),
11
+ // 2. `@takazudo/zudo-doc/route-context` (`createRouteContext`),
12
+ // 3. `@takazudo/zudo-doc/chrome` (`createChrome`), and
13
+ // 4. `virtual:zudo-doc-chrome-bindings` (the host-callables channel).
14
+ // The bindings import is unconditional: the routes plugin supplies an empty
15
+ // object when `chromeBindingsModule` is unset, while configured projects get
16
+ // their MDX/chrome bindings without editing this stub.
17
+ // Makes `/docs/getting-started/` return 200 in BOTH `zfb dev` and `zfb build`
18
+ // (see the "TM negative guard" case in route-injection-build.slow.test.ts for
19
+ // the no-stub 404 proof this fixes).
20
+ //
21
+ // docHistory note: when the docHistory feature is selected, the generator
22
+ // patches this file to statically import DocHistory from
23
+ // "@takazudo/zudo-doc/doc-history" and merge it over chromeBindings in
24
+ // createChrome's hostBindings (second) argument —
25
+ // DocHistory's chrome-derive default is a no-op stub (unlike
26
+ // DesignTokenPanelBootstrap, which the package auto-defaults), so without
27
+ // that patch the doc-history button never hydrates on this route.
28
+
29
+ import type { JSX } from "preact";
30
+ import { routeContext } from "virtual:zudo-doc-route-context";
31
+ import {
32
+ createRouteContext,
33
+ type RouteContextPayload,
34
+ } from "@takazudo/zudo-doc/route-context";
35
+ import { createChrome } from "@takazudo/zudo-doc/chrome";
36
+ import { chromeBindings } from "virtual:zudo-doc-chrome-bindings";
37
+
38
+ const ctx = routeContext as unknown as RouteContextPayload;
39
+ const routeCtx = createRouteContext(ctx);
40
+ const { renderDocPage } = createChrome(routeCtx, chromeBindings);
41
+
42
+ export const frontmatter = { title: "Docs" };
43
+
44
+ export function paths(): Array<{ params: { slug: string[] }; props: unknown }> {
45
+ const locale = routeCtx.defaultLocale;
46
+ const source = routeCtx.resolveNavSource(locale, undefined);
47
+ return routeCtx.buildDocRouteEntries({
48
+ source,
49
+ locale,
50
+ routeSig: `docs;${locale}`,
51
+ }).map((item) => ({
52
+ params: { slug: item.slugParams },
53
+ props: item.props,
54
+ }));
55
+ }
56
+
57
+ type PageArgs = { params: { slug: string[] } } & Record<string, unknown>;
58
+
59
+ export default function DocsPage(props: PageArgs): JSX.Element {
60
+ return renderDocPage(props as never, {
61
+ locale: routeCtx.defaultLocale,
62
+ docHistoryContentDir: routeCtx.settings.docsDir,
63
+ });
64
+ }
@@ -1,41 +1,6 @@
1
- /** @jsxRuntime automatic */
2
- /** @jsxImportSource preact */
3
- // Page module for the site index route.
4
- //
5
- // Default-locale (EN) site index. Static route — no paths() export needed.
6
- // Hands the resolved locale to the shared `prepareHomeData` factory (#2519)
7
- // — which now owns the nav-tree / tag-count data-prep sequence — and passes
8
- // the result to the shared HomePageView body (epic #2499, S4 #2503).
9
- //
10
- // Data flow:
11
- // routeContext host RouteContext (settings + i18n + nav helpers)
12
- // → prepareHomeData() nav tree, category order, tag count
13
- // → HomePageView renders hero + SiteTreeNav grid + tag section
14
- //
15
- // Thin consumer of `HomePageView` (S3 #2502) and `prepareHomeData` (#2519):
16
- // this file's only job is resolving the default locale — mirroring the
17
- // package route's shape (`packages/zudo-doc/src/routes/index.tsx`). No
18
- // `extras` here — the showcase's `@Takazudo` brand link (#1453) is
19
- // project-specific and is not part of the generated project's hero.
20
-
21
- import { routeContext } from "./lib/_route-context";
22
- import { prepareHomeData } from "@takazudo/zudo-doc/home-page";
23
- import type { JSX } from "preact";
24
- import { HomePageView } from "./lib/_chrome";
25
-
26
- export const frontmatter = { title: "Home" };
27
-
28
- export default function IndexPage(): JSX.Element {
29
- const locale = routeContext.defaultLocale;
30
-
31
- const { tree, categoryOrder, tagCount } = prepareHomeData(routeContext, locale);
32
-
33
- return (
34
- <HomePageView
35
- locale={locale}
36
- tree={tree}
37
- categoryOrder={categoryOrder}
38
- tagCount={tagCount}
39
- />
40
- );
41
- }
1
+ // Locked manifest (epic zudolab/zudo-doc#2651, Decision 4 on #2653): the home
2
+ // route is a 1-line re-export of the package-owned STATIC index route.
3
+ // Verified by the #2652 spike (Q2) to build, dev-render, and hydrate — a
4
+ // dynamic route (see pages/docs/[[...slug]].tsx) cannot use this form because
5
+ // `paths()` static-AST-extraction requires source, not compiled `dist/` JS.
6
+ export { default } from "@takazudo/zudo-doc/routes/index";