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
|
@@ -1,135 +1,45 @@
|
|
|
1
|
+
// @ts-check
|
|
1
2
|
// zfb plugin module: claude-resources.
|
|
2
3
|
//
|
|
3
4
|
// Wires `runClaudeResourcesPreStep` (from
|
|
4
5
|
// `@takazudo/zudo-doc/integrations/claude-resources`) into zfb's
|
|
5
|
-
// `preBuild` lifecycle hook.
|
|
6
|
-
// in `scripts/zfb-prebuild.mjs` for this step (the script remains in
|
|
7
|
-
// place during the merge window — T6 retires it).
|
|
6
|
+
// `preBuild` lifecycle hook.
|
|
8
7
|
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
// no build step.
|
|
16
|
-
//
|
|
17
|
-
// 2. The `runClaudeResourcesPreStep` runner pulls in `gray-matter`,
|
|
18
|
-
// which performs a CJS `require("fs")` that esbuild's
|
|
19
|
-
// configuration-loader bundle (ESM-only) cannot satisfy. Loading
|
|
20
|
-
// it inline at the top of `zfb.config.ts` therefore breaks config
|
|
21
|
-
// parsing entirely.
|
|
22
|
-
//
|
|
23
|
-
// Both problems are solved by isolating the runner behind a child
|
|
24
|
-
// process: this shim spawns `tsx` (the project's existing TS-aware
|
|
25
|
-
// Node runner, pinned via `tsx` in `package.json`) on a tiny inline
|
|
26
|
-
// script that imports the runner. tsx handles `.ts` resolution and
|
|
27
|
-
// Node's CJS↔ESM interop for gray-matter; the parent plugin host stays
|
|
28
|
-
// in plain Node and only sees a child-process exit code.
|
|
29
|
-
//
|
|
30
|
-
// Inline functions are not supported by zfb's plugin runtime — see
|
|
31
|
-
// `@takazudo/zfb/plugins` source comment. This file is the plugin-host
|
|
32
|
-
// equivalent once the npm script is retired (T6).
|
|
33
|
-
|
|
34
|
-
import { spawn } from "node:child_process";
|
|
35
|
-
import { fileURLToPath } from "node:url";
|
|
36
|
-
import { dirname, resolve } from "node:path";
|
|
37
|
-
|
|
38
|
-
const PLUGIN_NAME = "@takazudo/zudo-doc-claude-resources";
|
|
8
|
+
// Previously this shim spawned a `tsx` subprocess because the integration
|
|
9
|
+
// package shipped only TypeScript source (no build step) and `gray-matter`
|
|
10
|
+
// pulled in a CJS `require("fs")` that esbuild's ESM-only config-loader
|
|
11
|
+
// bundle could not satisfy. Both constraints are now lifted: the package
|
|
12
|
+
// ships compiled `dist/` and the plugin host is plain Node (not an esbuild
|
|
13
|
+
// bundle), so the runner can be imported directly.
|
|
39
14
|
|
|
40
|
-
|
|
41
|
-
// `<projectRoot>/node_modules/.bin/tsx` from this file. We resolve the
|
|
42
|
-
// binary explicitly so the shim does not depend on the parent shell's
|
|
43
|
-
// `PATH` (the plugin host is spawned by zfb without sourcing the
|
|
44
|
-
// user's profile — Node's `PATH` is whatever zfb itself was launched
|
|
45
|
-
// with).
|
|
46
|
-
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
47
|
-
const TSX_BIN = resolve(HERE, "..", "node_modules", ".bin", "tsx");
|
|
15
|
+
/** @import { ZfbBuildHookContext, ZfbPlugin } from "@takazudo/zfb/plugins" */
|
|
48
16
|
|
|
49
|
-
|
|
50
|
-
* Run the v2 claude-resources runner under tsx.
|
|
51
|
-
*
|
|
52
|
-
* Inlines a minimal ESM script via `tsx -e` so we don't have to ship a
|
|
53
|
-
* second `.ts` file just to host the call site. The runner returns a
|
|
54
|
-
* `{ claudemd, commands, skills, agents }` summary which we re-emit on
|
|
55
|
-
* the child's stdout — the parent (this shim) parses it and forwards
|
|
56
|
-
* the summary to the plugin host's logger.
|
|
57
|
-
*/
|
|
58
|
-
function runRunnerUnderTsx({ claudeDir, projectRoot, docsDir }) {
|
|
59
|
-
// The runner accepts `projectRoot` and `docsDir` as relative paths
|
|
60
|
-
// (resolved against `process.cwd()` in the runner). To insulate the
|
|
61
|
-
// child from whatever cwd zfb spawned us with, we set cwd explicitly
|
|
62
|
-
// and pass absolute paths through.
|
|
63
|
-
// tsx's `-e` flag emits CJS by default (it picks the format from the
|
|
64
|
-
// entry's extension, and an inline `-e` script has none), so we wrap
|
|
65
|
-
// the body in an `async`-IIFE rather than relying on top-level await.
|
|
66
|
-
const childScript = `
|
|
67
|
-
(async () => {
|
|
68
|
-
const { runClaudeResourcesPreStep } = await import("@takazudo/zudo-doc/integrations/claude-resources");
|
|
69
|
-
const result = await runClaudeResourcesPreStep({
|
|
70
|
-
claudeDir: ${JSON.stringify(claudeDir)},
|
|
71
|
-
projectRoot: ${JSON.stringify(projectRoot)},
|
|
72
|
-
docsDir: ${JSON.stringify(docsDir)},
|
|
73
|
-
});
|
|
74
|
-
process.stdout.write(JSON.stringify(result));
|
|
75
|
-
})().catch((err) => {
|
|
76
|
-
process.stderr.write(err && err.stack ? err.stack : String(err));
|
|
77
|
-
process.exit(1);
|
|
78
|
-
});
|
|
79
|
-
`;
|
|
17
|
+
import { runClaudeResourcesPreStep } from "@takazudo/zudo-doc/integrations/claude-resources";
|
|
80
18
|
|
|
81
|
-
|
|
82
|
-
const child = spawn(TSX_BIN, ["-e", childScript], {
|
|
83
|
-
cwd: projectRoot,
|
|
84
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
85
|
-
});
|
|
86
|
-
let stdout = "";
|
|
87
|
-
let stderr = "";
|
|
88
|
-
child.stdout.on("data", (chunk) => { stdout += chunk.toString(); });
|
|
89
|
-
child.stderr.on("data", (chunk) => { stderr += chunk.toString(); });
|
|
90
|
-
child.on("error", reject);
|
|
91
|
-
child.on("close", (code) => {
|
|
92
|
-
if (code !== 0) {
|
|
93
|
-
reject(
|
|
94
|
-
new Error(
|
|
95
|
-
`[${PLUGIN_NAME}] runner exited with code ${code}\n${stderr}`,
|
|
96
|
-
),
|
|
97
|
-
);
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
100
|
-
try {
|
|
101
|
-
resolveCall(JSON.parse(stdout));
|
|
102
|
-
} catch (err) {
|
|
103
|
-
reject(
|
|
104
|
-
new Error(
|
|
105
|
-
`[${PLUGIN_NAME}] failed to parse runner stdout: ${err.message}\nstdout: ${stdout}\nstderr: ${stderr}`,
|
|
106
|
-
),
|
|
107
|
-
);
|
|
108
|
-
}
|
|
109
|
-
});
|
|
110
|
-
});
|
|
111
|
-
}
|
|
19
|
+
const PLUGIN_NAME = "@takazudo/zudo-doc-claude-resources";
|
|
112
20
|
|
|
21
|
+
/** @type {ZfbPlugin} */
|
|
113
22
|
export default {
|
|
114
23
|
name: PLUGIN_NAME,
|
|
24
|
+
|
|
25
|
+
/** @param {ZfbBuildHookContext} ctx */
|
|
115
26
|
async preBuild(ctx) {
|
|
116
|
-
const claudeDir = ctx.options
|
|
27
|
+
const claudeDir = ctx.options["claudeDir"];
|
|
117
28
|
if (typeof claudeDir !== "string" || claudeDir.length === 0) {
|
|
118
29
|
throw new Error(
|
|
119
30
|
`[${PLUGIN_NAME}] preBuild: options.claudeDir must be a non-empty string (got ${JSON.stringify(claudeDir)})`,
|
|
120
31
|
);
|
|
121
32
|
}
|
|
122
|
-
const projectRootOpt = ctx.options
|
|
123
|
-
const docsDirOpt = ctx.options
|
|
124
|
-
const result = await
|
|
33
|
+
const projectRootOpt = ctx.options["projectRoot"];
|
|
34
|
+
const docsDirOpt = ctx.options["docsDir"];
|
|
35
|
+
const result = await runClaudeResourcesPreStep({
|
|
125
36
|
claudeDir,
|
|
126
37
|
projectRoot:
|
|
127
38
|
typeof projectRootOpt === "string" ? projectRootOpt : ctx.projectRoot,
|
|
128
39
|
docsDir: typeof docsDirOpt === "string" ? docsDirOpt : "src/content/docs",
|
|
129
40
|
});
|
|
130
|
-
// Surface a one-line summary so build logs make the
|
|
131
|
-
// observable
|
|
132
|
-
// npm-script glue).
|
|
41
|
+
// Surface a one-line summary so build logs make the generation
|
|
42
|
+
// observable.
|
|
133
43
|
ctx.logger.info(
|
|
134
44
|
`claude-resources: ${result.claudemd} CLAUDE.md, ${result.commands} commands, ${result.skills} skills, ${result.agents} agents`,
|
|
135
45
|
);
|
package/templates/features/claudeResources/files/src/integrations/claude-resources/generate.ts
CHANGED
|
@@ -64,7 +64,9 @@ function parseFrontmatter(content: string) {
|
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
function escapeTitle(s: string): string {
|
|
67
|
-
|
|
67
|
+
// Backslashes must be escaped first — the value is embedded in
|
|
68
|
+
// double-quoted YAML frontmatter where `\d` or `C:\path` is invalid.
|
|
69
|
+
return s.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
68
70
|
}
|
|
69
71
|
|
|
70
72
|
function listFiles(dir: string): string[] {
|
|
@@ -93,6 +95,35 @@ generated: true
|
|
|
93
95
|
fs.writeFileSync(path.join(outputDir, "index.mdx"), mdx);
|
|
94
96
|
}
|
|
95
97
|
|
|
98
|
+
/**
|
|
99
|
+
* Writes an unlisted sub-page MDX file (flat file with a custom nested slug).
|
|
100
|
+
* Used for skill references, scripts, and assets.
|
|
101
|
+
*/
|
|
102
|
+
function writeUnlistedSubPage(
|
|
103
|
+
outputPath: string,
|
|
104
|
+
title: string,
|
|
105
|
+
slug: string,
|
|
106
|
+
body: string,
|
|
107
|
+
) {
|
|
108
|
+
fs.writeFileSync(
|
|
109
|
+
outputPath,
|
|
110
|
+
`---\ntitle: "${escapeTitle(title)}"\nslug: "${slug}"\nunlisted: true\ngenerated: true\n---\n\n${body}\n`,
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Guards that the given name/slug is not the reserved "index" value.
|
|
116
|
+
* Throws with a contextual message if it is.
|
|
117
|
+
*/
|
|
118
|
+
function assertNotIndexReserved(
|
|
119
|
+
nameOrSlug: string,
|
|
120
|
+
errorMessage: string,
|
|
121
|
+
) {
|
|
122
|
+
if (nameOrSlug === "index") {
|
|
123
|
+
throw new Error(errorMessage);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
96
127
|
// ---------------------------------------------------------------------------
|
|
97
128
|
// CLAUDE.md discovery
|
|
98
129
|
// ---------------------------------------------------------------------------
|
|
@@ -176,11 +207,10 @@ function generateClaudemdDocs(
|
|
|
176
207
|
|
|
177
208
|
const emittedSlugs = new Map<string, string>();
|
|
178
209
|
items.forEach((item, index) => {
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
}
|
|
210
|
+
assertNotIndexReserved(
|
|
211
|
+
item.slug,
|
|
212
|
+
`claude-resources: "${item.relPath}" maps to the reserved slug "index", which is used for the category metadata file. Rename the directory to resolve the conflict.`,
|
|
213
|
+
);
|
|
184
214
|
const previous = emittedSlugs.get(item.slug);
|
|
185
215
|
if (previous !== undefined) {
|
|
186
216
|
throw new Error(
|
|
@@ -232,11 +262,10 @@ function generateCommandsDocs(config: ClaudeResourcesConfig): CommandItem[] {
|
|
|
232
262
|
if (!parsed) continue;
|
|
233
263
|
|
|
234
264
|
const name = file.replace(/\.md$/, "");
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
}
|
|
265
|
+
assertNotIndexReserved(
|
|
266
|
+
name,
|
|
267
|
+
`claude-resources: ".claude/commands/index.md" uses the reserved name "index", which is used for the category metadata file. Rename the command file to resolve the conflict.`,
|
|
268
|
+
);
|
|
240
269
|
const description = (parsed.data.description as string) || "";
|
|
241
270
|
|
|
242
271
|
items.push({ name, description });
|
|
@@ -357,11 +386,10 @@ function generateSkillsDocs(config: ClaudeResourcesConfig): SkillItem[] {
|
|
|
357
386
|
const items: SkillItem[] = [];
|
|
358
387
|
|
|
359
388
|
for (const dir of dirs) {
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
}
|
|
389
|
+
assertNotIndexReserved(
|
|
390
|
+
dir,
|
|
391
|
+
`claude-resources: skill directory ".claude/skills/index/" uses the reserved name "index", which is used for the category metadata file. Rename the skill directory to resolve the conflict.`,
|
|
392
|
+
);
|
|
365
393
|
const content = fs.readFileSync(
|
|
366
394
|
path.join(skillsDir, dir, "SKILL.md"),
|
|
367
395
|
"utf8",
|
|
@@ -445,46 +473,43 @@ ${body}`;
|
|
|
445
473
|
const skillSlugBase = `claude-skills/${dir}`;
|
|
446
474
|
|
|
447
475
|
for (const ref of references) {
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
---
|
|
455
|
-
|
|
456
|
-
${escapeForMdx(ref.content.trim())}
|
|
457
|
-
`;
|
|
458
|
-
fs.writeFileSync(path.join(outputDir, `${dir}--ref-${ref.name}.mdx`), refMdx);
|
|
476
|
+
writeUnlistedSubPage(
|
|
477
|
+
path.join(outputDir, `${dir}--ref-${ref.name}.mdx`),
|
|
478
|
+
ref.title,
|
|
479
|
+
`${skillSlugBase}/ref-${ref.name}`,
|
|
480
|
+
escapeForMdx(ref.content.trim()),
|
|
481
|
+
);
|
|
459
482
|
}
|
|
460
483
|
|
|
461
484
|
for (const f of scriptFiles.filter((s) => s.endsWith(".md"))) {
|
|
462
485
|
const slug = f.replace(/\.md$/, "");
|
|
463
|
-
const subSlug = `${skillSlugBase}/script-${slug}`;
|
|
464
486
|
const raw = fs.readFileSync(
|
|
465
487
|
path.join(skillsDir, dir, "scripts", f),
|
|
466
488
|
"utf8",
|
|
467
489
|
);
|
|
468
490
|
const h1Match = raw.match(/^#\s+(.+)$/m);
|
|
469
491
|
const title = h1Match ? h1Match[1] : slug;
|
|
470
|
-
|
|
492
|
+
writeUnlistedSubPage(
|
|
471
493
|
path.join(outputDir, `${dir}--script-${slug}.mdx`),
|
|
472
|
-
|
|
494
|
+
title,
|
|
495
|
+
`${skillSlugBase}/script-${slug}`,
|
|
496
|
+
escapeForMdx(raw.trim()),
|
|
473
497
|
);
|
|
474
498
|
}
|
|
475
499
|
|
|
476
500
|
for (const f of assetFiles.filter((a) => a.endsWith(".md"))) {
|
|
477
501
|
const slug = f.replace(/\.md$/, "");
|
|
478
|
-
const subSlug = `${skillSlugBase}/asset-${slug}`;
|
|
479
502
|
const raw = fs.readFileSync(
|
|
480
503
|
path.join(skillsDir, dir, "assets", f),
|
|
481
504
|
"utf8",
|
|
482
505
|
);
|
|
483
506
|
const h1Match = raw.match(/^#\s+(.+)$/m);
|
|
484
507
|
const title = h1Match ? h1Match[1] : slug;
|
|
485
|
-
|
|
508
|
+
writeUnlistedSubPage(
|
|
486
509
|
path.join(outputDir, `${dir}--asset-${slug}.mdx`),
|
|
487
|
-
|
|
510
|
+
title,
|
|
511
|
+
`${skillSlugBase}/asset-${slug}`,
|
|
512
|
+
escapeForMdx(raw.trim()),
|
|
488
513
|
);
|
|
489
514
|
}
|
|
490
515
|
}
|
|
@@ -522,11 +547,10 @@ function generateAgentsDocs(config: ClaudeResourcesConfig): AgentItem[] {
|
|
|
522
547
|
const description = (parsed.data.description as string) || "";
|
|
523
548
|
const model = (parsed.data.model as string) || "";
|
|
524
549
|
const fileSlug = file.replace(/\.md$/, "");
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
}
|
|
550
|
+
assertNotIndexReserved(
|
|
551
|
+
fileSlug,
|
|
552
|
+
`claude-resources: ".claude/agents/index.md" uses the reserved name "index", which is used for the category metadata file. Rename the agent file to resolve the conflict.`,
|
|
553
|
+
);
|
|
530
554
|
|
|
531
555
|
items.push({ name, file: fileSlug, description, model });
|
|
532
556
|
|
|
@@ -6,11 +6,12 @@
|
|
|
6
6
|
*
|
|
7
7
|
* Type notes:
|
|
8
8
|
* - zdtp's `ColorScheme` requires a `shikiTheme: string` field that is not
|
|
9
|
-
* present in
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
9
|
+
* present in this project's local `ColorScheme` type or data (zdtp uses it
|
|
10
|
+
* only for the panel's client-side code-block preview). Rather than an unsafe
|
|
11
|
+
* `as unknown as Record<string, ZdtpColorScheme>` double-cast, every local
|
|
12
|
+
* scheme map is run through `toZdtpColorSchemes()` below, which supplies
|
|
13
|
+
* `DEFAULT_SHIKI_THEME` as the fallback so the result satisfies zdtp's
|
|
14
|
+
* required-field shape with an ordinary type-checked assignment.
|
|
14
15
|
*/
|
|
15
16
|
|
|
16
17
|
import type {
|
|
@@ -28,6 +29,7 @@ import {
|
|
|
28
29
|
SIZE_TOKENS,
|
|
29
30
|
} from "./design-tokens-manifest";
|
|
30
31
|
import { colorSchemes } from "./color-schemes";
|
|
32
|
+
import type { ColorScheme as LocalColorScheme } from "./color-schemes";
|
|
31
33
|
import { SEMANTIC_DEFAULTS, SEMANTIC_CSS_NAMES } from "./color-scheme-utils";
|
|
32
34
|
import { settings } from "./settings";
|
|
33
35
|
import { DESIGN_TOKEN_SCHEMA } from "@takazudo/zudo-doc/theme";
|
|
@@ -49,6 +51,30 @@ const BASE_DEFAULTS = {
|
|
|
49
51
|
*/
|
|
50
52
|
const DEFAULT_SHIKI_THEME = "github-dark";
|
|
51
53
|
|
|
54
|
+
/**
|
|
55
|
+
* Normalize this project's local `ColorScheme` records into zdtp's
|
|
56
|
+
* `ColorScheme` shape. zdtp's type requires `shikiTheme: string`; the local
|
|
57
|
+
* scheme type makes it optional. This helper fills `DEFAULT_SHIKI_THEME` only
|
|
58
|
+
* when a scheme doesn't declare its own, so the result is assignable to
|
|
59
|
+
* `Record<string, ZdtpColorScheme>` via an ordinary type-checked assignment —
|
|
60
|
+
* replacing the previous `as unknown as` double-cast that bypassed every field
|
|
61
|
+
* check. Tracked upstream at Takazudo/zudo-design-token-panel#342 (shikiTheme
|
|
62
|
+
* should be optional in zdtp's `ColorScheme` type); drop this helper once that
|
|
63
|
+
* lands.
|
|
64
|
+
*/
|
|
65
|
+
function toZdtpColorSchemes(
|
|
66
|
+
schemes: Record<string, LocalColorScheme>,
|
|
67
|
+
): Record<string, ZdtpColorScheme> {
|
|
68
|
+
const normalized: Record<string, ZdtpColorScheme> = {};
|
|
69
|
+
for (const [name, scheme] of Object.entries(schemes)) {
|
|
70
|
+
normalized[name] = {
|
|
71
|
+
...scheme,
|
|
72
|
+
shikiTheme: scheme.shikiTheme ?? DEFAULT_SHIKI_THEME,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
return normalized;
|
|
76
|
+
}
|
|
77
|
+
|
|
52
78
|
/**
|
|
53
79
|
* Initial palette taken from the configured active scheme.
|
|
54
80
|
*/
|
|
@@ -178,9 +204,9 @@ const COLOR_EXTRAS: ColorClusterExtras = {
|
|
|
178
204
|
},
|
|
179
205
|
baseDefaults: BASE_DEFAULTS,
|
|
180
206
|
defaultShikiTheme: DEFAULT_SHIKI_THEME,
|
|
181
|
-
//
|
|
182
|
-
//
|
|
183
|
-
colorSchemes: colorSchemes
|
|
207
|
+
// toZdtpColorSchemes fills the fallback only for schemes without their own
|
|
208
|
+
// shikiTheme, so this is a type-checked assignment rather than an unsafe cast.
|
|
209
|
+
colorSchemes: toZdtpColorSchemes(colorSchemes),
|
|
184
210
|
panelSettings: {
|
|
185
211
|
colorScheme: settings.colorScheme,
|
|
186
212
|
// colorMode: strip off respectPrefersColorScheme (not in zdtp's shape).
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
+
// @ts-check
|
|
1
2
|
// zfb plugin module: doc-history.
|
|
2
3
|
//
|
|
3
4
|
// Wires three lifecycle hooks for the doc-history integration:
|
|
4
5
|
//
|
|
5
6
|
// preBuild — emits `.zfb/doc-history-meta.json` (per-page git metadata
|
|
6
|
-
// consumed at bundle time).
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
// directly. Honours `SKIP_DOC_HISTORY=1` via `env: process.env`.
|
|
7
|
+
// consumed at bundle time). Calls `runDocHistoryMetaStep`
|
|
8
|
+
// directly; honours `SKIP_DOC_HISTORY=1` via the runner's
|
|
9
|
+
// own env check.
|
|
10
10
|
//
|
|
11
11
|
// postBuild — invokes `runDocHistoryPostBuild` to write
|
|
12
12
|
// `<outDir>/doc-history/<slug>.json` files. Skipped by default
|
|
@@ -20,69 +20,45 @@
|
|
|
20
20
|
// Inline functions are not supported by zfb's plugin runtime — see
|
|
21
21
|
// `@takazudo/zfb/plugins` source comment. Plugins must be authored as
|
|
22
22
|
// standalone modules and referenced from `zfb.config.ts` by `name`.
|
|
23
|
-
//
|
|
24
|
-
// The legacy `scripts/zfb-{pre,post}build.mjs` npm-script glue and
|
|
25
|
-
// `scripts/dev-sidecar.mjs` stay in place during the merge window;
|
|
26
|
-
// T6 retires them once all lifecycle epics land.
|
|
27
23
|
|
|
28
|
-
import {
|
|
29
|
-
import { fileURLToPath } from "node:url";
|
|
30
|
-
import { dirname, resolve } from "node:path";
|
|
24
|
+
/** @import { ZfbBuildHookContext, ZfbDevMiddlewareContext, ZfbPlugin } from "@takazudo/zfb/plugins" */
|
|
31
25
|
|
|
26
|
+
import { runDocHistoryMetaStep } from "@takazudo/zudo-doc/integrations/doc-history";
|
|
32
27
|
import { runDocHistoryPostBuild } from "@takazudo/zudo-doc/integrations/doc-history";
|
|
33
28
|
import { createDocHistoryDevMiddleware } from "@takazudo/zudo-doc/integrations/doc-history";
|
|
34
29
|
import { connectToZfbHandler } from "./connect-adapter.mjs";
|
|
35
30
|
|
|
36
|
-
|
|
37
|
-
// tsx is a workspace dep; resolving the binary explicitly avoids PATH
|
|
38
|
-
// dependency — the plugin host is spawned by zfb without the user's shell profile.
|
|
39
|
-
const TSX_BIN = resolve(HERE, "..", "node_modules", ".bin", "tsx");
|
|
40
|
-
|
|
31
|
+
/** @type {ZfbPlugin} */
|
|
41
32
|
export default {
|
|
42
33
|
name: "doc-history",
|
|
43
34
|
|
|
35
|
+
/** @param {ZfbBuildHookContext} ctx */
|
|
44
36
|
async preBuild(ctx) {
|
|
45
37
|
const { docsDir, locales } = ctx.options;
|
|
46
|
-
|
|
47
|
-
// (`runDocHistoryMetaStep`) honours SKIP_DOC_HISTORY=1 internally;
|
|
48
|
-
// passing `env: process.env` propagates the flag to the child process.
|
|
49
|
-
const optsJson = JSON.stringify({
|
|
38
|
+
await runDocHistoryMetaStep({
|
|
50
39
|
projectRoot: ctx.projectRoot,
|
|
51
40
|
docsDir: typeof docsDir === "string" ? docsDir : "src/content/docs",
|
|
52
|
-
locales:
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
const { runDocHistoryMetaStep } = await import("@takazudo/zudo-doc/integrations/doc-history");
|
|
57
|
-
const opts = ${optsJson};
|
|
58
|
-
await runDocHistoryMetaStep(opts);
|
|
59
|
-
})().catch((err) => {
|
|
60
|
-
process.stderr.write(err && err.stack ? err.stack : String(err));
|
|
61
|
-
process.exit(1);
|
|
62
|
-
});
|
|
63
|
-
`;
|
|
64
|
-
const result = spawnSync(TSX_BIN, ["-e", script], {
|
|
65
|
-
cwd: ctx.projectRoot,
|
|
66
|
-
stdio: "inherit",
|
|
67
|
-
env: process.env,
|
|
41
|
+
locales:
|
|
42
|
+
locales != null
|
|
43
|
+
? /** @type {Record<string,{dir:string}>} */ (locales)
|
|
44
|
+
: undefined,
|
|
68
45
|
});
|
|
69
|
-
if (result.status !== 0) {
|
|
70
|
-
throw new Error(
|
|
71
|
-
`doc-history-meta preBuild failed (exit ${result.status})`,
|
|
72
|
-
);
|
|
73
|
-
}
|
|
74
46
|
},
|
|
75
47
|
|
|
48
|
+
/** @param {ZfbBuildHookContext} ctx */
|
|
76
49
|
async postBuild(ctx) {
|
|
77
|
-
const { docsDir, locales } = ctx.options;
|
|
78
50
|
await runDocHistoryPostBuild(
|
|
79
|
-
{
|
|
51
|
+
/** @type {import("@takazudo/zudo-doc/integrations/doc-history").DocHistoryOptions} */ (/** @type {unknown} */ (ctx.options)),
|
|
80
52
|
{ outDir: ctx.outDir, logger: ctx.logger },
|
|
81
53
|
);
|
|
82
54
|
},
|
|
83
55
|
|
|
56
|
+
/** @param {ZfbDevMiddlewareContext} ctx */
|
|
84
57
|
devMiddleware(ctx) {
|
|
85
|
-
const middleware = createDocHistoryDevMiddleware(
|
|
58
|
+
const middleware = createDocHistoryDevMiddleware(
|
|
59
|
+
/** @type {import("@takazudo/zudo-doc/integrations/doc-history").DocHistoryOptions} */ (/** @type {unknown} */ (ctx.options)),
|
|
60
|
+
ctx.logger,
|
|
61
|
+
);
|
|
86
62
|
// zfb's `register(path, handler)` matches against the FULL request
|
|
87
63
|
// URL (no base-stripping). For a non-root base (e.g. "/my-docs/"),
|
|
88
64
|
// requests arrive as `/my-docs/doc-history/foo.json`, so we register
|
|
@@ -90,11 +66,17 @@ export default {
|
|
|
90
66
|
// and the route is `/doc-history` as expected. The v2 middleware
|
|
91
67
|
// itself is base-tolerant (matches via `url.includes("/doc-history/")`)
|
|
92
68
|
// and slices from `/doc-history/` onward when proxying upstream.
|
|
93
|
-
const basePrefix = stripTrailingSlash(
|
|
69
|
+
const basePrefix = stripTrailingSlash(
|
|
70
|
+
typeof ctx.options["base"] === "string" ? ctx.options["base"] : "",
|
|
71
|
+
);
|
|
94
72
|
ctx.register(`${basePrefix}/doc-history`, connectToZfbHandler(middleware));
|
|
95
73
|
},
|
|
96
74
|
};
|
|
97
75
|
|
|
76
|
+
/**
|
|
77
|
+
* @param {string} s
|
|
78
|
+
* @returns {string}
|
|
79
|
+
*/
|
|
98
80
|
function stripTrailingSlash(s) {
|
|
99
81
|
if (typeof s !== "string" || s.length === 0) return "";
|
|
100
82
|
return s.endsWith("/") ? s.slice(0, -1) : s;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
1
3
|
import { useState, useEffect, useCallback, useMemo, useRef } from "preact/compat";
|
|
2
|
-
import {
|
|
4
|
+
import type { Change } from "diff";
|
|
3
5
|
import type { DocHistoryData, DocHistoryEntry } from "@/types/doc-history";
|
|
4
6
|
import { SmartBreak } from "@/utils/smart-break";
|
|
5
7
|
|
|
@@ -104,8 +106,10 @@ interface DiffRow {
|
|
|
104
106
|
type: "context" | "removed" | "added" | "changed";
|
|
105
107
|
}
|
|
106
108
|
|
|
109
|
+
type DiffChanges = Change[];
|
|
110
|
+
|
|
107
111
|
function buildSideBySideRows(
|
|
108
|
-
changes:
|
|
112
|
+
changes: DiffChanges,
|
|
109
113
|
): DiffRow[] {
|
|
110
114
|
const rows: DiffRow[] = [];
|
|
111
115
|
let leftNum = 0;
|
|
@@ -177,11 +181,24 @@ function DiffViewer({
|
|
|
177
181
|
onBack: () => void;
|
|
178
182
|
showBackButton: boolean;
|
|
179
183
|
}) {
|
|
180
|
-
const changes =
|
|
181
|
-
|
|
182
|
-
|
|
184
|
+
const [changes, setChanges] = useState<DiffChanges | null>(null);
|
|
185
|
+
|
|
186
|
+
useEffect(() => {
|
|
187
|
+
let cancelled = false;
|
|
188
|
+
// Lazy-load diff — only needed after History → Compare. This keeps the
|
|
189
|
+
// module out of the eager islands bundle.
|
|
190
|
+
import("diff").then(({ diffLines }) => {
|
|
191
|
+
if (!cancelled) {
|
|
192
|
+
setChanges(diffLines(selection.older.content, selection.newer.content));
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
return () => { cancelled = true; };
|
|
196
|
+
}, [selection.older.content, selection.newer.content]);
|
|
197
|
+
|
|
198
|
+
const rows = useMemo(
|
|
199
|
+
() => (changes ? buildSideBySideRows(changes) : []),
|
|
200
|
+
[changes],
|
|
183
201
|
);
|
|
184
|
-
const rows = useMemo(() => buildSideBySideRows(changes), [changes]);
|
|
185
202
|
|
|
186
203
|
return (
|
|
187
204
|
<div className="flex flex-col h-full">
|
|
@@ -207,8 +224,13 @@ function DiffViewer({
|
|
|
207
224
|
</div>
|
|
208
225
|
</div>
|
|
209
226
|
|
|
210
|
-
{/* Side-by-side diff */}
|
|
211
|
-
|
|
227
|
+
{/* Side-by-side diff — shows a loading state while the diff module lazy-loads */}
|
|
228
|
+
{!changes && (
|
|
229
|
+
<div className="flex-1 flex items-center justify-center py-vsp-xl">
|
|
230
|
+
<p className="text-small text-muted">Loading diff…</p>
|
|
231
|
+
</div>
|
|
232
|
+
)}
|
|
233
|
+
<div className={`flex-1 overflow-auto${!changes ? " hidden" : ""}`}>
|
|
212
234
|
<table className="w-full border-collapse" style={{ tableLayout: "fixed" }}>
|
|
213
235
|
<colgroup>
|
|
214
236
|
<col style={{ width: "2.5rem" }} />
|