create-zudo-doc 3.1.0 → 3.3.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 (127) hide show
  1. package/dist/claude-md-gen.d.ts +8 -0
  2. package/dist/claude-md-gen.js +43 -26
  3. package/dist/compose.d.ts +14 -20
  4. package/dist/compose.js +15 -25
  5. package/dist/features/body-foot-util.d.ts +8 -4
  6. package/dist/features/body-foot-util.js +8 -4
  7. package/dist/features/claude-resources.d.ts +12 -0
  8. package/dist/features/claude-resources.js +13 -6
  9. package/dist/features/design-token-panel.d.ts +0 -12
  10. package/dist/features/design-token-panel.js +32 -93
  11. package/dist/features/doc-history.d.ts +18 -3
  12. package/dist/features/doc-history.js +45 -60
  13. package/dist/features/doc-tags.d.ts +9 -13
  14. package/dist/features/doc-tags.js +10 -26
  15. package/dist/features/dynamic-page-transition.d.ts +19 -30
  16. package/dist/features/dynamic-page-transition.js +21 -209
  17. package/dist/features/footer-taglist.d.ts +1 -1
  18. package/dist/features/footer-taglist.js +1 -1
  19. package/dist/features/footer.d.ts +3 -2
  20. package/dist/features/footer.js +3 -2
  21. package/dist/features/i18n.d.ts +13 -8
  22. package/dist/features/i18n.js +14 -9
  23. package/dist/features/image-enlarge.d.ts +7 -26
  24. package/dist/features/image-enlarge.js +7 -26
  25. package/dist/features/llms-txt.d.ts +3 -5
  26. package/dist/features/llms-txt.js +3 -5
  27. package/dist/features/search.d.ts +7 -3
  28. package/dist/features/search.js +7 -3
  29. package/dist/features/sidebar-resizer.d.ts +4 -8
  30. package/dist/features/sidebar-resizer.js +4 -8
  31. package/dist/features/sidebar-toggle.d.ts +7 -7
  32. package/dist/features/sidebar-toggle.js +7 -7
  33. package/dist/features/tag-governance.d.ts +14 -7
  34. package/dist/features/tag-governance.js +52 -8
  35. package/dist/features/tauri.d.ts +13 -10
  36. package/dist/features/tauri.js +16 -52
  37. package/dist/features/versioning.d.ts +12 -24
  38. package/dist/features/versioning.js +13 -39
  39. package/dist/index.js +2 -3
  40. package/dist/preset.d.ts +1 -0
  41. package/dist/preset.js +5 -0
  42. package/dist/prompts.d.ts +2 -0
  43. package/dist/prompts.js +1 -0
  44. package/dist/scaffold.d.ts +13 -6
  45. package/dist/scaffold.js +78 -59
  46. package/dist/utils.d.ts +10 -0
  47. package/dist/utils.js +14 -0
  48. package/dist/zfb-config-gen.d.ts +32 -20
  49. package/dist/zfb-config-gen.js +400 -53
  50. package/package.json +2 -2
  51. package/templates/base/pages/docs/[[...slug]].tsx +59 -0
  52. package/templates/base/pages/index.tsx +6 -41
  53. package/templates/base/scripts/setup-doc-skill.sh +261 -0
  54. package/templates/base/src/styles/global.css +15 -340
  55. package/templates/base/tsconfig.json +3 -26
  56. package/templates/features/i18n/files/pages/[locale]/docs/[[...slug]].tsx +88 -0
  57. package/templates/features/tagGovernance/files/scripts/tags-suggest.ts +3 -1
  58. package/dist/settings-gen.d.ts +0 -2
  59. package/dist/settings-gen.js +0 -319
  60. package/templates/base/.htmlvalidate.json +0 -5
  61. package/templates/base/.zfb/doc-history-meta.json +0 -1
  62. package/templates/base/pages/_data.ts +0 -160
  63. package/templates/base/pages/lib/_body-end-islands.tsx +0 -165
  64. package/templates/base/pages/lib/_chrome.ts +0 -167
  65. package/templates/base/pages/lib/_details.tsx +0 -29
  66. package/templates/base/pages/lib/_doc-route-entries.ts +0 -10
  67. package/templates/base/pages/lib/_extract-headings.ts +0 -37
  68. package/templates/base/pages/lib/_frontmatter-preview-data.ts +0 -27
  69. package/templates/base/pages/lib/_nav-source-cache.ts +0 -100
  70. package/templates/base/pages/lib/_nav-source-docs.ts +0 -17
  71. package/templates/base/pages/lib/_preset-generator.tsx +0 -81
  72. package/templates/base/pages/lib/_route-context.ts +0 -32
  73. package/templates/base/pages/lib/_search-widget.tsx +0 -17
  74. package/templates/base/pages/lib/doc-page-props.ts +0 -30
  75. package/templates/base/pages/lib/locale-merge.ts +0 -59
  76. package/templates/base/scripts/run-b4push.sh +0 -102
  77. package/templates/base/src/components/ai-chat-modal.tsx +0 -18
  78. package/templates/base/src/components/content/code-group.tsx +0 -3
  79. package/templates/base/src/components/content/content-admonition.tsx +0 -4
  80. package/templates/base/src/components/desktop-sidebar-toggle.tsx +0 -15
  81. package/templates/base/src/components/doc-history.tsx +0 -21
  82. package/templates/base/src/components/image-enlarge.tsx +0 -24
  83. package/templates/base/src/components/preset-generator.tsx +0 -14
  84. package/templates/base/src/components/sidebar-toggle.tsx +0 -6
  85. package/templates/base/src/components/sidebar-tree.tsx +0 -6
  86. package/templates/base/src/config/color-scheme-utils.ts +0 -69
  87. package/templates/base/src/config/color-schemes.ts +0 -165
  88. package/templates/base/src/config/docs-schema.ts +0 -95
  89. package/templates/base/src/config/frontmatter-preview-defaults.ts +0 -27
  90. package/templates/base/src/config/frontmatter-preview-renderers.tsx +0 -46
  91. package/templates/base/src/config/i18n.ts +0 -239
  92. package/templates/base/src/config/settings-types.ts +0 -45
  93. package/templates/base/src/config/sidebars.ts +0 -66
  94. package/templates/base/src/config/tag-vocabulary-types.ts +0 -4
  95. package/templates/base/src/config/tag-vocabulary.ts +0 -20
  96. package/templates/base/src/config/z-index-tokens.ts +0 -128
  97. package/templates/base/src/types/docs-entry.ts +0 -28
  98. package/templates/base/src/types/heading.ts +0 -5
  99. package/templates/base/src/types/locale.ts +0 -10
  100. package/templates/base/src/utils/base.ts +0 -164
  101. package/templates/base/src/utils/docs.ts +0 -446
  102. package/templates/base/src/utils/git-info.ts +0 -70
  103. package/templates/base/src/utils/github.ts +0 -22
  104. package/templates/base/src/utils/nav-scope.ts +0 -34
  105. package/templates/base/src/utils/sidebar.ts +0 -36
  106. package/templates/base/src/utils/slug.ts +0 -10
  107. package/templates/base/src/utils/smart-break.tsx +0 -12
  108. package/templates/base/src/utils/tags.ts +0 -83
  109. package/templates/base/zfb-shim.d.ts +0 -178
  110. package/templates/features/bodyFootUtil/files/src/utils/github.ts +0 -22
  111. package/templates/features/claudeResources/files/src/integrations/claude-resources/__tests__/escape-for-mdx.test.ts +0 -42
  112. package/templates/features/claudeResources/files/src/integrations/claude-resources/__tests__/generate.test.ts +0 -752
  113. package/templates/features/claudeResources/files/src/integrations/claude-resources/escape-for-mdx.ts +0 -97
  114. package/templates/features/claudeResources/files/src/integrations/claude-resources/generate.ts +0 -735
  115. package/templates/features/designTokenPanel/files/src/components/design-token-panel-bootstrap.tsx +0 -15
  116. package/templates/features/designTokenPanel/files/src/config/design-token-panel-config.ts +0 -435
  117. package/templates/features/designTokenPanel/files/src/config/design-tokens-manifest.ts +0 -174
  118. package/templates/features/designTokenPanel/files/src/lib/design-token-panel-bootstrap.ts +0 -30
  119. package/templates/features/docHistory/files/src/components/doc-history.tsx +0 -10
  120. package/templates/features/docHistory/files/src/types/doc-history.ts +0 -7
  121. package/templates/features/dynamicPageTransition/files/src/components/client-router-bootstrap.tsx +0 -72
  122. package/templates/features/i18n/files/pages/[locale]/index.tsx +0 -72
  123. package/templates/features/imageEnlarge/files/src/components/image-enlarge.tsx +0 -11
  124. package/templates/features/sidebarToggle/files/src/components/desktop-sidebar-toggle.tsx +0 -6
  125. package/templates/features/tauri/files/src/components/find-bar.tsx +0 -122
  126. package/templates/features/tauri/files/src/components/find-in-page-init.tsx +0 -59
  127. package/templates/features/tauri/files/src/utils/find-in-page.ts +0 -175
