create-zudo-doc 0.2.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api.js +4 -1
- package/dist/cli.js +4 -6
- package/dist/compose.d.ts +2 -3
- package/dist/compose.js +7 -4
- package/dist/features/tauri.d.ts +10 -5
- package/dist/features/tauri.js +49 -6
- package/dist/preset.js +11 -0
- package/dist/prompts.js +2 -6
- package/dist/scaffold.js +15 -9
- package/dist/settings-gen.js +9 -6
- package/dist/utils.d.ts +8 -0
- package/dist/utils.js +25 -0
- package/dist/zfb-config-gen.js +11 -50
- package/package.json +1 -1
- package/templates/base/pages/_data.ts +10 -23
- package/templates/base/pages/docs/[[...slug]].tsx +27 -168
- package/templates/base/pages/lib/_body-end-islands.tsx +3 -0
- package/templates/base/pages/lib/_doc-content-header.tsx +24 -4
- package/templates/base/pages/lib/_doc-history-area.tsx +21 -5
- package/templates/base/pages/lib/_doc-metainfo-area.tsx +22 -2
- package/templates/base/pages/lib/_doc-page-renderer.tsx +192 -0
- package/templates/base/pages/lib/_doc-page-shell.tsx +3 -2
- package/templates/base/pages/lib/_doc-route-entries.ts +188 -0
- package/templates/base/pages/lib/_doc-tags-area.tsx +7 -2
- package/templates/base/pages/lib/_footer-with-defaults.tsx +38 -27
- package/templates/base/pages/lib/_head-with-defaults.tsx +7 -10
- package/templates/base/pages/lib/_header-with-defaults.tsx +54 -89
- package/templates/base/pages/lib/_inline-version-switcher.tsx +5 -4
- package/templates/base/pages/lib/_nav-data-prep.ts +137 -0
- package/templates/base/pages/lib/_nav-source-docs.ts +10 -6
- package/templates/base/pages/lib/_search-widget-script.ts +32 -9
- package/templates/base/pages/lib/_sidebar-with-defaults.tsx +15 -60
- package/templates/base/pages/lib/locale-merge.ts +1 -1
- package/templates/base/pages/lib/route-enumerators.ts +11 -7
- package/templates/base/plugins/connect-adapter.mjs +30 -1
- package/templates/base/plugins/copy-public-plugin.mjs +10 -2
- package/templates/base/plugins/search-index-plugin.mjs +20 -8
- package/templates/base/src/components/ai-chat-modal.tsx +2 -0
- package/templates/base/src/components/doc-history.tsx +2 -0
- package/templates/base/src/components/image-enlarge.tsx +2 -0
- package/templates/base/src/components/sidebar-toggle.tsx +1 -1
- package/templates/base/src/components/sidebar-tree.tsx +11 -5
- package/templates/base/src/components/theme-toggle.tsx +18 -102
- package/templates/base/src/config/color-schemes.ts +4 -0
- package/templates/base/src/config/docs-schema.ts +94 -0
- package/templates/base/src/config/i18n.ts +10 -3
- package/templates/base/src/styles/global.css +14 -0
- package/templates/base/src/types/docs-entry.ts +8 -26
- package/templates/base/src/utils/base.ts +5 -3
- package/templates/base/src/utils/docs.ts +144 -169
- package/templates/base/zfb-shim.d.ts +167 -0
- package/templates/features/claudeResources/files/plugins/claude-resources-plugin.mjs +20 -110
- package/templates/features/claudeResources/files/src/integrations/claude-resources/generate.ts +62 -38
- package/templates/features/designTokenPanel/files/src/config/design-token-panel-config.ts +34 -8
- package/templates/features/docHistory/files/plugins/doc-history-plugin.mjs +27 -45
- package/templates/features/docHistory/files/src/components/doc-history.tsx +30 -8
- package/templates/features/docTags/files/pages/[locale]/docs/tags/[tag].tsx +6 -74
- package/templates/features/docTags/files/pages/[locale]/docs/tags/index.tsx +6 -77
- package/templates/features/docTags/files/pages/docs/tags/[tag].tsx +7 -69
- package/templates/features/docTags/files/pages/docs/tags/index.tsx +6 -76
- package/templates/features/docTags/files/pages/lib/_tag-pages.tsx +201 -0
- package/templates/features/i18n/files/pages/[locale]/docs/[[...slug]].tsx +41 -179
- package/templates/features/i18n/files/pages/[locale]/index.tsx +5 -5
- package/templates/features/imageEnlarge/files/src/components/image-enlarge.tsx +2 -0
- package/templates/features/llmsTxt/files/plugins/llms-txt-plugin.mjs +33 -21
- package/templates/features/sidebarToggle/files/src/components/desktop-sidebar-toggle.tsx +1 -1
- package/templates/features/tauri/files/src/components/find-in-page-init.tsx +9 -3
- package/templates/features/versioning/files/pages/[locale]/docs/versions.tsx +5 -59
- package/templates/features/versioning/files/pages/docs/versions.tsx +8 -66
- package/templates/features/versioning/files/pages/lib/_versions-page.tsx +79 -0
- package/templates/features/versioning/files/pages/v/[version]/[locale]/docs/[[...slug]].tsx +46 -191
- package/templates/features/versioning/files/pages/v/[version]/docs/[[...slug]].tsx +31 -173
- package/templates/base/src/components/content/heading-h3.tsx +0 -20
- package/templates/base/src/hooks/use-active-heading.ts +0 -133
- package/templates/base/src/plugins/docs-source-map.ts +0 -103
- package/templates/base/src/plugins/hast-utils.ts +0 -10
- package/templates/base/src/plugins/rehype-code-title.ts +0 -50
- package/templates/base/src/plugins/rehype-heading-links.ts +0 -53
- package/templates/base/src/plugins/rehype-mermaid.ts +0 -41
- package/templates/base/src/plugins/url-utils.ts +0 -4
- package/templates/base/src/utils/dedent.ts +0 -24
- package/templates/features/docHistory/files/src/utils/doc-history.ts +0 -180
- package/templates/features/sidebarResizer/files/src/scripts/sidebar-resizer.ts +0 -198
package/dist/api.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import path from "path";
|
|
2
2
|
import { scaffold } from "./scaffold.js";
|
|
3
|
-
import { installDependencies } from "./utils.js";
|
|
3
|
+
import { installDependencies, validateProjectName } from "./utils.js";
|
|
4
4
|
export async function createZudoDoc(options) {
|
|
5
5
|
const { install = false, ...rest } = options;
|
|
6
|
+
const nameError = validateProjectName(rest.projectName);
|
|
7
|
+
if (nameError)
|
|
8
|
+
throw new Error(`Invalid projectName: ${nameError}`);
|
|
6
9
|
const choices = { ...rest, defaultLang: rest.defaultLang ?? "en" };
|
|
7
10
|
await scaffold(choices);
|
|
8
11
|
const targetDir = path.resolve(process.cwd(), choices.projectName);
|
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import minimist from "minimist";
|
|
2
2
|
import pc from "picocolors";
|
|
3
3
|
import { FEATURES, SINGLE_SCHEMES, SUPPORTED_LANGS } from "./constants.js";
|
|
4
|
+
import { validateProjectName } from "./utils.js";
|
|
4
5
|
export function parseArgs(argv = process.argv.slice(2)) {
|
|
5
6
|
const raw = minimist(argv, {
|
|
6
7
|
string: [
|
|
@@ -146,12 +147,9 @@ export function validateArgs(args) {
|
|
|
146
147
|
return `--scheme is only valid with --color-scheme-mode single`;
|
|
147
148
|
}
|
|
148
149
|
if (args.name) {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
if (/[<>:"|?*\\]/.test(args.name)) {
|
|
153
|
-
return "Project name contains invalid characters";
|
|
154
|
-
}
|
|
150
|
+
const nameError = validateProjectName(args.name);
|
|
151
|
+
if (nameError)
|
|
152
|
+
return nameError;
|
|
155
153
|
}
|
|
156
154
|
return null;
|
|
157
155
|
}
|
package/dist/compose.d.ts
CHANGED
|
@@ -78,9 +78,8 @@ export declare function validateDependencies(features: FeatureDefinition[], allS
|
|
|
78
78
|
* - `pages/_mdx-components.ts` — image-enlarge.ts injects the
|
|
79
79
|
* EnlargeableParagraph p-override (ENLARGE_SVG, EnlargeableParagraph def,
|
|
80
80
|
* `p:` map entry) when imageEnlarge is enabled.
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
* compatibility.
|
|
81
|
+
* - `pages/lib/_body-end-islands.tsx` — tauri.ts injects the FindInPageInit
|
|
82
|
+
* island (import, displayName, Island mount) when tauri is enabled.
|
|
84
83
|
*/
|
|
85
84
|
export declare const ANCHOR_FILES: string[];
|
|
86
85
|
/**
|
package/dist/compose.js
CHANGED
|
@@ -164,11 +164,14 @@ export function validateDependencies(features, allSelectedNames) {
|
|
|
164
164
|
* - `pages/_mdx-components.ts` — image-enlarge.ts injects the
|
|
165
165
|
* EnlargeableParagraph p-override (ENLARGE_SVG, EnlargeableParagraph def,
|
|
166
166
|
* `p:` map entry) when imageEnlarge is enabled.
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
* compatibility.
|
|
167
|
+
* - `pages/lib/_body-end-islands.tsx` — tauri.ts injects the FindInPageInit
|
|
168
|
+
* island (import, displayName, Island mount) when tauri is enabled.
|
|
170
169
|
*/
|
|
171
|
-
export const ANCHOR_FILES = [
|
|
170
|
+
export const ANCHOR_FILES = [
|
|
171
|
+
"src/styles/global.css",
|
|
172
|
+
"pages/_mdx-components.ts",
|
|
173
|
+
"pages/lib/_body-end-islands.tsx",
|
|
174
|
+
];
|
|
172
175
|
/**
|
|
173
176
|
* Main composition entry point. Orchestrates the full feature composition
|
|
174
177
|
* pipeline for a generated project.
|
package/dist/features/tauri.d.ts
CHANGED
|
@@ -2,10 +2,15 @@ import type { FeatureModule } from "../compose.js";
|
|
|
2
2
|
/**
|
|
3
3
|
* Tauri feature.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
5
|
+
* #2052: the FindInPageInit island (Cmd/Ctrl+F find bar for the Tauri
|
|
6
|
+
* WebView, where the browser-native find UI is unavailable) is wired into
|
|
7
|
+
* `pages/lib/_body-end-islands.tsx` via the three injections below — import,
|
|
8
|
+
* displayName, and Island mount. zfb's island scanner only registers
|
|
9
|
+
* components reachable through static import chains (page → wrapper →
|
|
10
|
+
* component), so without this injection the feature-copied component files
|
|
11
|
+
* are orphaned dead code that never hydrates. The find-match highlight CSS
|
|
12
|
+
* is unconditional in `templates/base/src/styles/global.css` (matches host);
|
|
13
|
+
* the component runtime-gates itself (renders null unless
|
|
14
|
+
* `window.__TAURI_INTERNALS__` exists), so no settings field is needed.
|
|
10
15
|
*/
|
|
11
16
|
export declare const tauriFeature: FeatureModule;
|
package/dist/features/tauri.js
CHANGED
|
@@ -3,15 +3,58 @@ import path from "path";
|
|
|
3
3
|
/**
|
|
4
4
|
* Tauri feature.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
6
|
+
* #2052: the FindInPageInit island (Cmd/Ctrl+F find bar for the Tauri
|
|
7
|
+
* WebView, where the browser-native find UI is unavailable) is wired into
|
|
8
|
+
* `pages/lib/_body-end-islands.tsx` via the three injections below — import,
|
|
9
|
+
* displayName, and Island mount. zfb's island scanner only registers
|
|
10
|
+
* components reachable through static import chains (page → wrapper →
|
|
11
|
+
* component), so without this injection the feature-copied component files
|
|
12
|
+
* are orphaned dead code that never hydrates. The find-match highlight CSS
|
|
13
|
+
* is unconditional in `templates/base/src/styles/global.css` (matches host);
|
|
14
|
+
* the component runtime-gates itself (renders null unless
|
|
15
|
+
* `window.__TAURI_INTERNALS__` exists), so no settings field is needed.
|
|
11
16
|
*/
|
|
12
17
|
export const tauriFeature = (choices) => ({
|
|
13
18
|
name: "tauri",
|
|
14
|
-
injections: [
|
|
19
|
+
injections: [
|
|
20
|
+
// 1. Import the island entry. Inserted AFTER the
|
|
21
|
+
// `// @slot:body-end-islands:imports` anchor.
|
|
22
|
+
{
|
|
23
|
+
file: "pages/lib/_body-end-islands.tsx",
|
|
24
|
+
anchor: "// @slot:body-end-islands:imports",
|
|
25
|
+
position: "after",
|
|
26
|
+
content: `import FindInPageInit from "@/components/find-in-page-init";`,
|
|
27
|
+
},
|
|
28
|
+
// 2. Stable island marker name (same belt-and-braces guard as the
|
|
29
|
+
// sibling islands in the file). Inserted AFTER the
|
|
30
|
+
// `// @slot:body-end-islands:display-names` anchor.
|
|
31
|
+
{
|
|
32
|
+
file: "pages/lib/_body-end-islands.tsx",
|
|
33
|
+
anchor: "// @slot:body-end-islands:display-names",
|
|
34
|
+
position: "after",
|
|
35
|
+
content: `(FindInPageInit as { displayName?: string }).displayName = "FindInPageInit";`,
|
|
36
|
+
},
|
|
37
|
+
// 3. Island mount. Inserted AFTER the
|
|
38
|
+
// `{/* @slot:body-end-islands:extra-islands */}` anchor.
|
|
39
|
+
// when="load" (not "idle"): the island's job is to intercept
|
|
40
|
+
// Cmd/Ctrl+F via a keydown listener, so it must hydrate as soon as
|
|
41
|
+
// the islands runtime mounts — same rationale as the
|
|
42
|
+
// clientRouterBootstrap click intercept above it. Deferring to idle
|
|
43
|
+
// would leave a post-load window where Cmd+F does nothing, which is
|
|
44
|
+
// the very bug this injection fixes.
|
|
45
|
+
{
|
|
46
|
+
file: "pages/lib/_body-end-islands.tsx",
|
|
47
|
+
anchor: "{/* @slot:body-end-islands:extra-islands */}",
|
|
48
|
+
position: "after",
|
|
49
|
+
content: ` {/* Tauri-only find-in-page (Cmd/Ctrl+F) bar. Renders null outside
|
|
50
|
+
a Tauri WebView, so the island is inert in plain browser builds
|
|
51
|
+
of the same scaffold. */}
|
|
52
|
+
{Island({
|
|
53
|
+
when: "load",
|
|
54
|
+
children: <FindInPageInit />,
|
|
55
|
+
}) as unknown as VNode}`,
|
|
56
|
+
},
|
|
57
|
+
],
|
|
15
58
|
postProcess: async (targetDir) => {
|
|
16
59
|
// Patch Cargo.toml package name
|
|
17
60
|
const cargoPath = path.join(targetDir, "src-tauri/Cargo.toml");
|
package/dist/preset.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs from "fs";
|
|
2
2
|
import { FEATURES, SINGLE_SCHEMES, SUPPORTED_LANGS } from "./constants.js";
|
|
3
|
+
import { validateProjectName } from "./utils.js";
|
|
3
4
|
const VALID_HEADER_RIGHT_COMPONENTS = new Set([
|
|
4
5
|
"theme-toggle",
|
|
5
6
|
"language-switcher",
|
|
@@ -34,6 +35,16 @@ export function validatePreset(json) {
|
|
|
34
35
|
return "Preset must be a JSON object";
|
|
35
36
|
}
|
|
36
37
|
const p = json;
|
|
38
|
+
if (p.projectName !== undefined) {
|
|
39
|
+
// Untyped JSON — guard the type before the string validator, otherwise
|
|
40
|
+
// RegExp.test would coerce numbers/booleans and could accept them.
|
|
41
|
+
if (typeof p.projectName !== "string") {
|
|
42
|
+
return "Invalid projectName: must be a string";
|
|
43
|
+
}
|
|
44
|
+
const nameError = validateProjectName(p.projectName);
|
|
45
|
+
if (nameError)
|
|
46
|
+
return `Invalid projectName: ${nameError}`;
|
|
47
|
+
}
|
|
37
48
|
if (p.features !== undefined && !Array.isArray(p.features)) {
|
|
38
49
|
return `"features" must be an array in preset`;
|
|
39
50
|
}
|
package/dist/prompts.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as p from "@clack/prompts";
|
|
2
2
|
import { LIGHT_DARK_PAIRINGS, SINGLE_SCHEMES, FEATURES, SUPPORTED_LANGS } from "./constants.js";
|
|
3
|
+
import { validateProjectName } from "./utils.js";
|
|
3
4
|
export async function runPrompts(prefilled = {}) {
|
|
4
5
|
// 1. Project name
|
|
5
6
|
let projectName;
|
|
@@ -12,12 +13,7 @@ export async function runPrompts(prefilled = {}) {
|
|
|
12
13
|
placeholder: "my-docs",
|
|
13
14
|
defaultValue: "my-docs",
|
|
14
15
|
validate(value) {
|
|
15
|
-
|
|
16
|
-
return "Project name is required";
|
|
17
|
-
if (/^[./]|\.\./.test(value))
|
|
18
|
-
return "Project name must not contain path traversal characters";
|
|
19
|
-
if (/[<>:"|?*\\]/.test(value))
|
|
20
|
-
return "Project name contains invalid characters";
|
|
16
|
+
return validateProjectName(value) ?? undefined;
|
|
21
17
|
},
|
|
22
18
|
});
|
|
23
19
|
if (p.isCancel(result))
|
package/dist/scaffold.js
CHANGED
|
@@ -264,16 +264,21 @@ function generatePackageJson(choices) {
|
|
|
264
264
|
// next.35 fixes resolve_links rewriting bare same-page `[text](#anchor)` /
|
|
265
265
|
// `[text](?query)` links to `/<parent-dir>/#anchor` (zudolab/zudo-doc#1948,
|
|
266
266
|
// upstream Takazudo/zudo-front-builder#875).
|
|
267
|
-
|
|
268
|
-
"
|
|
267
|
+
// next.38 adds client scripts (`.client.*` + `clientScript()`), the
|
|
268
|
+
// `when="media"` island strategy, exported VNode types, and stricter
|
|
269
|
+
// cross-file anchor validation. BREAKING upstream: the no-op
|
|
270
|
+
// `linkValidation.allowExternal` knob was removed — neither the host nor
|
|
271
|
+
// the generated config ever emitted it, so no migration is needed here.
|
|
272
|
+
"@takazudo/zfb": "0.1.0-next.38",
|
|
273
|
+
"@takazudo/zfb-runtime": "0.1.0-next.38",
|
|
269
274
|
// zfb-adapter-cloudflare — required for any route with `prerender = false`.
|
|
270
275
|
// Pinned in lockstep with @takazudo/zfb.
|
|
271
|
-
"@takazudo/zfb-adapter-cloudflare": "0.1.0-next.
|
|
276
|
+
"@takazudo/zfb-adapter-cloudflare": "0.1.0-next.38",
|
|
272
277
|
// @takazudo/zudo-doc — published from this monorepo via
|
|
273
278
|
// .github/workflows/publish-zudo-doc.yml. The pin here is bumped in
|
|
274
279
|
// lockstep by scripts/release-create-zudo-doc.sh whenever zudo-doc's
|
|
275
280
|
// version moves, so a fresh scaffold pulls the version we just published.
|
|
276
|
-
"@takazudo/zudo-doc": "^0.2.
|
|
281
|
+
"@takazudo/zudo-doc": "^0.2.2",
|
|
277
282
|
// zod — used by the generated zfb.config.ts. zfb-config-gen emits
|
|
278
283
|
// `import { z } from "zod"` for the content-collection schema +
|
|
279
284
|
// `z.toJSONSchema(...)` conversion. Without this dep, the consumer
|
|
@@ -297,11 +302,9 @@ function generatePackageJson(choices) {
|
|
|
297
302
|
"@shikijs/transformers": "^4.0.0",
|
|
298
303
|
clsx: "^2.1.0",
|
|
299
304
|
"gray-matter": "^4.0.0",
|
|
300
|
-
"github-slugger": "^2.0.0",
|
|
301
305
|
mermaid: "^11.12.3",
|
|
302
306
|
"remark-cjk-friendly": "^2.0.1",
|
|
303
307
|
"remark-directive": "^3.0.0",
|
|
304
|
-
"unist-util-visit": "^5.1.0",
|
|
305
308
|
// katex — server-side LaTeX renderer used by the always-on
|
|
306
309
|
// pages/lib/_math-block.tsx (called from pages/_mdx-components.ts
|
|
307
310
|
// for `$…$` and `$$…$$` math nodes). Caught by W6B (#1735)
|
|
@@ -313,8 +316,6 @@ function generatePackageJson(choices) {
|
|
|
313
316
|
"@tailwindcss/vite": "^4.2.0",
|
|
314
317
|
tailwindcss: "^4.2.0",
|
|
315
318
|
typescript: "^5.9.0",
|
|
316
|
-
"@types/hast": "^3.0.4",
|
|
317
|
-
"@types/mdast": "^4.0.4",
|
|
318
319
|
"@types/node": "^22.0.0",
|
|
319
320
|
"@types/react": "^19.2.0", // needed for preact/compat type resolution
|
|
320
321
|
"html-validate": "^10.0.0",
|
|
@@ -331,7 +332,7 @@ function generatePackageJson(choices) {
|
|
|
331
332
|
// @takazudo/zudo-doc/integrations/doc-history which in turn imports
|
|
332
333
|
// @takazudo/zudo-doc-history-server/git-history. Without this dep the
|
|
333
334
|
// plugin host fails at init with ERR_MODULE_NOT_FOUND — W8A (#1739).
|
|
334
|
-
deps["@takazudo/zudo-doc-history-server"] = "^0.2.
|
|
335
|
+
deps["@takazudo/zudo-doc-history-server"] = "^0.2.2";
|
|
335
336
|
// W7A (#1736): doc-history-plugin.mjs spawns `tsx -e <inline-script>` to
|
|
336
337
|
// run the v2 runtime in a TS-aware Node subprocess; without tsx the
|
|
337
338
|
// plugin's preBuild step exits with ENOENT before zfb finishes config
|
|
@@ -363,6 +364,11 @@ function generatePackageJson(choices) {
|
|
|
363
364
|
build: "zfb build",
|
|
364
365
|
preview: "zfb preview",
|
|
365
366
|
check: "zfb check",
|
|
367
|
+
// NOTE: no `check:pages` here — the host repo's pages/ typecheck
|
|
368
|
+
// (tsconfig.pages.json, #2018) is host-only for now. The base template's
|
|
369
|
+
// no-op feature stubs (e.g. doc-history.tsx) are not type-clean against
|
|
370
|
+
// the pages/lib call sites, so emitting the script would fail on a fresh
|
|
371
|
+
// scaffold. Revisit once the template stubs carry typed props.
|
|
366
372
|
"check:html": "html-validate \"dist/**/*.html\"",
|
|
367
373
|
};
|
|
368
374
|
if (choices.features.includes("tagGovernance")) {
|
package/dist/settings-gen.js
CHANGED
|
@@ -43,7 +43,7 @@ export function generateSettingsFile(choices) {
|
|
|
43
43
|
lines.push(` lightScheme: ${JSON.stringify(choices.lightScheme ?? "GitHub Light")},`);
|
|
44
44
|
lines.push(` darkScheme: ${JSON.stringify(choices.darkScheme ?? "GitHub Dark")},`);
|
|
45
45
|
lines.push(` respectPrefersColorScheme: ${choices.respectPrefersColorScheme ?? true},`);
|
|
46
|
-
lines.push(` } satisfies ColorModeConfig,`);
|
|
46
|
+
lines.push(` } satisfies ColorModeConfig as ColorModeConfig | false,`);
|
|
47
47
|
}
|
|
48
48
|
lines.push(` siteName: ${JSON.stringify(capitalize(choices.projectName.replace(/-/g, " ")))},`);
|
|
49
49
|
lines.push(` siteDescription: "" as string,`);
|
|
@@ -60,15 +60,18 @@ export function generateSettingsFile(choices) {
|
|
|
60
60
|
}
|
|
61
61
|
lines.push(` siteUrl: "" as string,`);
|
|
62
62
|
lines.push(` docsDir: "src/content/docs",`);
|
|
63
|
-
lines.push(` defaultLocale: ${JSON.stringify(choices.defaultLang ?? "en")} as
|
|
63
|
+
lines.push(` defaultLocale: ${JSON.stringify(choices.defaultLang ?? "en")} as const,`);
|
|
64
64
|
if (choices.features.includes("i18n")) {
|
|
65
65
|
const secondaryLang = getSecondaryLang(choices.defaultLang);
|
|
66
66
|
const secondaryLabel = getLangLabel(secondaryLang);
|
|
67
67
|
lines.push(` locales: {`);
|
|
68
68
|
lines.push(` ${secondaryLang}: { label: ${JSON.stringify(secondaryLabel)}, dir: "src/content/docs-${secondaryLang}" },`);
|
|
69
|
-
lines.push(` }
|
|
69
|
+
lines.push(` } satisfies Record<string, LocaleConfig>,`);
|
|
70
70
|
}
|
|
71
71
|
else {
|
|
72
|
+
// `as`, not `satisfies`: satisfies keeps the inferred type at literal {},
|
|
73
|
+
// so Object.entries(settings.locales) in the generated zfb.config.ts
|
|
74
|
+
// yields unknown values and `zfb check` fails with TS18046 (#2053).
|
|
72
75
|
lines.push(` locales: {} as Record<string, LocaleConfig>,`);
|
|
73
76
|
}
|
|
74
77
|
// mermaid is controlled by the markdown.features block in zfb.config.ts
|
|
@@ -159,7 +162,7 @@ export function generateSettingsFile(choices) {
|
|
|
159
162
|
}
|
|
160
163
|
lines.push(` htmlPreview: undefined as HtmlPreviewConfig | undefined,`);
|
|
161
164
|
if (choices.features.includes("versioning")) {
|
|
162
|
-
lines.push(` versions: [] as VersionConfig[],`);
|
|
165
|
+
lines.push(` versions: [] satisfies VersionConfig[] as VersionConfig[] | false,`);
|
|
163
166
|
}
|
|
164
167
|
else {
|
|
165
168
|
lines.push(` versions: false as VersionConfig[] | false,`);
|
|
@@ -219,7 +222,7 @@ export function generateSettingsFile(choices) {
|
|
|
219
222
|
if (choices.features.includes("changelog")) {
|
|
220
223
|
lines.push(` { label: "Changelog", path: "/docs/changelog", categoryMatch: "changelog" },`);
|
|
221
224
|
}
|
|
222
|
-
lines.push(` ] as HeaderNavItem[],`);
|
|
225
|
+
lines.push(` ] satisfies HeaderNavItem[] as HeaderNavItem[],`);
|
|
223
226
|
lines.push(` headerRightItems: [`);
|
|
224
227
|
if (choices.headerRightItems !== undefined) {
|
|
225
228
|
// User-supplied override (including empty array): emit each entry verbatim,
|
|
@@ -250,7 +253,7 @@ export function generateSettingsFile(choices) {
|
|
|
250
253
|
lines.push(` { type: "component", component: "language-switcher" },`);
|
|
251
254
|
}
|
|
252
255
|
}
|
|
253
|
-
lines.push(` ] as HeaderRightItem[],`);
|
|
256
|
+
lines.push(` ] satisfies HeaderRightItem[] as HeaderRightItem[],`);
|
|
254
257
|
lines.push(`};`);
|
|
255
258
|
return lines.join("\n") + "\n";
|
|
256
259
|
}
|
package/dist/utils.d.ts
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validate a project name against the locked grammar.
|
|
3
|
+
*
|
|
4
|
+
* Returns `null` when valid; a human-readable error string when invalid.
|
|
5
|
+
* Apply on every input path: CLI arg, interactive prompt, preset, and
|
|
6
|
+
* programmatic API.
|
|
7
|
+
*/
|
|
8
|
+
export declare function validateProjectName(name: string): string | null;
|
|
1
9
|
export declare function installDependencies(dir: string, pm: string): void;
|
|
2
10
|
export declare function capitalize(str: string): string;
|
|
3
11
|
/** Get a short uppercase label for a language code (e.g. "en" → "EN", "zh-cn" → "ZH-CN"). */
|
package/dist/utils.js
CHANGED
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
import { execSync } from "child_process";
|
|
2
2
|
import fs from "fs-extra";
|
|
3
|
+
// Project-name grammar (locked by F4 — S4 #2013):
|
|
4
|
+
// /^[a-z0-9][a-z0-9._-]*$/, max 214 chars, unscoped, used as both directory
|
|
5
|
+
// name and package name. Mirrors npm's unscoped-name rules + max path safety.
|
|
6
|
+
const PROJECT_NAME_RE = /^[a-z0-9][a-z0-9._-]*$/;
|
|
7
|
+
const PROJECT_NAME_MAX = 214;
|
|
8
|
+
/**
|
|
9
|
+
* Validate a project name against the locked grammar.
|
|
10
|
+
*
|
|
11
|
+
* Returns `null` when valid; a human-readable error string when invalid.
|
|
12
|
+
* Apply on every input path: CLI arg, interactive prompt, preset, and
|
|
13
|
+
* programmatic API.
|
|
14
|
+
*/
|
|
15
|
+
export function validateProjectName(name) {
|
|
16
|
+
if (!name || name.length === 0) {
|
|
17
|
+
return "Project name is required";
|
|
18
|
+
}
|
|
19
|
+
if (name.length > PROJECT_NAME_MAX) {
|
|
20
|
+
return `Project name must be ${PROJECT_NAME_MAX} characters or fewer`;
|
|
21
|
+
}
|
|
22
|
+
if (!PROJECT_NAME_RE.test(name)) {
|
|
23
|
+
return ("Project name must start with a lowercase letter or digit and contain " +
|
|
24
|
+
"only lowercase letters, digits, dots, underscores, and hyphens");
|
|
25
|
+
}
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
3
28
|
export function installDependencies(dir, pm) {
|
|
4
29
|
const commands = {
|
|
5
30
|
pnpm: "pnpm install",
|
package/dist/zfb-config-gen.js
CHANGED
|
@@ -19,62 +19,20 @@ export function generateZfbConfig(choices) {
|
|
|
19
19
|
const hasDocHistory = choices.features.includes("docHistory");
|
|
20
20
|
const hasLlmsTxt = choices.features.includes("llmsTxt");
|
|
21
21
|
const hasClaudeResources = choices.features.includes("claudeResources");
|
|
22
|
-
const hasTagGovernance = choices.features.includes("tagGovernance");
|
|
23
22
|
const lines = [];
|
|
24
23
|
// --- Imports ---
|
|
25
24
|
lines.push(`import { z } from "zod";`);
|
|
26
25
|
lines.push(`import { defineConfig } from "zfb/config";`);
|
|
27
26
|
lines.push(`import { settings } from "./src/config/settings";`);
|
|
28
|
-
|
|
29
|
-
lines.push(`import { tagVocabulary } from "./src/config/tag-vocabulary";`);
|
|
30
|
-
}
|
|
27
|
+
lines.push(`import { buildDocsSchema } from "./src/config/docs-schema";`);
|
|
31
28
|
lines.push(``);
|
|
32
|
-
// ---
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
lines.push(` allowed.add(entry.id);`);
|
|
40
|
-
lines.push(` for (const alias of entry.aliases ?? []) allowed.add(alias);`);
|
|
41
|
-
lines.push(` }`);
|
|
42
|
-
lines.push(` const allowedList = [...allowed];`);
|
|
43
|
-
lines.push(` if (allowedList.length === 0) return z.array(z.string()).optional();`);
|
|
44
|
-
lines.push(` const [first, ...rest] = allowedList;`);
|
|
45
|
-
lines.push(` return z.array(z.enum([first, ...rest] as [string, ...string[]])).optional();`);
|
|
46
|
-
lines.push(`}`);
|
|
47
|
-
lines.push(``);
|
|
48
|
-
}
|
|
49
|
-
// --- Schema definition ---
|
|
50
|
-
lines.push(`const docsSchema = z`);
|
|
51
|
-
lines.push(` .object({`);
|
|
52
|
-
lines.push(` title: z.string(),`);
|
|
53
|
-
lines.push(` description: z.string().optional(),`);
|
|
54
|
-
lines.push(` category: z.string().optional(),`);
|
|
55
|
-
lines.push(` sidebar_position: z.number().optional(),`);
|
|
56
|
-
lines.push(` sidebar_label: z.string().optional(),`);
|
|
57
|
-
if (hasTagGovernance) {
|
|
58
|
-
lines.push(` tags: buildTagsSchema(),`);
|
|
59
|
-
}
|
|
60
|
-
else {
|
|
61
|
-
lines.push(` tags: z.array(z.string()).optional(),`);
|
|
62
|
-
}
|
|
63
|
-
lines.push(` search_exclude: z.boolean().optional(),`);
|
|
64
|
-
lines.push(` pagination_next: z.string().nullable().optional(),`);
|
|
65
|
-
lines.push(` pagination_prev: z.string().nullable().optional(),`);
|
|
66
|
-
lines.push(` draft: z.boolean().optional(),`);
|
|
67
|
-
lines.push(` unlisted: z.boolean().optional(),`);
|
|
68
|
-
lines.push(` hide_sidebar: z.boolean().optional(),`);
|
|
69
|
-
lines.push(` hide_toc: z.boolean().optional(),`);
|
|
70
|
-
lines.push(` doc_history: z.boolean().optional(),`);
|
|
71
|
-
lines.push(` standalone: z.boolean().optional(),`);
|
|
72
|
-
lines.push(` slug: z.string().optional(),`);
|
|
73
|
-
lines.push(` generated: z.boolean().optional(),`);
|
|
74
|
-
lines.push(` category_no_page: z.boolean().optional(),`);
|
|
75
|
-
lines.push(` category_sort_order: z.enum(["asc", "desc"]).optional(),`);
|
|
76
|
-
lines.push(` })`);
|
|
77
|
-
lines.push(` .passthrough();`);
|
|
29
|
+
// --- Schema definition — delegated to the single source of truth ---
|
|
30
|
+
// buildDocsSchema() lives in src/config/docs-schema.ts and is shared by
|
|
31
|
+
// pages/_data.ts (ZfbDocsData alias) and src/types/docs-entry.ts (DocsData).
|
|
32
|
+
// tagGovernance projects: docs-schema.ts reads settings + tagVocabulary
|
|
33
|
+
// internally, so the generated zfb.config.ts needs no extra import or
|
|
34
|
+
// inline buildTagsSchema — the schema builder encapsulates all of that.
|
|
35
|
+
lines.push(`const docsSchema = buildDocsSchema();`);
|
|
78
36
|
lines.push(``);
|
|
79
37
|
lines.push(`const docsSchemaJson = z.toJSONSchema(docsSchema) as Record<string, unknown>;`);
|
|
80
38
|
lines.push(``);
|
|
@@ -203,6 +161,9 @@ export function generateZfbConfig(choices) {
|
|
|
203
161
|
// --- Export ---
|
|
204
162
|
lines.push(`export default defineConfig({`);
|
|
205
163
|
lines.push(` framework: "preact",`);
|
|
164
|
+
lines.push(` // Pin the dev/preview port — zfb defaults to 3000, but the generated`);
|
|
165
|
+
lines.push(` // CLAUDE.md and the Tauri dev wrappers assume 4321.`);
|
|
166
|
+
lines.push(` port: 4321,`);
|
|
206
167
|
lines.push(` tailwind: { enabled: true },`);
|
|
207
168
|
lines.push(` collections,`);
|
|
208
169
|
lines.push(` stripMdExt: true,`);
|
package/package.json
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
import { getCollection } from "zfb/content";
|
|
14
14
|
import type { CollectionEntry } from "zfb/content";
|
|
15
|
+
import type { DocsData } from "@/config/docs-schema";
|
|
15
16
|
import type { DocsEntry } from "@/types/docs-entry";
|
|
16
17
|
import type { DocPageEntry } from "./lib/doc-page-props";
|
|
17
18
|
import { toRouteSlug } from "@/utils/slug";
|
|
@@ -22,30 +23,16 @@ import { toRouteSlug } from "@/utils/slug";
|
|
|
22
23
|
|
|
23
24
|
/**
|
|
24
25
|
* Frontmatter shape shared by all docs collections (EN, locale, versioned).
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
26
|
+
*
|
|
27
|
+
* Re-exported alias for `DocsData` (the `z.infer`-derived type from
|
|
28
|
+
* `src/config/docs-schema.ts`) so call sites that import `ZfbDocsData` from
|
|
29
|
+
* `pages/_data` continue to work without changes.
|
|
30
|
+
*
|
|
31
|
+
* The `[key: string]: unknown` index signature comes from `.passthrough()` on
|
|
32
|
+
* the zod schema — custom frontmatter keys remain accessible downstream (e.g.
|
|
33
|
+
* for frontmatter-preview) without extra casting.
|
|
28
34
|
*/
|
|
29
|
-
export type ZfbDocsData =
|
|
30
|
-
title: string;
|
|
31
|
-
description?: string;
|
|
32
|
-
category?: string;
|
|
33
|
-
sidebar_position?: number;
|
|
34
|
-
sidebar_label?: string;
|
|
35
|
-
tags?: string[];
|
|
36
|
-
search_exclude?: boolean;
|
|
37
|
-
pagination_next?: string | null;
|
|
38
|
-
pagination_prev?: string | null;
|
|
39
|
-
draft?: boolean;
|
|
40
|
-
unlisted?: boolean;
|
|
41
|
-
hide_sidebar?: boolean;
|
|
42
|
-
hide_toc?: boolean;
|
|
43
|
-
doc_history?: boolean;
|
|
44
|
-
standalone?: boolean;
|
|
45
|
-
slug?: string;
|
|
46
|
-
generated?: boolean;
|
|
47
|
-
[key: string]: unknown;
|
|
48
|
-
};
|
|
35
|
+
export type ZfbDocsData = DocsData;
|
|
49
36
|
|
|
50
37
|
/**
|
|
51
38
|
* zfb collection entry augmented with the `id` and `collection` fields that
|