@takazudo/zudo-doc 0.2.22 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/zudo-doc.mjs +83 -0
- package/dist/eject/index.d.ts +33 -0
- package/dist/eject/index.js +229 -0
- package/dist/plugins/routes.d.ts +2 -0
- package/dist/plugins/routes.js +100 -0
- package/dist/preset.d.ts +47 -1
- package/dist/preset.js +29 -13
- package/dist/routes/404.d.ts +7 -0
- package/dist/routes/404.js +47 -0
- package/dist/routes/_chrome.d.ts +32 -0
- package/dist/routes/_chrome.js +365 -0
- package/dist/routes/_context.d.ts +56 -0
- package/dist/routes/_context.js +222 -0
- package/dist/routes/_docs-helpers.d.ts +43 -0
- package/dist/routes/_docs-helpers.js +173 -0
- package/dist/routes/_virtual.d.js +0 -0
- package/dist/routes/api-ai-chat.d.ts +5 -0
- package/dist/routes/api-ai-chat.js +24 -0
- package/dist/routes/docs-slug.d.ts +21 -0
- package/dist/routes/docs-slug.js +26 -0
- package/dist/routes/docs-tags-index.d.ts +7 -0
- package/dist/routes/docs-tags-index.js +11 -0
- package/dist/routes/docs-tags-tag.d.ts +23 -0
- package/dist/routes/docs-tags-tag.js +19 -0
- package/dist/routes/docs-versions.d.ts +7 -0
- package/dist/routes/docs-versions.js +11 -0
- package/dist/routes/index.d.ts +7 -0
- package/dist/routes/index.js +117 -0
- package/dist/routes/locale-docs-slug.d.ts +27 -0
- package/dist/routes/locale-docs-slug.js +40 -0
- package/dist/routes/locale-docs-tags-index.d.ts +18 -0
- package/dist/routes/locale-docs-tags-index.js +15 -0
- package/dist/routes/locale-docs-tags-tag.d.ts +25 -0
- package/dist/routes/locale-docs-tags-tag.js +22 -0
- package/dist/routes/locale-docs-versions.d.ts +18 -0
- package/dist/routes/locale-docs-versions.js +15 -0
- package/dist/routes/locale-index.d.ts +24 -0
- package/dist/routes/locale-index.js +132 -0
- package/dist/routes/robots.txt.d.ts +3 -0
- package/dist/routes/robots.txt.js +8 -0
- package/dist/routes/sitemap.xml.d.ts +5 -0
- package/dist/routes/sitemap.xml.js +31 -0
- package/dist/routes/v-docs-slug.d.ts +27 -0
- package/dist/routes/v-docs-slug.js +34 -0
- package/dist/routes/v-locale-docs-slug.d.ts +31 -0
- package/dist/routes/v-locale-docs-slug.js +50 -0
- package/dist/safelist.css +1 -1
- package/dist/settings.d.ts +15 -0
- package/eject/breadcrumb/breadcrumb.tsx +194 -0
- package/eject/breadcrumb/find-path.ts +26 -0
- package/eject/breadcrumb/index.ts +4 -0
- package/eject/breadcrumb/types.ts +32 -0
- package/eject/code-group/index.tsx +78 -0
- package/eject/content-admonition/index.tsx +50 -0
- package/eject/details/details.tsx +34 -0
- package/eject/details/index.ts +4 -0
- package/eject/doc-pager/index.tsx +91 -0
- package/eject/footer/footer.tsx +167 -0
- package/eject/footer/index.ts +19 -0
- package/eject/footer/types.ts +60 -0
- package/eject/header/header.tsx +725 -0
- package/eject/header/index.ts +36 -0
- package/eject/header/nav-active.ts +122 -0
- package/eject/header/nav-overflow-script.ts +302 -0
- package/eject/header/right-items.ts +78 -0
- package/eject/header/types.ts +69 -0
- package/eject/page-loading/index.ts +10 -0
- package/eject/page-loading/page-loading-overlay.tsx +103 -0
- package/eject/sidebar/index.ts +27 -0
- package/eject/sidebar/sidebar.tsx +137 -0
- package/eject/sidebar/types.ts +73 -0
- package/eject/tab-item/index.ts +7 -0
- package/eject/tab-item/tab-item.tsx +70 -0
- package/eject/theme-toggle/color-scheme-sync.ts +73 -0
- package/eject/theme-toggle/index.tsx +120 -0
- package/eject/toc/cx.ts +38 -0
- package/eject/toc/index.ts +11 -0
- package/eject/toc/mobile-toc.tsx +133 -0
- package/eject/toc/toc-title.ts +33 -0
- package/eject/toc/toc.tsx +119 -0
- package/eject/toc/types.ts +14 -0
- package/eject/toc/use-active-heading.ts +162 -0
- package/package.json +87 -7
- package/routes-src/404.tsx +49 -0
- package/routes-src/_chrome.tsx +470 -0
- package/routes-src/_context.ts +314 -0
- package/routes-src/_docs-helpers.ts +284 -0
- package/routes-src/_virtual.d.ts +15 -0
- package/routes-src/api-ai-chat.tsx +46 -0
- package/routes-src/docs-slug.tsx +43 -0
- package/routes-src/docs-tags-index.tsx +14 -0
- package/routes-src/docs-tags-tag.tsx +29 -0
- package/routes-src/docs-versions.tsx +15 -0
- package/routes-src/index.tsx +134 -0
- package/routes-src/locale-docs-slug.tsx +69 -0
- package/routes-src/locale-docs-tags-index.tsx +23 -0
- package/routes-src/locale-docs-tags-tag.tsx +39 -0
- package/routes-src/locale-docs-versions.tsx +23 -0
- package/routes-src/locale-index.tsx +150 -0
- package/routes-src/robots.txt.tsx +13 -0
- package/routes-src/sitemap.xml.tsx +48 -0
- package/routes-src/v-docs-slug.tsx +64 -0
- package/routes-src/v-locale-docs-slug.tsx +85 -0
package/bin/zudo-doc.mjs
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// @takazudo/zudo-doc/bin/zudo-doc.mjs
|
|
3
|
+
//
|
|
4
|
+
// Package bin: `zudo-doc eject <component>` swizzle CLI.
|
|
5
|
+
//
|
|
6
|
+
// Self-contained ESM — runs on plain `node` with NO `tsx` requirement. The eject
|
|
7
|
+
// logic is imported from the package's COMPILED `../dist/eject/index.js`, and the
|
|
8
|
+
// only runtime deps are `minimist` + `picocolors` (declared deps of this package,
|
|
9
|
+
// so they are present transitively in any consumer's node_modules). This is the
|
|
10
|
+
// key difference from the tsx-runner pattern used by `bin/tags-audit.mjs`:
|
|
11
|
+
// tags-audit must load the *project's* TypeScript config files at runtime (hence
|
|
12
|
+
// tsx), whereas eject only copies files + rewrites imports — no TS eval needed —
|
|
13
|
+
// so it works in a default generated project that never installed tsx (#2367).
|
|
14
|
+
//
|
|
15
|
+
// Usage:
|
|
16
|
+
// zudo-doc eject <component> # eject a component's TS source into the project
|
|
17
|
+
// zudo-doc --help # show help
|
|
18
|
+
|
|
19
|
+
import minimist from "minimist";
|
|
20
|
+
import pc from "picocolors";
|
|
21
|
+
import { eject, EJECTABLE } from "../dist/eject/index.js";
|
|
22
|
+
|
|
23
|
+
const argv = minimist(process.argv.slice(2), {
|
|
24
|
+
boolean: ["help"],
|
|
25
|
+
alias: { h: "help" },
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
function printHelp() {
|
|
29
|
+
const validNames = Object.keys(EJECTABLE).sort().join(", ");
|
|
30
|
+
console.log(`
|
|
31
|
+
${pc.bold("Usage:")} zudo-doc <subcommand> [options]
|
|
32
|
+
|
|
33
|
+
${pc.bold("Subcommands:")}
|
|
34
|
+
eject <component> Copy a component's TS source into your project and
|
|
35
|
+
rewrite imports so it resolves locally.
|
|
36
|
+
|
|
37
|
+
${pc.bold("Ejectable components:")}
|
|
38
|
+
${validNames}
|
|
39
|
+
|
|
40
|
+
${pc.bold("Options:")}
|
|
41
|
+
-h, --help Show this help message
|
|
42
|
+
|
|
43
|
+
${pc.bold("Examples:")}
|
|
44
|
+
${pc.dim("# Eject the header component")}
|
|
45
|
+
zudo-doc eject header
|
|
46
|
+
|
|
47
|
+
${pc.dim("# Eject the theme-toggle component")}
|
|
48
|
+
zudo-doc eject theme-toggle
|
|
49
|
+
`);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function main() {
|
|
53
|
+
if (argv["help"] || argv._.length === 0) {
|
|
54
|
+
printHelp();
|
|
55
|
+
process.exit(0);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const [subcommand, componentArg] = argv._;
|
|
59
|
+
|
|
60
|
+
if (subcommand !== "eject") {
|
|
61
|
+
console.error(
|
|
62
|
+
pc.red(`Unknown subcommand "${subcommand}".`) +
|
|
63
|
+
`\nRun \`zudo-doc --help\` for usage.`,
|
|
64
|
+
);
|
|
65
|
+
process.exit(1);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (!componentArg) {
|
|
69
|
+
console.error(
|
|
70
|
+
pc.red(`Missing component name.`) +
|
|
71
|
+
`\nUsage: zudo-doc eject <component>` +
|
|
72
|
+
`\nRun \`zudo-doc --help\` for the list of ejectable components.`,
|
|
73
|
+
);
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
await eject(componentArg, { cwd: process.cwd() });
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
main().catch((err) => {
|
|
81
|
+
console.error(pc.red(String(err)));
|
|
82
|
+
process.exit(1);
|
|
83
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export interface EjectableEntry {
|
|
2
|
+
/** Package subpath, e.g. `@takazudo/zudo-doc/header` */
|
|
3
|
+
packageSubpath: string;
|
|
4
|
+
/** Local destination dir, project-relative POSIX (no trailing slash) */
|
|
5
|
+
localDir: string;
|
|
6
|
+
}
|
|
7
|
+
export declare const EJECTABLE: Record<string, EjectableEntry>;
|
|
8
|
+
export interface ZudoDocJson {
|
|
9
|
+
/** @takazudo/zudo-doc version the ejected source came from */
|
|
10
|
+
packageVersion: string;
|
|
11
|
+
/** component name → local dir (project-relative POSIX, no trailing slash) */
|
|
12
|
+
ejected: Record<string, string>;
|
|
13
|
+
}
|
|
14
|
+
export interface EjectOptions {
|
|
15
|
+
/** Project root to eject into. Defaults to `process.cwd()`. */
|
|
16
|
+
cwd?: string;
|
|
17
|
+
/**
|
|
18
|
+
* Injectable resolver for the installed `@takazudo/zudo-doc` package root.
|
|
19
|
+
* Defaults to resolving `@takazudo/zudo-doc/package.json` from `cwd`
|
|
20
|
+
* node_modules. Override in tests to point at a fixture.
|
|
21
|
+
*/
|
|
22
|
+
resolvePackageRoot?: (cwd: string) => string;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Eject a single component into the project.
|
|
26
|
+
*
|
|
27
|
+
* - If already ejected (recorded in `.zudo-doc.json`): prints a notice and
|
|
28
|
+
* exits cleanly (safe no-op — never clobbers local edits).
|
|
29
|
+
* - Otherwise: copies the TS source from the installed package's `eject/`
|
|
30
|
+
* tree, rewires parent-relative imports, rewrites host call sites, and
|
|
31
|
+
* records the entry in `.zudo-doc.json`.
|
|
32
|
+
*/
|
|
33
|
+
export declare function eject(component: string, options?: EjectOptions): Promise<void>;
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import fs from "fs-extra";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import pc from "picocolors";
|
|
4
|
+
const EJECTABLE = {
|
|
5
|
+
header: {
|
|
6
|
+
packageSubpath: "@takazudo/zudo-doc/header",
|
|
7
|
+
localDir: "src/components/zudo-doc/header"
|
|
8
|
+
},
|
|
9
|
+
footer: {
|
|
10
|
+
packageSubpath: "@takazudo/zudo-doc/footer",
|
|
11
|
+
localDir: "src/components/zudo-doc/footer"
|
|
12
|
+
},
|
|
13
|
+
breadcrumb: {
|
|
14
|
+
packageSubpath: "@takazudo/zudo-doc/breadcrumb",
|
|
15
|
+
localDir: "src/components/zudo-doc/breadcrumb"
|
|
16
|
+
},
|
|
17
|
+
toc: {
|
|
18
|
+
packageSubpath: "@takazudo/zudo-doc/toc",
|
|
19
|
+
localDir: "src/components/zudo-doc/toc"
|
|
20
|
+
},
|
|
21
|
+
sidebar: {
|
|
22
|
+
packageSubpath: "@takazudo/zudo-doc/sidebar",
|
|
23
|
+
localDir: "src/components/zudo-doc/sidebar"
|
|
24
|
+
},
|
|
25
|
+
"theme-toggle": {
|
|
26
|
+
packageSubpath: "@takazudo/zudo-doc/theme-toggle",
|
|
27
|
+
localDir: "src/components/zudo-doc/theme-toggle"
|
|
28
|
+
},
|
|
29
|
+
"page-loading": {
|
|
30
|
+
packageSubpath: "@takazudo/zudo-doc/page-loading",
|
|
31
|
+
localDir: "src/components/zudo-doc/page-loading"
|
|
32
|
+
},
|
|
33
|
+
"tab-item": {
|
|
34
|
+
packageSubpath: "@takazudo/zudo-doc/tab-item",
|
|
35
|
+
localDir: "src/components/zudo-doc/tab-item"
|
|
36
|
+
},
|
|
37
|
+
"doc-pager": {
|
|
38
|
+
packageSubpath: "@takazudo/zudo-doc/doc-pager",
|
|
39
|
+
localDir: "src/components/zudo-doc/doc-pager"
|
|
40
|
+
},
|
|
41
|
+
"content-admonition": {
|
|
42
|
+
packageSubpath: "@takazudo/zudo-doc/content-admonition",
|
|
43
|
+
localDir: "src/components/zudo-doc/content-admonition"
|
|
44
|
+
},
|
|
45
|
+
"code-group": {
|
|
46
|
+
packageSubpath: "@takazudo/zudo-doc/code-group",
|
|
47
|
+
localDir: "src/components/zudo-doc/code-group"
|
|
48
|
+
},
|
|
49
|
+
details: {
|
|
50
|
+
packageSubpath: "@takazudo/zudo-doc/details",
|
|
51
|
+
localDir: "src/components/zudo-doc/details"
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
async function rewireImports(dir) {
|
|
55
|
+
const re = /(from\s+)(["'])\.\.\/([\w-]+)\/[^"']+\2/g;
|
|
56
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
57
|
+
for (const entry of entries) {
|
|
58
|
+
if (entry.isDirectory()) continue;
|
|
59
|
+
if (!/\.[cm]?[jt]sx?$/.test(entry.name)) continue;
|
|
60
|
+
const filePath = path.join(dir, entry.name);
|
|
61
|
+
const original = await fs.readFile(filePath, "utf8");
|
|
62
|
+
const rewritten = original.replace(re, (_, from, quote, seg) => {
|
|
63
|
+
return `${from}${quote}@takazudo/zudo-doc/${seg}${quote}`;
|
|
64
|
+
});
|
|
65
|
+
if (rewritten !== original) {
|
|
66
|
+
await fs.writeFile(filePath, rewritten, "utf8");
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
async function rewireHostCallSites(projectRoot, component, localDir) {
|
|
71
|
+
const packageSubpath = EJECTABLE[component].packageSubpath;
|
|
72
|
+
const escapedSubpath = packageSubpath.replace(/[/\\^$*+?.()|[\]{}]/g, "\\$&");
|
|
73
|
+
const re = new RegExp(`(from\\s+)(["'])${escapedSubpath}\\2`, "g");
|
|
74
|
+
const searchDirs = [
|
|
75
|
+
path.join(projectRoot, "src"),
|
|
76
|
+
path.join(projectRoot, "pages")
|
|
77
|
+
];
|
|
78
|
+
let rewroteCount = 0;
|
|
79
|
+
for (const searchDir of searchDirs) {
|
|
80
|
+
if (!await fs.pathExists(searchDir)) continue;
|
|
81
|
+
const rewriteDir = async (dir, relFromProject) => {
|
|
82
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
83
|
+
for (const entry of entries) {
|
|
84
|
+
const abs = path.join(dir, entry.name);
|
|
85
|
+
if (entry.isDirectory()) {
|
|
86
|
+
await rewriteDir(abs, path.join(relFromProject, entry.name));
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
if (!/\.[cm]?[jt]sx?$/.test(entry.name)) continue;
|
|
90
|
+
const original = await fs.readFile(abs, "utf8");
|
|
91
|
+
if (!original.includes(packageSubpath)) continue;
|
|
92
|
+
const fileDir = path.join(projectRoot, relFromProject);
|
|
93
|
+
const destAbs = path.join(projectRoot, localDir);
|
|
94
|
+
let rel = path.relative(fileDir, destAbs).split(path.sep).join("/");
|
|
95
|
+
if (!rel.startsWith(".")) rel = `./${rel}`;
|
|
96
|
+
const rewritten = original.replace(re, (_, from, quote) => {
|
|
97
|
+
return `${from}${quote}${rel}${quote}`;
|
|
98
|
+
});
|
|
99
|
+
if (rewritten !== original) {
|
|
100
|
+
await fs.writeFile(abs, rewritten, "utf8");
|
|
101
|
+
rewroteCount++;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
const relBase = path.relative(projectRoot, searchDir);
|
|
106
|
+
await rewriteDir(searchDir, relBase);
|
|
107
|
+
}
|
|
108
|
+
return rewroteCount;
|
|
109
|
+
}
|
|
110
|
+
function defaultResolvePackageRoot(cwd) {
|
|
111
|
+
let dir = cwd;
|
|
112
|
+
while (true) {
|
|
113
|
+
const candidate = path.join(dir, "node_modules/@takazudo/zudo-doc");
|
|
114
|
+
if (fs.pathExistsSync(candidate)) return candidate;
|
|
115
|
+
const parent = path.dirname(dir);
|
|
116
|
+
if (parent === dir) break;
|
|
117
|
+
dir = parent;
|
|
118
|
+
}
|
|
119
|
+
throw new Error(
|
|
120
|
+
`Could not find @takazudo/zudo-doc in node_modules from ${cwd}.
|
|
121
|
+
Run \`pnpm install\` first.`
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
async function eject(component, options = {}) {
|
|
125
|
+
const cwd = options.cwd ?? process.cwd();
|
|
126
|
+
const resolvePackageRoot = options.resolvePackageRoot ?? defaultResolvePackageRoot;
|
|
127
|
+
if (!Object.prototype.hasOwnProperty.call(EJECTABLE, component)) {
|
|
128
|
+
const valid = Object.keys(EJECTABLE).sort().join(", ");
|
|
129
|
+
console.error(
|
|
130
|
+
pc.red(`Unknown component "${component}".`) + `
|
|
131
|
+
Ejectable components: ${valid}`
|
|
132
|
+
);
|
|
133
|
+
process.exit(1);
|
|
134
|
+
}
|
|
135
|
+
const entry = EJECTABLE[component];
|
|
136
|
+
const localDir = entry.localDir;
|
|
137
|
+
const destAbs = path.join(cwd, localDir);
|
|
138
|
+
const provenancePath = path.join(cwd, ".zudo-doc.json");
|
|
139
|
+
let provenance = { packageVersion: "unknown", ejected: {} };
|
|
140
|
+
if (await fs.pathExists(provenancePath)) {
|
|
141
|
+
try {
|
|
142
|
+
provenance = await fs.readJson(provenancePath);
|
|
143
|
+
} catch {
|
|
144
|
+
provenance = { packageVersion: "unknown", ejected: {} };
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
if (Object.prototype.hasOwnProperty.call(provenance.ejected, component)) {
|
|
148
|
+
const recorded = provenance.ejected[component];
|
|
149
|
+
console.log(
|
|
150
|
+
pc.yellow(`already ejected at ${recorded}`) + ` \u2014 skipping (re-eject is a safe no-op)`
|
|
151
|
+
);
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
if (await fs.pathExists(destAbs)) {
|
|
155
|
+
console.log(
|
|
156
|
+
pc.yellow(`Destination ${localDir} already exists`) + ` but is not recorded in .zudo-doc.json.
|
|
157
|
+
Treating as already ejected \u2014 skipping to avoid clobbering local edits.
|
|
158
|
+
If you want to re-eject, remove the directory and its .zudo-doc.json entry manually.`
|
|
159
|
+
);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
let pkgRoot;
|
|
163
|
+
try {
|
|
164
|
+
pkgRoot = resolvePackageRoot(cwd);
|
|
165
|
+
} catch (err) {
|
|
166
|
+
console.error(pc.red(String(err)));
|
|
167
|
+
process.exit(1);
|
|
168
|
+
}
|
|
169
|
+
const ejectSrcDir = path.join(pkgRoot, "eject", component);
|
|
170
|
+
if (!await fs.pathExists(ejectSrcDir)) {
|
|
171
|
+
const pkgJsonPath = path.join(pkgRoot, "package.json");
|
|
172
|
+
let version = "unknown";
|
|
173
|
+
try {
|
|
174
|
+
const pkgJson = await fs.readJson(pkgJsonPath);
|
|
175
|
+
version = pkgJson.version ?? "unknown";
|
|
176
|
+
} catch {
|
|
177
|
+
}
|
|
178
|
+
console.error(
|
|
179
|
+
pc.red(
|
|
180
|
+
`@takazudo/zudo-doc@${version} does not ship eject sources for "${component}".
|
|
181
|
+
Bump @takazudo/zudo-doc to a version that includes the eject/ tree and retry.`
|
|
182
|
+
)
|
|
183
|
+
);
|
|
184
|
+
process.exit(1);
|
|
185
|
+
}
|
|
186
|
+
let installedVersion = provenance.packageVersion;
|
|
187
|
+
try {
|
|
188
|
+
const pkgJson = await fs.readJson(path.join(pkgRoot, "package.json"));
|
|
189
|
+
installedVersion = pkgJson.version ?? installedVersion;
|
|
190
|
+
} catch {
|
|
191
|
+
}
|
|
192
|
+
await fs.ensureDir(destAbs);
|
|
193
|
+
await fs.copy(ejectSrcDir, destAbs);
|
|
194
|
+
console.log(pc.green(`Copied ${component} source \u2192 ${localDir}`));
|
|
195
|
+
await rewireImports(destAbs);
|
|
196
|
+
console.log(pc.dim(` Rewired cross-component imports in ${localDir}`));
|
|
197
|
+
const rewroteCount = await rewireHostCallSites(cwd, component, localDir);
|
|
198
|
+
if (rewroteCount > 0) {
|
|
199
|
+
console.log(
|
|
200
|
+
pc.dim(` Rewrote ${rewroteCount} host file(s) to import from ${localDir}`)
|
|
201
|
+
);
|
|
202
|
+
} else {
|
|
203
|
+
console.log(
|
|
204
|
+
pc.dim(
|
|
205
|
+
` No host call sites found for ${entry.packageSubpath}.
|
|
206
|
+
Update your imports manually: change the \`"${entry.packageSubpath}"\`
|
|
207
|
+
specifier to a local one, e.g. \`"@/${localDir}/index.js"\` or a relative path.`
|
|
208
|
+
)
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
provenance.packageVersion = installedVersion;
|
|
212
|
+
provenance.ejected[component] = localDir;
|
|
213
|
+
await fs.writeJson(provenancePath, provenance, { spaces: 2 });
|
|
214
|
+
console.log(pc.green(`Updated .zudo-doc.json`));
|
|
215
|
+
console.log(
|
|
216
|
+
pc.bold(
|
|
217
|
+
`
|
|
218
|
+
${pc.green("\u2713")} Ejected ${component} from @takazudo/zudo-doc@${installedVersion}
|
|
219
|
+
Source: node_modules/@takazudo/zudo-doc/eject/${component}/
|
|
220
|
+
Destination: ${localDir}/
|
|
221
|
+
Provenance: .zudo-doc.json
|
|
222
|
+
`
|
|
223
|
+
)
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
export {
|
|
227
|
+
EJECTABLE,
|
|
228
|
+
eject
|
|
229
|
+
};
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import { existsSync, cpSync, rmSync, mkdirSync } from "node:fs";
|
|
3
|
+
import { dirname, basename, join } from "node:path";
|
|
4
|
+
import { definePlugin } from "@takazudo/zfb/plugins";
|
|
5
|
+
function deriveRoutes(settings) {
|
|
6
|
+
const routes = [];
|
|
7
|
+
const localeCodes = Object.keys(settings.locales ?? {});
|
|
8
|
+
const hasVersions = Array.isArray(settings.versions) && settings.versions.length > 0;
|
|
9
|
+
const docTags = settings.docTags === true;
|
|
10
|
+
const aiAssistant = settings.aiAssistant === true;
|
|
11
|
+
routes.push({ pattern: "/404", entrypoint: "@takazudo/zudo-doc/routes/404" });
|
|
12
|
+
routes.push({ pattern: "/sitemap.xml", entrypoint: "@takazudo/zudo-doc/routes/sitemap.xml" });
|
|
13
|
+
routes.push({ pattern: "/robots.txt", entrypoint: "@takazudo/zudo-doc/routes/robots.txt" });
|
|
14
|
+
routes.push({ pattern: "/docs/[[...slug]]", entrypoint: "@takazudo/zudo-doc/routes/docs-slug" });
|
|
15
|
+
if (docTags) {
|
|
16
|
+
routes.push({ pattern: "/docs/tags", entrypoint: "@takazudo/zudo-doc/routes/docs-tags-index" });
|
|
17
|
+
routes.push({ pattern: "/docs/tags/[tag]", entrypoint: "@takazudo/zudo-doc/routes/docs-tags-tag" });
|
|
18
|
+
}
|
|
19
|
+
if (aiAssistant) {
|
|
20
|
+
routes.push({
|
|
21
|
+
pattern: "/api/ai-chat",
|
|
22
|
+
entrypoint: "@takazudo/zudo-doc/routes/api-ai-chat",
|
|
23
|
+
opts: { prerender: false }
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
if (hasVersions) {
|
|
27
|
+
routes.push({ pattern: "/docs/versions", entrypoint: "@takazudo/zudo-doc/routes/docs-versions" });
|
|
28
|
+
routes.push({ pattern: "/v/[version]/docs/[[...slug]]", entrypoint: "@takazudo/zudo-doc/routes/v-docs-slug" });
|
|
29
|
+
routes.push({ pattern: "/v/[version]/[locale]/docs/[[...slug]]", entrypoint: "@takazudo/zudo-doc/routes/v-locale-docs-slug" });
|
|
30
|
+
}
|
|
31
|
+
if (localeCodes.length > 0) {
|
|
32
|
+
routes.push({ pattern: "/[locale]", entrypoint: "@takazudo/zudo-doc/routes/locale-index" });
|
|
33
|
+
routes.push({ pattern: "/[locale]/docs/[[...slug]]", entrypoint: "@takazudo/zudo-doc/routes/locale-docs-slug" });
|
|
34
|
+
if (docTags) {
|
|
35
|
+
routes.push({ pattern: "/[locale]/docs/tags", entrypoint: "@takazudo/zudo-doc/routes/locale-docs-tags-index" });
|
|
36
|
+
routes.push({ pattern: "/[locale]/docs/tags/[tag]", entrypoint: "@takazudo/zudo-doc/routes/locale-docs-tags-tag" });
|
|
37
|
+
}
|
|
38
|
+
if (hasVersions) {
|
|
39
|
+
routes.push({ pattern: "/[locale]/docs/versions", entrypoint: "@takazudo/zudo-doc/routes/locale-docs-versions" });
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return routes;
|
|
43
|
+
}
|
|
44
|
+
const plugin = definePlugin({
|
|
45
|
+
name: "@takazudo/zudo-doc/plugins/routes",
|
|
46
|
+
setup(ctx) {
|
|
47
|
+
const options = ctx.options;
|
|
48
|
+
const settings = options.settings ?? {};
|
|
49
|
+
const translations = options.translations ?? {};
|
|
50
|
+
const tagVocabulary = options.tagVocabulary ?? [];
|
|
51
|
+
ctx.addVirtualModule(
|
|
52
|
+
"virtual:zudo-doc-route-context",
|
|
53
|
+
() => `export const routeContext = ${JSON.stringify({
|
|
54
|
+
settings,
|
|
55
|
+
translations,
|
|
56
|
+
tagVocabulary
|
|
57
|
+
})};
|
|
58
|
+
`
|
|
59
|
+
);
|
|
60
|
+
const require2 = createRequire(import.meta.url);
|
|
61
|
+
let stagedRoutesDir;
|
|
62
|
+
const ensureStaged = (routesSrcDir) => {
|
|
63
|
+
if (stagedRoutesDir) return stagedRoutesDir;
|
|
64
|
+
const dest = join(ctx.projectRoot, ".zudo-doc", "routes-src");
|
|
65
|
+
rmSync(dest, { recursive: true, force: true });
|
|
66
|
+
mkdirSync(dirname(dest), { recursive: true });
|
|
67
|
+
cpSync(routesSrcDir, dest, { recursive: true });
|
|
68
|
+
stagedRoutesDir = dest;
|
|
69
|
+
return dest;
|
|
70
|
+
};
|
|
71
|
+
for (const route of deriveRoutes(settings)) {
|
|
72
|
+
let resolvedEntrypoint;
|
|
73
|
+
try {
|
|
74
|
+
const compiledPath = require2.resolve(route.entrypoint);
|
|
75
|
+
const tsxName = basename(compiledPath).replace(/\.js$/, ".tsx");
|
|
76
|
+
const routesSrcDir = dirname(compiledPath).replace(
|
|
77
|
+
/dist[\\/]routes$/,
|
|
78
|
+
"routes-src"
|
|
79
|
+
);
|
|
80
|
+
const routesSrcPath = join(routesSrcDir, tsxName);
|
|
81
|
+
const srcPath = compiledPath.replace(/[\\/]dist[\\/]/, "/src/").replace(/\.js$/, ".tsx");
|
|
82
|
+
if (existsSync(routesSrcPath)) {
|
|
83
|
+
const underNodeModules = /[\\/]node_modules[\\/]/.test(routesSrcPath);
|
|
84
|
+
resolvedEntrypoint = underNodeModules ? join(ensureStaged(routesSrcDir), tsxName) : routesSrcPath;
|
|
85
|
+
} else if (existsSync(srcPath)) {
|
|
86
|
+
resolvedEntrypoint = srcPath;
|
|
87
|
+
} else {
|
|
88
|
+
resolvedEntrypoint = compiledPath;
|
|
89
|
+
}
|
|
90
|
+
} catch {
|
|
91
|
+
resolvedEntrypoint = route.entrypoint;
|
|
92
|
+
}
|
|
93
|
+
ctx.injectRoute(route.pattern, resolvedEntrypoint, route.opts);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
var routes_default = plugin;
|
|
98
|
+
export {
|
|
99
|
+
routes_default as default
|
|
100
|
+
};
|
package/dist/preset.d.ts
CHANGED
|
@@ -72,6 +72,18 @@ export interface PresetSettings {
|
|
|
72
72
|
claudeResources?: PresetClaudeResourcesConfig | false;
|
|
73
73
|
/** "owner/repo" — when set, enables `#123` / SHA autolinks in markdown. Omit to disable entirely. */
|
|
74
74
|
githubAutolinksRepo?: string;
|
|
75
|
+
/** When `true`, the preset adds the package-owned route-injection plugin
|
|
76
|
+
* (`@takazudo/zudo-doc/plugins/routes`). On by default since the fast-follow
|
|
77
|
+
* (#2372): the showcase and create-zudo-doc both EMIT `packageOwnedRoutes: true`
|
|
78
|
+
* into their generated `settings.ts`. `buildPlugins` reads this value truthily
|
|
79
|
+
* and does NO central defaulting — a hand-written settings object that OMITS the
|
|
80
|
+
* field is treated as off; set it explicitly to control injection.
|
|
81
|
+
* See `docs/adr/route-injection-seam.md`. */
|
|
82
|
+
packageOwnedRoutes?: boolean;
|
|
83
|
+
/** Gate for the `/docs/tags` + `/docs/tags/[tag]` injected routes. */
|
|
84
|
+
docTags?: boolean;
|
|
85
|
+
/** Gate for the SSR `/api/ai-chat` injected route (`prerender: false`). */
|
|
86
|
+
aiAssistant?: boolean;
|
|
75
87
|
}
|
|
76
88
|
/**
|
|
77
89
|
* The directives recipe map (`markdown.features.directives`): directive name →
|
|
@@ -81,6 +93,29 @@ export interface PresetSettings {
|
|
|
81
93
|
* the canonical seven (note/tip/info/warning/danger/caution/details).
|
|
82
94
|
*/
|
|
83
95
|
export type DirectiveVocabulary = Record<string, string>;
|
|
96
|
+
/**
|
|
97
|
+
* The UI-string translation table (`src/config/i18n.ts` `translations`).
|
|
98
|
+
* Locale code → key → translated string. Passed (not imported) so the preset's
|
|
99
|
+
* import graph stays node-builtin-free; rides into the route-context virtual
|
|
100
|
+
* module verbatim when `packageOwnedRoutes` is on. Optional — only consumed by
|
|
101
|
+
* the routes plugin; omitting it makes the virtual module carry `{}`.
|
|
102
|
+
*/
|
|
103
|
+
export type PresetTranslations = Record<string, Record<string, string>>;
|
|
104
|
+
/**
|
|
105
|
+
* The tag vocabulary entries (`src/config/tag-vocabulary.ts`). Serializable
|
|
106
|
+
* data, threaded into the route-context virtual module when
|
|
107
|
+
* `packageOwnedRoutes` is on. Optional — only consumed by the routes plugin.
|
|
108
|
+
*/
|
|
109
|
+
export type PresetTagVocabularyEntry = {
|
|
110
|
+
id: string;
|
|
111
|
+
label?: string;
|
|
112
|
+
description?: string;
|
|
113
|
+
group?: string;
|
|
114
|
+
aliases?: readonly string[];
|
|
115
|
+
deprecated?: boolean | {
|
|
116
|
+
redirect?: string;
|
|
117
|
+
};
|
|
118
|
+
};
|
|
84
119
|
/** Arguments to `zudoDocPreset`. */
|
|
85
120
|
export interface ZudoDocPresetArgs {
|
|
86
121
|
/** The project's `settings` object (structurally `PresetSettings`). */
|
|
@@ -97,6 +132,17 @@ export interface ZudoDocPresetArgs {
|
|
|
97
132
|
* The directives recipe map. See {@link DirectiveVocabulary}.
|
|
98
133
|
*/
|
|
99
134
|
directiveVocabulary: DirectiveVocabulary;
|
|
135
|
+
/**
|
|
136
|
+
* The host's UI-string translation table. Only consumed when
|
|
137
|
+
* `settings.packageOwnedRoutes` is true (rides into the route-context virtual
|
|
138
|
+
* module). Optional — defaults to `{}`. See {@link PresetTranslations}.
|
|
139
|
+
*/
|
|
140
|
+
translations?: PresetTranslations;
|
|
141
|
+
/**
|
|
142
|
+
* The host's tag vocabulary entries. Only consumed when
|
|
143
|
+
* `settings.packageOwnedRoutes` is true. Optional — defaults to `[]`.
|
|
144
|
+
*/
|
|
145
|
+
tagVocabulary?: readonly PresetTagVocabularyEntry[];
|
|
100
146
|
}
|
|
101
147
|
export interface PresetCollection {
|
|
102
148
|
name: string;
|
|
@@ -149,4 +195,4 @@ export interface ZudoDocPresetResult {
|
|
|
149
195
|
* });
|
|
150
196
|
* ```
|
|
151
197
|
*/
|
|
152
|
-
export declare function zudoDocPreset({ settings, buildDocsSchema, directiveVocabulary, }: ZudoDocPresetArgs): ZudoDocPresetResult;
|
|
198
|
+
export declare function zudoDocPreset({ settings, buildDocsSchema, directiveVocabulary, translations, tagVocabulary, }: ZudoDocPresetArgs): ZudoDocPresetResult;
|
package/dist/preset.js
CHANGED
|
@@ -2,12 +2,14 @@ import { z } from "zod";
|
|
|
2
2
|
function zudoDocPreset({
|
|
3
3
|
settings,
|
|
4
4
|
buildDocsSchema,
|
|
5
|
-
directiveVocabulary
|
|
5
|
+
directiveVocabulary,
|
|
6
|
+
translations,
|
|
7
|
+
tagVocabulary
|
|
6
8
|
}) {
|
|
7
9
|
const docsSchemaJson = z.toJSONSchema(buildDocsSchema());
|
|
8
10
|
return {
|
|
9
11
|
collections: buildCollections(settings, docsSchemaJson),
|
|
10
|
-
plugins: buildPlugins(settings),
|
|
12
|
+
plugins: buildPlugins(settings, { translations, tagVocabulary }),
|
|
11
13
|
markdown: { features: buildMarkdownFeatures(settings, directiveVocabulary) },
|
|
12
14
|
// Dual-theme syntect (zfb >= 0.1.0-next.45). Theme names are SYNTECT
|
|
13
15
|
// built-ins, NOT Shiki names. Tokens emit `--shiki-light`/`--shiki-dark`
|
|
@@ -103,7 +105,7 @@ function buildMarkdownFeatures(settings, directiveVocabulary) {
|
|
|
103
105
|
headingIds: { strategy: settings.headingIdStrategy }
|
|
104
106
|
};
|
|
105
107
|
}
|
|
106
|
-
function buildPlugins(settings) {
|
|
108
|
+
function buildPlugins(settings, routeContext) {
|
|
107
109
|
const localeArray = Object.entries(settings.locales).map(([code, locale]) => ({
|
|
108
110
|
code,
|
|
109
111
|
dir: locale.dir
|
|
@@ -112,6 +114,29 @@ function buildPlugins(settings) {
|
|
|
112
114
|
Object.entries(settings.locales).map(([code, locale]) => [code, { dir: locale.dir }])
|
|
113
115
|
);
|
|
114
116
|
return [
|
|
117
|
+
// Package-owned route injection — dormant by default (Decision 4). The
|
|
118
|
+
// descriptor is a BARE SPECIFIER, never an imported plugin function: the
|
|
119
|
+
// preset's node-free eval-graph guard (preset.test.ts) bundles this module
|
|
120
|
+
// under --platform=neutral, and importing the plugin would drag its
|
|
121
|
+
// `injectRoute`/`node:*` graph into the config eval. The plugin's `setup`
|
|
122
|
+
// hook reads `options` to (a) emit the route-context virtual module
|
|
123
|
+
// (serializable settings/translations/tagVocabulary only) and (b) derive
|
|
124
|
+
// the route catalog from `settings.locales` / `settings.versions`. Listed
|
|
125
|
+
// FIRST so an injected route is registered before the other plugins'
|
|
126
|
+
// preBuild work runs (ordering is cosmetic — injection happens in `setup`).
|
|
127
|
+
...settings.packageOwnedRoutes ? [
|
|
128
|
+
{
|
|
129
|
+
name: "@takazudo/zudo-doc/plugins/routes",
|
|
130
|
+
options: {
|
|
131
|
+
// Serializable project settings — JSON.stringify-d verbatim into
|
|
132
|
+
// the virtual module. No function-valued fields (verified in
|
|
133
|
+
// src/config/settings.ts), so it round-trips losslessly.
|
|
134
|
+
settings,
|
|
135
|
+
translations: routeContext.translations ?? {},
|
|
136
|
+
tagVocabulary: routeContext.tagVocabulary ?? []
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
] : [],
|
|
115
140
|
...settings.claudeResources ? [
|
|
116
141
|
{
|
|
117
142
|
name: "@takazudo/zudo-doc/plugins/claude-resources",
|
|
@@ -152,16 +177,7 @@ function buildPlugins(settings) {
|
|
|
152
177
|
locales: localeArray
|
|
153
178
|
}
|
|
154
179
|
}
|
|
155
|
-
] : []
|
|
156
|
-
// copy-public stays a PROJECT-relative descriptor (NOT relocated in S3).
|
|
157
|
-
// Native zfb copy_public_dir is not yet validated for base="/", so the
|
|
158
|
-
// host plugin remains until that lands; deleted in place in a later epic.
|
|
159
|
-
{
|
|
160
|
-
name: "./plugins/copy-public-plugin.mjs",
|
|
161
|
-
options: {
|
|
162
|
-
publicDir: "public"
|
|
163
|
-
}
|
|
164
|
-
}
|
|
180
|
+
] : []
|
|
165
181
|
];
|
|
166
182
|
}
|
|
167
183
|
export {
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { Fragment, jsx, jsxs } from "preact/jsx-runtime";
|
|
2
|
+
import { DocLayoutWithDefaults } from "../doclayout/index.js";
|
|
3
|
+
import { settings, defaultLocale, withBase } from "./_context.js";
|
|
4
|
+
import {
|
|
5
|
+
HeadWithDefaults,
|
|
6
|
+
HeaderWithDefaults,
|
|
7
|
+
FooterWithDefaults,
|
|
8
|
+
BodyEndIslandsStub,
|
|
9
|
+
composeMetaTitle
|
|
10
|
+
} from "./_chrome.js";
|
|
11
|
+
const frontmatter = { title: "404" };
|
|
12
|
+
function NotFoundPage() {
|
|
13
|
+
const locale = defaultLocale;
|
|
14
|
+
const title = "Page Not Found";
|
|
15
|
+
return /* @__PURE__ */ jsx(
|
|
16
|
+
DocLayoutWithDefaults,
|
|
17
|
+
{
|
|
18
|
+
title: composeMetaTitle(title),
|
|
19
|
+
head: /* @__PURE__ */ jsx(HeadWithDefaults, { title }),
|
|
20
|
+
lang: locale,
|
|
21
|
+
noindex: true,
|
|
22
|
+
hideSidebar: true,
|
|
23
|
+
hideToc: true,
|
|
24
|
+
sidebarOverride: /* @__PURE__ */ jsx(Fragment, {}),
|
|
25
|
+
headerOverride: /* @__PURE__ */ jsx(HeaderWithDefaults, { lang: locale }),
|
|
26
|
+
footerOverride: /* @__PURE__ */ jsx(FooterWithDefaults, { lang: locale }),
|
|
27
|
+
bodyEndComponents: /* @__PURE__ */ jsx(BodyEndIslandsStub, { basePath: settings.base ?? "/" }),
|
|
28
|
+
enableClientRouter: settings.dynamicPageTransition,
|
|
29
|
+
children: /* @__PURE__ */ jsxs("div", { class: "min-h-[60vh] flex flex-col items-center justify-center px-hsp-2xl py-vsp-xl", children: [
|
|
30
|
+
/* @__PURE__ */ jsx("h1", { class: "text-display font-bold mb-vsp-md", children: "404" }),
|
|
31
|
+
/* @__PURE__ */ jsx("p", { class: "text-title text-muted mb-vsp-xl", children: "Page not found." }),
|
|
32
|
+
/* @__PURE__ */ jsx(
|
|
33
|
+
"a",
|
|
34
|
+
{
|
|
35
|
+
href: withBase("/"),
|
|
36
|
+
class: "bg-accent px-hsp-lg py-vsp-xs font-medium text-bg hover:bg-accent-hover",
|
|
37
|
+
children: "Back to Home"
|
|
38
|
+
}
|
|
39
|
+
)
|
|
40
|
+
] })
|
|
41
|
+
}
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
export {
|
|
45
|
+
NotFoundPage as default,
|
|
46
|
+
frontmatter
|
|
47
|
+
};
|