@@ -1,62 +1,409 @@
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, and
7
- * trailingSlash 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
+ siteName: "Docs",
36
+ minifyHtml: true,
37
+ defaultLocale: "en",
38
+ locales: {},
39
+ noindex: false,
40
+ githubUrl: false,
41
+ metaTags: {
42
+ description: true,
43
+ keywords: false,
44
+ ogImage: false,
45
+ ogSiteName: true,
46
+ twitterCard: false,
47
+ },
48
+ docTags: false,
49
+ tagGovernance: "off",
50
+ tagVocabulary: false,
51
+ llmsTxt: false,
52
+ cjkFriendly: false,
53
+ designTokenPanel: false,
54
+ sidebarResizer: false,
55
+ sidebarToggle: false,
56
+ imageEnlarge: false,
57
+ findInPage: false,
58
+ dynamicPageTransition: false,
59
+ docHistory: false,
60
+ bodyFootUtilArea: false,
61
+ versions: false,
62
+ claudeResources: false,
63
+ defaultLocaleOnlyPrefixes: [],
64
+ footer: false,
65
+ headerNav: [],
66
+ headerRightItems: [{ type: "component", component: "theme-toggle" }],
67
+ };
68
+ function raw(code) {
69
+ return { __rawCode: code };
70
+ }
71
+ function isRawCode(value) {
72
+ return (typeof value === "object" &&
73
+ value !== null &&
74
+ "__rawCode" in value);
75
+ }
76
+ function deepEqual(a, b) {
77
+ if (a === b)
78
+ return true;
79
+ if (typeof a !== typeof b)
80
+ return false;
81
+ if (Array.isArray(a) || Array.isArray(b)) {
82
+ if (!Array.isArray(a) || !Array.isArray(b))
83
+ return false;
84
+ if (a.length !== b.length)
85
+ return false;
86
+ return a.every((v, i) => deepEqual(v, b[i]));
87
+ }
88
+ if (a && b && typeof a === "object" && typeof b === "object") {
89
+ const ak = Object.keys(a);
90
+ const bk = Object.keys(b);
91
+ if (ak.length !== bk.length)
92
+ return false;
93
+ return ak.every((k) => deepEqual(a[k], b[k]));
94
+ }
95
+ return false;
96
+ }
97
+ const IDENT_RE = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
98
+ function serializeValue(value, indent) {
99
+ const pad = " ".repeat(indent);
100
+ const padInner = " ".repeat(indent + 1);
101
+ if (isRawCode(value))
102
+ return value.__rawCode;
103
+ if (value === null)
104
+ return "null";
105
+ if (typeof value === "string")
106
+ return JSON.stringify(value);
107
+ if (typeof value === "number" || typeof value === "boolean") {
108
+ return String(value);
109
+ }
110
+ if (Array.isArray(value)) {
111
+ if (value.length === 0)
112
+ return "[]";
113
+ const items = value
114
+ .map((v) => `${padInner}${serializeValue(v, indent + 1)}`)
115
+ .join(",\n");
116
+ return `[\n${items},\n${pad}]`;
117
+ }
118
+ if (typeof value === "object") {
119
+ const entries = Object.entries(value).filter(([, v]) => v !== undefined);
120
+ if (entries.length === 0)
121
+ return "{}";
122
+ const items = entries
123
+ .map(([k, v]) => {
124
+ const key = IDENT_RE.test(k) ? k : JSON.stringify(k);
125
+ return `${padInner}${key}: ${serializeValue(v, indent + 1)}`;
126
+ })
127
+ .join(",\n");
128
+ return `{\n${items},\n${pad}}`;
129
+ }
130
+ throw new Error(`zfb-config-gen: cannot serialize value ${String(value)}`);
131
+ }
132
+ // ---------------------------------------------------------------------------
133
+ // Desired-config builder — mirrors every user-choice → settings mapping the
134
+ // old settings-gen.ts had, but as a plain object instead of emitted lines.
135
+ // ---------------------------------------------------------------------------
136
+ function buildDesiredConfig(choices) {
137
+ const desired = {};
138
+ // siteName is ALWAYS emitted (locked spec — the one field you almost
139
+ // always set, and the clearest anchor in a near-empty config file).
140
+ desired.siteName = capitalize(choices.projectName.replace(/-/g, " "));
141
+ // ── Color scheme ──────────────────────────────────────────────────────
142
+ if (choices.colorSchemeMode === "single") {
143
+ desired.colorScheme = choices.singleScheme ?? "Default Dark";
144
+ desired.colorMode = false;
145
+ }
146
+ else {
147
+ desired.colorScheme = choices.darkScheme ?? "Default Dark";
148
+ desired.colorMode = {
149
+ defaultMode: choices.defaultMode ?? "dark",
150
+ lightScheme: choices.lightScheme ?? "Default Light",
151
+ darkScheme: choices.darkScheme ?? "Default Dark",
152
+ respectPrefersColorScheme: choices.respectPrefersColorScheme ?? true,
153
+ };
154
+ }
155
+ // ── i18n ──────────────────────────────────────────────────────────────
156
+ desired.defaultLocale = choices.defaultLang ?? "en";
157
+ if (choices.features.includes("i18n")) {
158
+ const secondaryLang = getSecondaryLang(choices.defaultLang);
159
+ desired.locales = {
160
+ [secondaryLang]: {
161
+ label: getLangLabel(secondaryLang),
162
+ dir: `src/content/docs-${secondaryLang}`,
163
+ },
164
+ };
165
+ }
166
+ else {
167
+ desired.locales = {};
168
+ }
169
+ // ── Misc site fields ──────────────────────────────────────────────────
170
+ desired.minifyHtml = choices.minifyHtml ?? true;
171
+ desired.noindex = choices.features.includes("noindex");
172
+ const rawGithubUrl = (choices.githubUrl ?? "").trim();
173
+ desired.githubUrl = rawGithubUrl ? rawGithubUrl : false;
174
+ desired.cjkFriendly = choices.cjkFriendly ?? false;
175
+ // ── Meta tags ─────────────────────────────────────────────────────────
176
+ if (choices.metaTags) {
177
+ const mt = choices.metaTags;
178
+ desired.metaTags = {
179
+ description: mt.description !== undefined ? mt.description : true,
180
+ keywords: mt.keywords !== undefined ? mt.keywords : false,
181
+ ogImage: mt.ogImage !== undefined ? mt.ogImage : false,
182
+ ogSiteName: mt.ogSiteName !== undefined ? mt.ogSiteName : true,
183
+ ...(mt.twitterCard
184
+ ? {
185
+ twitterCard: mt.twitterCard,
186
+ ...(mt.twitterSite ? { twitterSite: mt.twitterSite } : {}),
187
+ ...(mt.twitterCreator
188
+ ? { twitterCreator: mt.twitterCreator }
189
+ : {}),
190
+ }
191
+ : { twitterCard: false }),
192
+ };
193
+ }
194
+ // ── Tags / docs ───────────────────────────────────────────────────────
195
+ desired.docTags = choices.features.includes("docTags");
196
+ if (choices.features.includes("tagGovernance")) {
197
+ desired.tagGovernance = "warn";
198
+ desired.tagVocabulary = true;
199
+ // Wired to the src/config/tag-vocabulary.ts starter file the
200
+ // tagGovernance feature module emits (single source of truth shared
201
+ // with scripts/tags-audit.ts and scripts/tags-suggest.ts).
202
+ desired.tagVocabularyEntries = raw("tagVocabulary");
203
+ }
204
+ else {
205
+ desired.tagGovernance = "off";
206
+ desired.tagVocabulary = false;
207
+ }
208
+ desired.llmsTxt = choices.features.includes("llmsTxt");
209
+ // ── Feature toggles ───────────────────────────────────────────────────
210
+ desired.designTokenPanel = choices.features.includes("designTokenPanel");
211
+ desired.sidebarResizer = choices.features.includes("sidebarResizer");
212
+ desired.sidebarToggle = choices.features.includes("sidebarToggle");
213
+ desired.imageEnlarge = choices.features.includes("imageEnlarge");
214
+ // findInPage rides the existing tauri feature (#2690) — the Cmd/Ctrl+F find
215
+ // bar only makes sense inside the Tauri desktop shell, so it has no CLI
216
+ // flag or prompt of its own; there is no separate "findInPage" feature
217
+ // module.
218
+ desired.findInPage = choices.features.includes("tauri");
219
+ desired.dynamicPageTransition = choices.features.includes("dynamicPageTransition");
220
+ desired.docHistory = choices.features.includes("docHistory");
221
+ if (choices.features.includes("bodyFootUtil")) {
222
+ desired.bodyFootUtilArea = {
223
+ docHistory: choices.features.includes("docHistory"),
224
+ viewSourceLink: Boolean(rawGithubUrl),
225
+ };
226
+ }
227
+ else {
228
+ desired.bodyFootUtilArea = false;
229
+ }
230
+ desired.versions = choices.features.includes("versioning") ? [] : false;
231
+ if (choices.features.includes("claudeResources")) {
232
+ desired.claudeResources = { claudeDir: ".claude" };
233
+ desired.defaultLocaleOnlyPrefixes = [
234
+ "/docs/claude-md/",
235
+ "/docs/claude-skills/",
236
+ "/docs/claude-agents/",
237
+ "/docs/claude-commands/",
238
+ ];
239
+ }
240
+ else {
241
+ desired.claudeResources = false;
242
+ desired.defaultLocaleOnlyPrefixes = [];
243
+ }
244
+ // ── Footer ────────────────────────────────────────────────────────────
245
+ if (choices.features.includes("footerNavGroup") ||
246
+ choices.features.includes("footerCopyright") ||
247
+ choices.features.includes("footerTaglist")) {
248
+ const footer = {
249
+ links: choices.features.includes("footerNavGroup")
250
+ ? [
251
+ {
252
+ title: "Docs",
253
+ items: [
254
+ { label: "Getting Started", href: "/docs/getting-started" },
255
+ ],
256
+ },
257
+ ]
258
+ : [],
259
+ };
260
+ if (choices.features.includes("footerCopyright")) {
261
+ footer.copyright = `Copyright © ${new Date().getFullYear()} Your Name. Built with zudo-doc.`;
262
+ }
263
+ if (choices.features.includes("footerTaglist")) {
264
+ footer.taglist = { enabled: true, groupBy: "group" };
265
+ }
266
+ desired.footer = footer;
267
+ }
268
+ else {
269
+ desired.footer = false;
270
+ }
271
+ // ── Header nav / header-right ────────────────────────────────────────
272
+ const headerNav = [
273
+ {
274
+ label: "Getting Started",
275
+ path: "/docs/getting-started",
276
+ categoryMatch: "getting-started",
277
+ },
278
+ ];
279
+ // The "claude" categoryMatch is load-bearing beyond the header link — see
280
+ // the old settings-gen.ts note (preserved): getCategoryOrder() derives the
281
+ // satellite-grouping prefixes from headerNav.
282
+ if (choices.features.includes("claudeResources")) {
283
+ headerNav.push({
284
+ label: "Claude",
285
+ path: "/docs/claude",
286
+ categoryMatch: "claude",
287
+ });
288
+ }
289
+ if (choices.features.includes("changelog")) {
290
+ headerNav.push({
291
+ label: "Changelog",
292
+ path: "/docs/changelog",
293
+ categoryMatch: "changelog",
294
+ });
295
+ }
296
+ desired.headerNav = headerNav;
297
+ if (choices.headerRightItems !== undefined) {
298
+ // User-supplied override (including empty array) — emit verbatim, minus
299
+ // a defensive strip of "design-token-panel" when the feature is off.
300
+ desired.headerRightItems = choices.headerRightItems.filter((item) => !(item.type === "trigger" &&
301
+ item.trigger === "design-token-panel" &&
302
+ !choices.features.includes("designTokenPanel")));
303
+ }
304
+ else {
305
+ const items = [];
306
+ if (choices.features.includes("designTokenPanel")) {
307
+ items.push({ type: "trigger", trigger: "design-token-panel" });
308
+ }
309
+ if (choices.features.includes("versioning")) {
310
+ items.push({ type: "component", component: "version-switcher" });
311
+ }
312
+ items.push({ type: "component", component: "github-link" });
313
+ items.push({ type: "component", component: "theme-toggle" });
314
+ if (choices.features.includes("search")) {
315
+ items.push({ type: "component", component: "search" });
316
+ }
317
+ if (choices.features.includes("i18n")) {
318
+ items.push({ type: "component", component: "language-switcher" });
319
+ }
320
+ desired.headerRightItems = items;
321
+ }
322
+ return desired;
323
+ }
324
+ // ---------------------------------------------------------------------------
325
+ // Field order — purely cosmetic (mirrors ZudoDocConfig's declaration order
326
+ // in packages/zudo-doc/src/config.ts so the emitted file reads like the
327
+ // documented reference). Any key produced by buildDesiredConfig() that is
328
+ // missing here is appended AFTER the ordered ones in ALPHABETICAL order (see
329
+ // orderDesiredKeys) — defensive: a newly-added desired key someone forgot to
330
+ // list here still emits deterministically instead of being silently dropped.
331
+ // ---------------------------------------------------------------------------
332
+ const FIELD_ORDER = [
333
+ "colorScheme",
334
+ "colorMode",
335
+ "siteName",
336
+ "defaultLocale",
337
+ "locales",
338
+ "noindex",
339
+ "githubUrl",
340
+ "metaTags",
341
+ "docTags",
342
+ "tagGovernance",
343
+ "tagVocabulary",
344
+ "tagVocabularyEntries",
345
+ "llmsTxt",
346
+ "cjkFriendly",
347
+ "designTokenPanel",
348
+ "sidebarResizer",
349
+ "sidebarToggle",
350
+ "imageEnlarge",
351
+ "findInPage",
352
+ "dynamicPageTransition",
353
+ "docHistory",
354
+ "bodyFootUtilArea",
355
+ "versions",
356
+ "claudeResources",
357
+ "defaultLocaleOnlyPrefixes",
358
+ "footer",
359
+ "headerNav",
360
+ "headerRightItems",
361
+ "minifyHtml",
362
+ ];
363
+ /**
364
+ * Deterministic emission order for the keys of a `desired` config object:
365
+ * the keys present in `FIELD_ORDER` first (cosmetic reference order), then any
366
+ * leftover keys NOT in `FIELD_ORDER` appended in ALPHABETICAL order. The
367
+ * leftover branch exists so a key `buildDesiredConfig()` produces but nobody
368
+ * added to `FIELD_ORDER` is still emitted (deterministically) rather than
369
+ * silently dropped — matching what the `FIELD_ORDER` comment promises.
370
+ */
371
+ export function orderDesiredKeys(desiredKeys) {
372
+ const inOrder = FIELD_ORDER.filter((k) => desiredKeys.includes(k));
373
+ const leftover = desiredKeys
374
+ .filter((k) => !FIELD_ORDER.includes(k))
375
+ .sort();
376
+ return [...inOrder, ...leftover];
377
+ }
378
+ /**
379
+ * Generate the full `zfb.config.ts` source: `defineConfig(zudoDoc({ ... }))`
380
+ * with only the diff-from-defaults fields.
381
+ */
382
+ export function generateZfbConfig(choices) {
383
+ const desired = buildDesiredConfig(choices);
384
+ const emittedEntries = [];
385
+ for (const key of orderDesiredKeys(Object.keys(desired))) {
386
+ const value = desired[key];
387
+ // siteName always emitted (locked spec); everything else diff-from-default.
388
+ // A leftover key not in DEFAULT_MIRROR has `DEFAULT_MIRROR[key] ===
389
+ // undefined`, so it never deep-equals a real value and always emits.
390
+ if (key !== "siteName" && deepEqual(value, DEFAULT_MIRROR[key]))
391
+ continue;
392
+ emittedEntries.push([key, value]);
393
+ }
25
394
  const lines = [];
26
- // --- Imports ---
27
395
  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,`);
396
+ lines.push(`import { zudoDoc } from "@takazudo/zudo-doc/config";`);
397
+ if (choices.features.includes("tagGovernance")) {
398
+ lines.push(`import { tagVocabulary } from "./src/config/tag-vocabulary";`);
399
+ }
57
400
  lines.push(``);
58
- lines.push(` // ── Preset-owned fields (content collections, plugins, markdown, …) ────────`);
59
- lines.push(` ...zudoDocPreset({ settings, buildDocsSchema, directiveVocabulary, translations, colorSchemes }),`);
60
- lines.push(`});`);
401
+ lines.push(`export default defineConfig(`);
402
+ lines.push(` zudoDoc({`);
403
+ for (const [key, value] of emittedEntries) {
404
+ lines.push(` ${key}: ${serializeValue(value, 2)},`);
405
+ }
406
+ lines.push(` }),`);
407
+ lines.push(`);`);
61
408
  return lines.join("\n") + "\n";
62
409
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-zudo-doc",
3
- "version": "3.1.0",
3
+ "version": "3.3.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,59 @@
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
+ // three 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`), and
12
+ // 3. `@takazudo/zudo-doc/chrome` (`createChrome`).
13
+ // Makes `/docs/getting-started/` return 200 in BOTH `zfb dev` and `zfb build`
14
+ // (see the "TM negative guard" case in route-injection-build.slow.test.ts for
15
+ // the no-stub 404 proof this fixes).
16
+ //
17
+ // docHistory note: when the docHistory feature is selected, the generator
18
+ // patches this file to statically import DocHistory from
19
+ // "@takazudo/zudo-doc/doc-history" and pass it to createChrome's hostBindings
20
+ // (second) argument via defineChromeBindings({ DocHistory }) —
21
+ // DocHistory's chrome-derive default is a no-op stub (unlike
22
+ // DesignTokenPanelBootstrap, which the package auto-defaults), so without
23
+ // that patch the doc-history button never hydrates on this route.
24
+
25
+ import type { JSX } from "preact";
26
+ import { routeContext } from "virtual:zudo-doc-route-context";
27
+ import {
28
+ createRouteContext,
29
+ type RouteContextPayload,
30
+ } from "@takazudo/zudo-doc/route-context";
31
+ import { createChrome } from "@takazudo/zudo-doc/chrome";
32
+
33
+ const ctx = routeContext as unknown as RouteContextPayload;
34
+ const routeCtx = createRouteContext(ctx);
35
+ const { renderDocPage } = createChrome(routeCtx);
36
+
37
+ export const frontmatter = { title: "Docs" };
38
+
39
+ export function paths(): Array<{ params: { slug: string[] }; props: unknown }> {
40
+ const locale = routeCtx.defaultLocale;
41
+ const source = routeCtx.resolveNavSource(locale, undefined);
42
+ return routeCtx.buildDocRouteEntries({
43
+ source,
44
+ locale,
45
+ routeSig: `docs;${locale}`,
46
+ }).map((item) => ({
47
+ params: { slug: item.slugParams },
48
+ props: item.props,
49
+ }));
50
+ }
51
+
52
+ type PageArgs = { params: { slug: string[] } } & Record<string, unknown>;
53
+
54
+ export default function DocsPage(props: PageArgs): JSX.Element {
55
+ return renderDocPage(props as never, {
56
+ locale: routeCtx.defaultLocale,
57
+ docHistoryContentDir: routeCtx.settings.docsDir,
58
+ });
59
+ }
@@ -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";