@takazudo/zudo-doc 5.14.0 → 5.16.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/CHANGELOG.md +40 -0
- package/dist/asset-components/asset-card.js +3 -1
- package/dist/asset-components/asset-code.js +2 -0
- package/dist/asset-components/shared.d.ts +5 -0
- package/dist/asset-components/shared.js +7 -0
- package/dist/asset-index-page/index.d.ts +1 -0
- package/dist/asset-index-page/index.js +10 -9
- package/dist/asset-page/index.d.ts +20 -2
- package/dist/asset-page/index.js +43 -24
- package/dist/asset-path/index.d.ts +2 -0
- package/dist/asset-path/index.js +11 -1
- package/dist/chrome/derive.js +31 -6
- package/dist/compiled.css +12 -0
- package/dist/config.d.ts +15 -6
- package/dist/config.js +2 -0
- package/dist/content/content-link.d.ts +5 -1
- package/dist/content/content-link.js +4 -1
- package/dist/doc-content-header/index.js +1 -1
- package/dist/doc-page-shell/index.js +1 -1
- package/dist/factory-context/index.d.ts +7 -3
- package/dist/header-with-defaults/index.js +2 -0
- package/dist/home-page/index.js +1 -1
- package/dist/html-preview-wrapper/html-preview-wrapper.d.ts +41 -7
- package/dist/html-preview-wrapper/html-preview-wrapper.js +86 -2
- package/dist/html-preview-wrapper/html-preview.d.ts +29 -4
- package/dist/html-preview-wrapper/html-preview.js +64 -6
- package/dist/html-preview-wrapper/index.d.ts +1 -1
- package/dist/html-preview-wrapper/preview-auto-height.d.ts +24 -0
- package/dist/html-preview-wrapper/preview-auto-height.js +120 -0
- package/dist/html-preview-wrapper/preview-base.d.ts +21 -1
- package/dist/html-preview-wrapper/preview-base.js +99 -50
- package/dist/i18n-defaults/index.d.ts +5 -3
- package/dist/i18n-defaults/index.js +89 -0
- package/dist/i18n-version/language-switcher.d.ts +10 -5
- package/dist/i18n-version/language-switcher.js +115 -22
- package/dist/i18n-version/types.d.ts +1 -1
- package/dist/mdx-components/index.d.ts +4 -0
- package/dist/mdx-components/index.js +11 -2
- package/dist/nav-indexing/versions-page-content.js +1 -1
- package/dist/plugins/claude-resources.js +15 -1
- package/dist/plugins/codex-resources.js +15 -1
- package/dist/plugins/internal/asset-viewer/asset-pages.d.ts +53 -0
- package/dist/plugins/internal/asset-viewer/asset-pages.js +98 -0
- package/dist/plugins/internal/asset-viewer/scan.d.ts +18 -1
- package/dist/plugins/internal/asset-viewer/scan.js +34 -27
- package/dist/plugins/internal/claude-resources/generate.d.ts +9 -0
- package/dist/plugins/internal/claude-resources/generate.js +188 -18
- package/dist/plugins/internal/claude-resources/index.d.ts +9 -0
- package/dist/plugins/internal/claude-resources/index.js +13 -1
- package/dist/plugins/internal/codex-resources/agents-md.js +15 -2
- package/dist/plugins/internal/codex-resources/agents.js +15 -2
- package/dist/plugins/internal/codex-resources/config.js +15 -2
- package/dist/plugins/internal/codex-resources/generate.d.ts +9 -0
- package/dist/plugins/internal/codex-resources/generate.js +19 -7
- package/dist/plugins/internal/codex-resources/hooks.js +15 -2
- package/dist/plugins/internal/codex-resources/index.d.ts +9 -0
- package/dist/plugins/internal/codex-resources/index.js +13 -1
- package/dist/plugins/internal/codex-resources/overview.js +136 -7
- package/dist/plugins/internal/codex-resources/rules.js +15 -2
- package/dist/plugins/internal/codex-resources/skills.js +16 -3
- package/dist/plugins/internal/llms-txt/assets.d.ts +24 -0
- package/dist/plugins/internal/llms-txt/assets.js +65 -0
- package/dist/plugins/internal/llms-txt/dev-middleware.d.ts +5 -0
- package/dist/plugins/internal/llms-txt/dev-middleware.js +9 -1
- package/dist/plugins/internal/llms-txt/emit.js +21 -5
- package/dist/plugins/internal/llms-txt/generate.d.ts +3 -3
- package/dist/plugins/internal/llms-txt/generate.js +55 -2
- package/dist/plugins/internal/llms-txt/index.d.ts +4 -1
- package/dist/plugins/internal/llms-txt/index.js +4 -0
- package/dist/plugins/internal/llms-txt/types.d.ts +24 -0
- package/dist/plugins/internal/llms-txt/types.js +4 -0
- package/dist/plugins/internal/resource-docs-shared/fs.d.ts +33 -0
- package/dist/plugins/internal/resource-docs-shared/fs.js +82 -1
- package/dist/plugins/internal/resource-docs-shared/index.d.ts +4 -2
- package/dist/plugins/internal/resource-docs-shared/index.js +21 -1
- package/dist/plugins/internal/resource-docs-shared/labels.d.ts +24 -0
- package/dist/plugins/internal/resource-docs-shared/labels.js +14 -0
- package/dist/plugins/internal/resource-docs-shared/locale-routes.d.ts +16 -0
- package/dist/plugins/internal/resource-docs-shared/locale-routes.js +14 -0
- package/dist/plugins/internal/resource-docs-shared/mdx.d.ts +7 -1
- package/dist/plugins/internal/resource-docs-shared/mdx.js +2 -2
- package/dist/plugins/internal/search-index/collect.js +46 -1
- package/dist/plugins/internal/search-index/types.d.ts +6 -1
- package/dist/plugins/llms-txt.js +9 -1
- package/dist/plugins/routes.js +14 -0
- package/dist/plugins/search-index.js +10 -1
- package/dist/preset.d.ts +9 -1
- package/dist/preset.js +25 -4
- package/dist/route-context/index.js +5 -17
- package/dist/route-enumerators/index.d.ts +14 -1
- package/dist/route-enumerators/index.js +38 -0
- package/dist/routes/locale-files-path.d.ts +29 -0
- package/dist/routes/locale-files-path.js +52 -0
- package/dist/safelist.css +1 -1
- package/dist/settings.d.ts +15 -0
- package/dist/tag-pages/index.js +2 -2
- package/eject/asset-components/asset-card.tsx +2 -0
- package/eject/asset-components/asset-code.tsx +2 -0
- package/eject/asset-components/shared.tsx +16 -0
- package/package.json +13 -9
- package/routes-src/locale-files-path.tsx +73 -0
|
@@ -128,7 +128,10 @@ function makeEnlargeableParagraph(imageEnlarge, ContentImg, assetOptions) {
|
|
|
128
128
|
href: assetViewerHref({
|
|
129
129
|
base: assetOptions.base,
|
|
130
130
|
routePrefix: assetOptions.routePrefix,
|
|
131
|
-
path: imageEntry.path
|
|
131
|
+
path: imageEntry.path,
|
|
132
|
+
locale: assetOptions.isDefaultLocaleOnlyPath?.(
|
|
133
|
+
`/${assetOptions.routePrefix}/${imageEntry.path}`
|
|
134
|
+
) ? void 0 : assetOptions.locale
|
|
132
135
|
}),
|
|
133
136
|
children: `\u2922 Open asset page${dimensions}`
|
|
134
137
|
},
|
|
@@ -160,6 +163,8 @@ function createMdxComponents(options) {
|
|
|
160
163
|
const {
|
|
161
164
|
settings,
|
|
162
165
|
assetManifest = null,
|
|
166
|
+
assetViewerLocale,
|
|
167
|
+
isDefaultLocaleOnlyPath,
|
|
163
168
|
locale,
|
|
164
169
|
currentVersion,
|
|
165
170
|
currentSlug,
|
|
@@ -176,6 +181,8 @@ function createMdxComponents(options) {
|
|
|
176
181
|
base: settings.base,
|
|
177
182
|
dir: assetDir,
|
|
178
183
|
routePrefix: assetRoutePrefix,
|
|
184
|
+
locale: assetViewerLocale,
|
|
185
|
+
isDefaultLocaleOnlyPath,
|
|
179
186
|
manifest: assetManifest
|
|
180
187
|
}
|
|
181
188
|
);
|
|
@@ -183,7 +190,9 @@ function createMdxComponents(options) {
|
|
|
183
190
|
base: settings.base,
|
|
184
191
|
assetManifest,
|
|
185
192
|
routePrefix: assetRoutePrefix,
|
|
186
|
-
dir: assetDir
|
|
193
|
+
dir: assetDir,
|
|
194
|
+
locale: assetViewerLocale,
|
|
195
|
+
isDefaultLocaleOnlyPath
|
|
187
196
|
}) : defaultComponents.a;
|
|
188
197
|
const CategoryNavBound = (props) => navData.CategoryNav({ ...props, lang: locale, currentVersion });
|
|
189
198
|
const CategoryTreeNavBound = (props) => navData.CategoryTreeNav({ ...props, lang: locale, currentVersion });
|
|
@@ -3,7 +3,7 @@ import { ChevronRight } from "../icons/index.js";
|
|
|
3
3
|
function VersionsPageContent(props) {
|
|
4
4
|
const { latestHref, versions, labels } = props;
|
|
5
5
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
6
|
-
/* @__PURE__ */ jsx("h1", { class: "text-heading font-bold mb-vsp-lg", children: labels.pageTitle }),
|
|
6
|
+
/* @__PURE__ */ jsx("h1", { class: "text-heading font-bold mb-vsp-lg break-words", children: labels.pageTitle }),
|
|
7
7
|
/* @__PURE__ */ jsxs("section", { class: "mb-vsp-xl", children: [
|
|
8
8
|
/* @__PURE__ */ jsx("h2", { class: "text-title font-bold mb-vsp-xs", children: labels.latestTitle }),
|
|
9
9
|
/* @__PURE__ */ jsx("p", { class: "text-small text-muted mb-vsp-sm", children: labels.latestDescription }),
|
|
@@ -13,17 +13,31 @@ const plugin = {
|
|
|
13
13
|
const projectRootOpt = ctx.options["projectRoot"];
|
|
14
14
|
const scanRootOpt = ctx.options["scanRoot"];
|
|
15
15
|
const docsDirOpt = ctx.options["docsDir"];
|
|
16
|
+
const localesOpt = ctx.options["locales"];
|
|
17
|
+
const defaultLocaleOpt = ctx.options["defaultLocale"];
|
|
18
|
+
const translationsOpt = ctx.options["translations"];
|
|
19
|
+
const defaultLocaleOnlyPrefixesOpt = ctx.options["defaultLocaleOnlyPrefixes"];
|
|
16
20
|
const result = await runClaudeResourcesPreStep({
|
|
17
21
|
claudeDir,
|
|
18
22
|
projectRoot: typeof projectRootOpt === "string" ? projectRootOpt : ctx.projectRoot,
|
|
19
23
|
scanRoot: typeof scanRootOpt === "string" ? scanRootOpt : void 0,
|
|
20
|
-
docsDir: typeof docsDirOpt === "string" ? docsDirOpt : "src/content/docs"
|
|
24
|
+
docsDir: typeof docsDirOpt === "string" ? docsDirOpt : "src/content/docs",
|
|
25
|
+
locales: isRecord(localesOpt) ? localesOpt : void 0,
|
|
26
|
+
defaultLocale: typeof defaultLocaleOpt === "string" ? defaultLocaleOpt : void 0,
|
|
27
|
+
translations: isRecord(translationsOpt) ? translationsOpt : void 0,
|
|
28
|
+
defaultLocaleOnlyPrefixes: isStringArray(defaultLocaleOnlyPrefixesOpt) ? defaultLocaleOnlyPrefixesOpt : void 0
|
|
21
29
|
});
|
|
22
30
|
ctx.logger.info(
|
|
23
31
|
`claude-resources: ${result.claudemd} CLAUDE.md, ${result.commands} commands, ${result.skills} skills, ${result.agents} agents`
|
|
24
32
|
);
|
|
25
33
|
}
|
|
26
34
|
};
|
|
35
|
+
function isRecord(value) {
|
|
36
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
37
|
+
}
|
|
38
|
+
function isStringArray(value) {
|
|
39
|
+
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
40
|
+
}
|
|
27
41
|
var claude_resources_default = plugin;
|
|
28
42
|
export {
|
|
29
43
|
claude_resources_default as default,
|
|
@@ -13,17 +13,31 @@ const plugin = {
|
|
|
13
13
|
const projectRootOpt = ctx.options["projectRoot"];
|
|
14
14
|
const scanRootOpt = ctx.options["scanRoot"];
|
|
15
15
|
const docsDirOpt = ctx.options["docsDir"];
|
|
16
|
+
const localesOpt = ctx.options["locales"];
|
|
17
|
+
const defaultLocaleOpt = ctx.options["defaultLocale"];
|
|
18
|
+
const translationsOpt = ctx.options["translations"];
|
|
19
|
+
const defaultLocaleOnlyPrefixesOpt = ctx.options["defaultLocaleOnlyPrefixes"];
|
|
16
20
|
const result = runCodexResourcesPreStep({
|
|
17
21
|
codexDir,
|
|
18
22
|
projectRoot: typeof projectRootOpt === "string" ? projectRootOpt : ctx.projectRoot,
|
|
19
23
|
scanRoot: typeof scanRootOpt === "string" ? scanRootOpt : void 0,
|
|
20
|
-
docsDir: typeof docsDirOpt === "string" ? docsDirOpt : "src/content/docs"
|
|
24
|
+
docsDir: typeof docsDirOpt === "string" ? docsDirOpt : "src/content/docs",
|
|
25
|
+
locales: isRecord(localesOpt) ? localesOpt : void 0,
|
|
26
|
+
defaultLocale: typeof defaultLocaleOpt === "string" ? defaultLocaleOpt : void 0,
|
|
27
|
+
translations: isRecord(translationsOpt) ? translationsOpt : void 0,
|
|
28
|
+
defaultLocaleOnlyPrefixes: isStringArray(defaultLocaleOnlyPrefixesOpt) ? defaultLocaleOnlyPrefixesOpt : void 0
|
|
21
29
|
});
|
|
22
30
|
ctx.logger.info(
|
|
23
31
|
`codex-resources: ${result.agentsMd} AGENTS.md, ${result.config} config, ${result.agents} agents, ${result.hooks} hooks, ${result.rules} rules, ${result.skills} skills`
|
|
24
32
|
);
|
|
25
33
|
}
|
|
26
34
|
};
|
|
35
|
+
function isRecord(value) {
|
|
36
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
37
|
+
}
|
|
38
|
+
function isStringArray(value) {
|
|
39
|
+
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
40
|
+
}
|
|
27
41
|
var codex_resources_default = plugin;
|
|
28
42
|
export {
|
|
29
43
|
codex_resources_default as default,
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { AssetViewerIndexingConfig } from "../../../settings.js";
|
|
2
|
+
/** The serialized asset-viewer settings shared by index consumers. */
|
|
3
|
+
export interface AssetScanProjection {
|
|
4
|
+
assetViewer: boolean;
|
|
5
|
+
assetViewerIndexing: AssetViewerIndexingConfig | false;
|
|
6
|
+
assetViewerDir: string;
|
|
7
|
+
assetViewerRoutePrefix: string;
|
|
8
|
+
assetViewerExclude: readonly string[];
|
|
9
|
+
base: string;
|
|
10
|
+
/** Non-default locales, keyed by locale code. */
|
|
11
|
+
locales: Record<string, {
|
|
12
|
+
dir?: string;
|
|
13
|
+
}>;
|
|
14
|
+
/** Route prefixes whose generated pages exist only in the default locale. */
|
|
15
|
+
defaultLocaleOnlyPrefixes: readonly string[];
|
|
16
|
+
}
|
|
17
|
+
/** The asset-page output currently supported by a consumer. */
|
|
18
|
+
export type AssetIndexConsumer = keyof AssetViewerIndexingConfig;
|
|
19
|
+
/** Minimal normalized descriptor consumed by search, llms.txt, and sitemap. */
|
|
20
|
+
export interface AssetPageDescriptor {
|
|
21
|
+
/** NFC-normalized POSIX path relative to `assetViewerDir`. */
|
|
22
|
+
path: string;
|
|
23
|
+
/** Base-prefixed, trailing-slashed generated asset-viewer URL. */
|
|
24
|
+
url: string;
|
|
25
|
+
/** Non-default locale segment; omitted for the default locale. */
|
|
26
|
+
locale?: string;
|
|
27
|
+
/** Whether the asset has a text-like extension understood by the viewer. */
|
|
28
|
+
isText: boolean;
|
|
29
|
+
/** Current byte size of the public asset. */
|
|
30
|
+
size: number;
|
|
31
|
+
}
|
|
32
|
+
export interface AssetPageDescriptorOptions {
|
|
33
|
+
/** Runtime project root injected by the zfb plugin wrapper. */
|
|
34
|
+
projectRoot: string;
|
|
35
|
+
/** Serialized projection from `zudoDocPreset()`. */
|
|
36
|
+
assetScan?: AssetScanProjection;
|
|
37
|
+
/** The output whose explicit indexing gate must be enabled. */
|
|
38
|
+
consumer: AssetIndexConsumer;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Scan the public asset directory and materialize one descriptor per emitted
|
|
42
|
+
* asset page. The default-locale descriptor is emitted first, followed by
|
|
43
|
+
* configured locales in object iteration order. Every descriptor URL is
|
|
44
|
+
* minted by `assetViewerHref()` so encoding, base, and trailing-slash rules
|
|
45
|
+
* cannot diverge between consumers.
|
|
46
|
+
*
|
|
47
|
+
* The descriptor path is intentionally synchronous. Search and llms.txt are
|
|
48
|
+
* synchronous build/dev paths, and `connect-adapter.ts` does not await a
|
|
49
|
+
* middleware's returned promise. Callers must pass the runtime `projectRoot`
|
|
50
|
+
* supplied by the plugin wrapper; it is never part of the serialized preset
|
|
51
|
+
* projection.
|
|
52
|
+
*/
|
|
53
|
+
export declare function collectAssetPageDescriptors(options: AssetPageDescriptorOptions): AssetPageDescriptor[];
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { closeSync, openSync, readSync, statSync } from "node:fs";
|
|
2
|
+
import { extname, resolve } from "node:path";
|
|
3
|
+
import { assetViewerHref } from "../../../asset-path/index.js";
|
|
4
|
+
import { scanAssetsSync } from "./scan.js";
|
|
5
|
+
const TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
6
|
+
"js",
|
|
7
|
+
"ts",
|
|
8
|
+
"tsx",
|
|
9
|
+
"jsx",
|
|
10
|
+
"mjs",
|
|
11
|
+
"cjs",
|
|
12
|
+
"json",
|
|
13
|
+
"yaml",
|
|
14
|
+
"yml",
|
|
15
|
+
"toml",
|
|
16
|
+
"css",
|
|
17
|
+
"scss",
|
|
18
|
+
"html",
|
|
19
|
+
"md",
|
|
20
|
+
"mdx",
|
|
21
|
+
"sh",
|
|
22
|
+
"bash",
|
|
23
|
+
"py",
|
|
24
|
+
"rb",
|
|
25
|
+
"go",
|
|
26
|
+
"rs",
|
|
27
|
+
"java",
|
|
28
|
+
"kt",
|
|
29
|
+
"swift",
|
|
30
|
+
"c",
|
|
31
|
+
"h",
|
|
32
|
+
"cpp",
|
|
33
|
+
"hpp",
|
|
34
|
+
"cs",
|
|
35
|
+
"php",
|
|
36
|
+
"sql",
|
|
37
|
+
"xml",
|
|
38
|
+
"txt",
|
|
39
|
+
"log",
|
|
40
|
+
"csv",
|
|
41
|
+
"tsv",
|
|
42
|
+
"env",
|
|
43
|
+
"conf",
|
|
44
|
+
"ini"
|
|
45
|
+
]);
|
|
46
|
+
function hasBinaryPrefix(absPath) {
|
|
47
|
+
const fd = openSync(absPath, "r");
|
|
48
|
+
try {
|
|
49
|
+
const sample = Buffer.allocUnsafe(8 * 1024);
|
|
50
|
+
const bytesRead = readSync(fd, sample, 0, sample.length, 0);
|
|
51
|
+
return sample.subarray(0, bytesRead).includes(0);
|
|
52
|
+
} finally {
|
|
53
|
+
closeSync(fd);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function isTextAsset(absPath, path) {
|
|
57
|
+
const extension = extname(path).slice(1).toLowerCase();
|
|
58
|
+
if (extension.length !== 0 && !TEXT_EXTENSIONS.has(extension)) return false;
|
|
59
|
+
return !hasBinaryPrefix(absPath);
|
|
60
|
+
}
|
|
61
|
+
function isDefaultLocaleOnlyAsset(routePrefix, path, prefixes) {
|
|
62
|
+
const routePath = assetViewerHref({ base: "/", routePrefix, path });
|
|
63
|
+
return prefixes.some((prefix) => routePath.startsWith(prefix));
|
|
64
|
+
}
|
|
65
|
+
function hasConsumerGate(assetScan, consumer) {
|
|
66
|
+
const indexing = assetScan.assetViewerIndexing;
|
|
67
|
+
return assetScan.assetViewer === true && indexing !== false && typeof indexing === "object" && indexing !== null && indexing[consumer] === true;
|
|
68
|
+
}
|
|
69
|
+
function collectAssetPageDescriptors(options) {
|
|
70
|
+
const { projectRoot, assetScan, consumer } = options;
|
|
71
|
+
if (assetScan === void 0 || !hasConsumerGate(assetScan, consumer)) return [];
|
|
72
|
+
const dir = assetScan.assetViewerDir;
|
|
73
|
+
const routePrefix = assetScan.assetViewerRoutePrefix;
|
|
74
|
+
const base = assetScan.base;
|
|
75
|
+
const exclude = assetScan.assetViewerExclude ?? [];
|
|
76
|
+
const paths = scanAssetsSync(projectRoot, dir, exclude);
|
|
77
|
+
const assetRoot = resolve(projectRoot, "public", dir);
|
|
78
|
+
const descriptorFor = (path, locale) => ({
|
|
79
|
+
path,
|
|
80
|
+
url: assetViewerHref({ base, routePrefix, path, ...locale === void 0 ? {} : { locale } }),
|
|
81
|
+
...locale === void 0 ? {} : { locale },
|
|
82
|
+
isText: isTextAsset(resolve(assetRoot, path), path),
|
|
83
|
+
size: statSync(resolve(assetRoot, path)).size
|
|
84
|
+
});
|
|
85
|
+
const descriptors = paths.map((path) => descriptorFor(path));
|
|
86
|
+
for (const locale of Object.keys(assetScan.locales ?? {})) {
|
|
87
|
+
for (const path of paths) {
|
|
88
|
+
if (isDefaultLocaleOnlyAsset(routePrefix, path, assetScan.defaultLocaleOnlyPrefixes ?? [])) {
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
descriptors.push(descriptorFor(path, locale));
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return descriptors;
|
|
95
|
+
}
|
|
96
|
+
export {
|
|
97
|
+
collectAssetPageDescriptors
|
|
98
|
+
};
|
|
@@ -1,2 +1,19 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
|
+
* Recursively enumerate safe, public assets in deterministic path order.
|
|
3
|
+
*
|
|
4
|
+
* This synchronous implementation is the canonical walk. Search and llms.txt
|
|
5
|
+
* build/dev consumers are synchronous, while the routes setup hook already
|
|
6
|
+
* exposes an async `scanAssets()` contract. Keeping one walk here gives both
|
|
7
|
+
* boundaries identical symlink, exclusion, and collision behavior.
|
|
8
|
+
*/
|
|
9
|
+
export declare function scanAssetsSync(projectRoot: string, dir: string, exclude?: readonly string[]): string[];
|
|
10
|
+
/**
|
|
11
|
+
* Async-compatible asset scan used by the routes setup hook.
|
|
12
|
+
*
|
|
13
|
+
* The route plugin's public lifecycle contract is async, but its scan itself
|
|
14
|
+
* is only filesystem enumeration. Delegating to the canonical sync walk keeps
|
|
15
|
+
* it behaviorally identical to `scanAssetsSync()` without introducing a
|
|
16
|
+
* second implementation (or a promise that synchronous middleware cannot
|
|
17
|
+
* consume).
|
|
18
|
+
*/
|
|
2
19
|
export declare function scanAssets(projectRoot: string, dir: string, exclude?: readonly string[]): Promise<string[]>;
|
|
@@ -1,11 +1,27 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readdirSync, realpathSync, statSync } from "node:fs";
|
|
2
2
|
import { basename, relative, resolve, sep } from "node:path";
|
|
3
3
|
import { matchExclude, normalizeAssetPath } from "../../../asset-path/index.js";
|
|
4
4
|
function isWithin(root, candidate) {
|
|
5
5
|
const rel = relative(root, candidate);
|
|
6
6
|
return rel === "" || !rel.startsWith(`..${sep}`) && rel !== "..";
|
|
7
7
|
}
|
|
8
|
-
|
|
8
|
+
function finishScan(paths) {
|
|
9
|
+
paths.sort((a, b) => a.localeCompare(b, "en"));
|
|
10
|
+
const folded = /* @__PURE__ */ new Map();
|
|
11
|
+
for (const path of paths) {
|
|
12
|
+
const key = path.toLocaleLowerCase("en-US");
|
|
13
|
+
const existing = folded.get(key);
|
|
14
|
+
if (existing !== void 0) {
|
|
15
|
+
throw new Error(`[asset-viewer] case-insensitive or Unicode-normalized URL collision: ${existing} and ${path}`);
|
|
16
|
+
}
|
|
17
|
+
folded.set(key, path);
|
|
18
|
+
if (path === "client" || path.startsWith("client/")) {
|
|
19
|
+
console.warn(`[asset-viewer] reserved client/ asset path: ${path}`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return paths;
|
|
23
|
+
}
|
|
24
|
+
function scanAssetsSync(projectRoot, dir, exclude = []) {
|
|
9
25
|
const publicRoot = resolve(projectRoot, "public");
|
|
10
26
|
const assetRoot = resolve(publicRoot, dir);
|
|
11
27
|
if (!isWithin(publicRoot, assetRoot)) {
|
|
@@ -13,8 +29,8 @@ async function scanAssets(projectRoot, dir, exclude = []) {
|
|
|
13
29
|
}
|
|
14
30
|
let canonicalRoot;
|
|
15
31
|
try {
|
|
16
|
-
const canonicalPublicRoot =
|
|
17
|
-
canonicalRoot =
|
|
32
|
+
const canonicalPublicRoot = realpathSync(publicRoot);
|
|
33
|
+
canonicalRoot = realpathSync(assetRoot);
|
|
18
34
|
if (!isWithin(canonicalPublicRoot, canonicalRoot)) {
|
|
19
35
|
throw new Error(`[asset-viewer] asset directory symlink escapes public/: ${dir}`);
|
|
20
36
|
}
|
|
@@ -23,29 +39,29 @@ async function scanAssets(projectRoot, dir, exclude = []) {
|
|
|
23
39
|
throw error;
|
|
24
40
|
}
|
|
25
41
|
const paths = [];
|
|
26
|
-
|
|
27
|
-
const canonicalDir =
|
|
42
|
+
function walk(absDir, relDir, canonicalAncestors) {
|
|
43
|
+
const canonicalDir = realpathSync(absDir);
|
|
28
44
|
if (!isWithin(canonicalRoot, canonicalDir)) {
|
|
29
45
|
throw new Error(`[asset-viewer] symlink escapes public/${dir}: ${relDir || basename(absDir)}`);
|
|
30
46
|
}
|
|
31
47
|
if (canonicalAncestors.has(canonicalDir)) return;
|
|
32
48
|
const nextAncestors = new Set(canonicalAncestors).add(canonicalDir);
|
|
33
|
-
const entries =
|
|
49
|
+
const entries = readdirSync(absDir, { withFileTypes: true });
|
|
34
50
|
entries.sort((a, b) => a.name.localeCompare(b.name, "en"));
|
|
35
51
|
for (const entry of entries) {
|
|
36
52
|
if (entry.name.startsWith(".")) continue;
|
|
37
53
|
const relPath = relDir ? `${relDir}/${entry.name}` : entry.name;
|
|
38
54
|
if (entry.name.endsWith(".meta.json")) continue;
|
|
39
55
|
const absPath = resolve(absDir, entry.name);
|
|
40
|
-
const canonical =
|
|
56
|
+
const canonical = realpathSync(absPath);
|
|
41
57
|
if (!isWithin(canonicalRoot, canonical)) {
|
|
42
58
|
throw new Error(`[asset-viewer] symlink escapes public/${dir}: ${relPath}`);
|
|
43
59
|
}
|
|
44
60
|
if (entry.isDirectory()) {
|
|
45
|
-
|
|
61
|
+
walk(absPath, relPath, nextAncestors);
|
|
46
62
|
} else if (entry.isSymbolicLink()) {
|
|
47
|
-
const target =
|
|
48
|
-
if (target.isDirectory())
|
|
63
|
+
const target = statSync(canonical);
|
|
64
|
+
if (target.isDirectory()) walk(absPath, relPath, nextAncestors);
|
|
49
65
|
else {
|
|
50
66
|
const path = normalizeAssetPath(relPath);
|
|
51
67
|
if (!matchExclude(path, exclude)) paths.push(path);
|
|
@@ -56,22 +72,13 @@ async function scanAssets(projectRoot, dir, exclude = []) {
|
|
|
56
72
|
}
|
|
57
73
|
}
|
|
58
74
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
const existing = folded.get(key);
|
|
65
|
-
if (existing !== void 0) {
|
|
66
|
-
throw new Error(`[asset-viewer] case-insensitive or Unicode-normalized URL collision: ${existing} and ${path}`);
|
|
67
|
-
}
|
|
68
|
-
folded.set(key, path);
|
|
69
|
-
if (path === "client" || path.startsWith("client/")) {
|
|
70
|
-
console.warn(`[asset-viewer] reserved client/ asset path: ${path}`);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
return paths;
|
|
75
|
+
walk(assetRoot, "", /* @__PURE__ */ new Set());
|
|
76
|
+
return finishScan(paths);
|
|
77
|
+
}
|
|
78
|
+
async function scanAssets(projectRoot, dir, exclude = []) {
|
|
79
|
+
return scanAssetsSync(projectRoot, dir, exclude);
|
|
74
80
|
}
|
|
75
81
|
export {
|
|
76
|
-
scanAssets
|
|
82
|
+
scanAssets,
|
|
83
|
+
scanAssetsSync
|
|
77
84
|
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type ResourceLocaleConfig, type ResourceTranslations } from "../resource-docs-shared/index.js";
|
|
1
2
|
export interface ClaudeResourcesConfig {
|
|
2
3
|
claudeDir: string;
|
|
3
4
|
/**
|
|
@@ -13,6 +14,14 @@ export interface ClaudeResourcesConfig {
|
|
|
13
14
|
*/
|
|
14
15
|
scanRoot?: string;
|
|
15
16
|
docsDir: string;
|
|
17
|
+
/** Additional locale content roots, resolved by the runner when possible. */
|
|
18
|
+
locales?: Record<string, ResourceLocaleConfig>;
|
|
19
|
+
/** Default locale code (the unprefixed docs directory). */
|
|
20
|
+
defaultLocale?: string;
|
|
21
|
+
/** UI-string translation table used by localized generated indexes. */
|
|
22
|
+
translations?: ResourceTranslations;
|
|
23
|
+
/** Route prefixes that must not receive non-default-locale indexes. */
|
|
24
|
+
defaultLocaleOnlyPrefixes?: string[];
|
|
16
25
|
}
|
|
17
26
|
export declare function generateClaudeResourcesDocs(config: ClaudeResourcesConfig): {
|
|
18
27
|
claudemd: number;
|
|
@@ -8,10 +8,38 @@ import {
|
|
|
8
8
|
escapeForMdx,
|
|
9
9
|
escapeTitle,
|
|
10
10
|
findNamedFiles,
|
|
11
|
+
formatFrontmatterString,
|
|
11
12
|
generateSkillsCategory,
|
|
12
13
|
parseFrontmatter,
|
|
13
|
-
|
|
14
|
+
removeGeneratedIndex,
|
|
15
|
+
resolveLocaleDirs,
|
|
16
|
+
resolveResourceLabel,
|
|
17
|
+
shouldEmitResourceLocaleRoute,
|
|
18
|
+
writeCategoryIndex,
|
|
19
|
+
writeGeneratedIndex
|
|
14
20
|
} from "../resource-docs-shared/index.js";
|
|
21
|
+
const DEFAULT_RESOURCE_LOCALE = "en";
|
|
22
|
+
function resourceLabel(config, locale, key, fallbackLiteral) {
|
|
23
|
+
return resolveResourceLabel({
|
|
24
|
+
translations: config.translations,
|
|
25
|
+
locale,
|
|
26
|
+
defaultLocale: config.defaultLocale,
|
|
27
|
+
key,
|
|
28
|
+
fallbackLiteral
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
function defaultResourceLabel(config, key, fallbackLiteral) {
|
|
32
|
+
return resourceLabel(
|
|
33
|
+
config,
|
|
34
|
+
config.defaultLocale ?? DEFAULT_RESOURCE_LOCALE,
|
|
35
|
+
key,
|
|
36
|
+
fallbackLiteral
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
function formatClaudeFrontmatterString(value) {
|
|
40
|
+
const formatted = formatFrontmatterString(value);
|
|
41
|
+
return formatted === value ? JSON.stringify(value) : formatted;
|
|
42
|
+
}
|
|
15
43
|
function generateClaudemdDocs(config) {
|
|
16
44
|
const projectRoot = config.projectRoot ?? config.claudeDir;
|
|
17
45
|
const scanRoot = config.scanRoot ?? projectRoot;
|
|
@@ -66,7 +94,16 @@ ${escapeForMdx(downgradeRepoRelativeLinks(content.trim()))}
|
|
|
66
94
|
`;
|
|
67
95
|
fs.writeFileSync(path.join(outputDir, `${item.slug}.mdx`), mdx);
|
|
68
96
|
});
|
|
69
|
-
writeCategoryIndex(
|
|
97
|
+
writeCategoryIndex(
|
|
98
|
+
outputDir,
|
|
99
|
+
defaultResourceLabel(config, "resource.claudeMd.label", "CLAUDE.md"),
|
|
100
|
+
900,
|
|
101
|
+
defaultResourceLabel(
|
|
102
|
+
config,
|
|
103
|
+
"resource.claudeMd.description",
|
|
104
|
+
"Project-specific instructions"
|
|
105
|
+
)
|
|
106
|
+
);
|
|
70
107
|
return items;
|
|
71
108
|
}
|
|
72
109
|
function generateCommandsDocs(config) {
|
|
@@ -101,16 +138,29 @@ ${escapeForMdx(parsed.content.trim())}
|
|
|
101
138
|
fs.writeFileSync(path.join(outputDir, `${name}.mdx`), mdx);
|
|
102
139
|
}
|
|
103
140
|
items.sort((a, b) => a.name.localeCompare(b.name));
|
|
104
|
-
writeCategoryIndex(
|
|
141
|
+
writeCategoryIndex(
|
|
142
|
+
outputDir,
|
|
143
|
+
defaultResourceLabel(config, "resource.claudeCommands.label", "Commands"),
|
|
144
|
+
901,
|
|
145
|
+
defaultResourceLabel(
|
|
146
|
+
config,
|
|
147
|
+
"resource.claudeCommands.description",
|
|
148
|
+
"Custom slash commands"
|
|
149
|
+
)
|
|
150
|
+
);
|
|
105
151
|
return items;
|
|
106
152
|
}
|
|
107
153
|
function generateSkillsDocs(config) {
|
|
108
154
|
return generateSkillsCategory({
|
|
109
155
|
skillsDirs: [path.join(config.claudeDir, "skills")],
|
|
110
156
|
outputDir: path.join(config.docsDir, "claude-skills"),
|
|
111
|
-
label: "Skills",
|
|
157
|
+
label: defaultResourceLabel(config, "resource.claudeSkills.label", "Skills"),
|
|
112
158
|
position: 902,
|
|
113
|
-
description:
|
|
159
|
+
description: defaultResourceLabel(
|
|
160
|
+
config,
|
|
161
|
+
"resource.claudeSkills.description",
|
|
162
|
+
"Skill packages"
|
|
163
|
+
),
|
|
114
164
|
sourceLabel: ".claude/skills"
|
|
115
165
|
});
|
|
116
166
|
}
|
|
@@ -151,7 +201,16 @@ ${escapeForMdx(parsed.content.trim())}
|
|
|
151
201
|
fs.writeFileSync(path.join(outputDir, `${fileSlug}.mdx`), mdx);
|
|
152
202
|
}
|
|
153
203
|
items.sort((a, b) => a.name.localeCompare(b.name));
|
|
154
|
-
writeCategoryIndex(
|
|
204
|
+
writeCategoryIndex(
|
|
205
|
+
outputDir,
|
|
206
|
+
defaultResourceLabel(config, "resource.claudeAgents.label", "Agents"),
|
|
207
|
+
903,
|
|
208
|
+
defaultResourceLabel(
|
|
209
|
+
config,
|
|
210
|
+
"resource.claudeAgents.description",
|
|
211
|
+
"Custom subagents"
|
|
212
|
+
)
|
|
213
|
+
);
|
|
155
214
|
return items;
|
|
156
215
|
}
|
|
157
216
|
function generateOverviewIndex(config, {
|
|
@@ -168,26 +227,137 @@ function generateOverviewIndex(config, {
|
|
|
168
227
|
if (hasSkills) categorySlugs.push("claude-skills");
|
|
169
228
|
if (hasAgents) categorySlugs.push("claude-agents");
|
|
170
229
|
if (hasCommands) categorySlugs.push("claude-commands");
|
|
171
|
-
const
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
230
|
+
const index = renderOverviewIndex(
|
|
231
|
+
config,
|
|
232
|
+
config.defaultLocale ?? DEFAULT_RESOURCE_LOCALE,
|
|
233
|
+
categorySlugs
|
|
234
|
+
);
|
|
235
|
+
fs.writeFileSync(path.join(outputDir, "index.mdx"), index);
|
|
236
|
+
for (const [locale, localeConfig] of Object.entries(config.locales ?? {})) {
|
|
237
|
+
emitLocaleCategoryIndexes(config, locale, localeConfig.dir, {
|
|
238
|
+
hasCommands,
|
|
239
|
+
hasSkills,
|
|
240
|
+
hasAgents,
|
|
241
|
+
hasClaudemd
|
|
242
|
+
});
|
|
243
|
+
const overviewPath = path.join(localeConfig.dir, "claude", "index.mdx");
|
|
244
|
+
if (shouldEmitResourceLocaleRoute({
|
|
245
|
+
slug: "claude",
|
|
246
|
+
locale,
|
|
247
|
+
defaultLocale: config.defaultLocale,
|
|
248
|
+
defaultLocaleOnlyPrefixes: config.defaultLocaleOnlyPrefixes
|
|
249
|
+
})) {
|
|
250
|
+
writeGeneratedIndex(
|
|
251
|
+
overviewPath,
|
|
252
|
+
renderOverviewIndex(config, locale, categorySlugs)
|
|
253
|
+
);
|
|
254
|
+
} else {
|
|
255
|
+
removeGeneratedIndex(overviewPath);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
function renderOverviewIndex(config, locale, categorySlugs) {
|
|
260
|
+
return `---
|
|
261
|
+
title: ${formatClaudeFrontmatterString(resourceLabel(config, locale, "resource.claude.title", "Claude"))}
|
|
262
|
+
description: ${formatClaudeFrontmatterString(resourceLabel(
|
|
263
|
+
config,
|
|
264
|
+
locale,
|
|
265
|
+
"resource.claude.description",
|
|
266
|
+
"Claude Code configuration reference."
|
|
267
|
+
))}
|
|
175
268
|
sidebar_position: 899
|
|
176
269
|
generated: true
|
|
177
270
|
---
|
|
178
271
|
|
|
179
|
-
## Resources
|
|
272
|
+
## ${escapeForMdx(resourceLabel(config, locale, "resource.resources", "Resources"))}
|
|
180
273
|
|
|
181
|
-
<CategoryNav categories={${
|
|
274
|
+
<CategoryNav categories={${JSON.stringify(categorySlugs)}} />
|
|
182
275
|
`;
|
|
183
|
-
|
|
276
|
+
}
|
|
277
|
+
function emitLocaleCategoryIndexes(config, locale, localeDir, presence) {
|
|
278
|
+
writeLocaleCategoryIndex(
|
|
279
|
+
config,
|
|
280
|
+
locale,
|
|
281
|
+
localeDir,
|
|
282
|
+
"claude-md",
|
|
283
|
+
presence.hasClaudemd,
|
|
284
|
+
"resource.claudeMd.label",
|
|
285
|
+
"CLAUDE.md",
|
|
286
|
+
900,
|
|
287
|
+
"resource.claudeMd.description",
|
|
288
|
+
"Project-specific instructions"
|
|
289
|
+
);
|
|
290
|
+
writeLocaleCategoryIndex(
|
|
291
|
+
config,
|
|
292
|
+
locale,
|
|
293
|
+
localeDir,
|
|
294
|
+
"claude-commands",
|
|
295
|
+
presence.hasCommands,
|
|
296
|
+
"resource.claudeCommands.label",
|
|
297
|
+
"Commands",
|
|
298
|
+
901,
|
|
299
|
+
"resource.claudeCommands.description",
|
|
300
|
+
"Custom slash commands"
|
|
301
|
+
);
|
|
302
|
+
writeLocaleCategoryIndex(
|
|
303
|
+
config,
|
|
304
|
+
locale,
|
|
305
|
+
localeDir,
|
|
306
|
+
"claude-skills",
|
|
307
|
+
presence.hasSkills,
|
|
308
|
+
"resource.claudeSkills.label",
|
|
309
|
+
"Skills",
|
|
310
|
+
902,
|
|
311
|
+
"resource.claudeSkills.description",
|
|
312
|
+
"Skill packages"
|
|
313
|
+
);
|
|
314
|
+
writeLocaleCategoryIndex(
|
|
315
|
+
config,
|
|
316
|
+
locale,
|
|
317
|
+
localeDir,
|
|
318
|
+
"claude-agents",
|
|
319
|
+
presence.hasAgents,
|
|
320
|
+
"resource.claudeAgents.label",
|
|
321
|
+
"Agents",
|
|
322
|
+
903,
|
|
323
|
+
"resource.claudeAgents.description",
|
|
324
|
+
"Custom subagents"
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
function writeLocaleCategoryIndex(config, locale, localeDir, categoryDir, present, labelKey, fallbackLabel, position, descriptionKey, fallbackDescription) {
|
|
328
|
+
const indexPath = path.join(localeDir, categoryDir, "index.mdx");
|
|
329
|
+
if (!present || !shouldEmitResourceLocaleRoute({
|
|
330
|
+
slug: categoryDir,
|
|
331
|
+
locale,
|
|
332
|
+
defaultLocale: config.defaultLocale,
|
|
333
|
+
defaultLocaleOnlyPrefixes: config.defaultLocaleOnlyPrefixes
|
|
334
|
+
})) {
|
|
335
|
+
removeGeneratedIndex(indexPath);
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
writeCategoryIndex(
|
|
339
|
+
path.join(localeDir, categoryDir),
|
|
340
|
+
resourceLabel(config, locale, labelKey, fallbackLabel),
|
|
341
|
+
position,
|
|
342
|
+
resourceLabel(config, locale, descriptionKey, fallbackDescription),
|
|
343
|
+
formatClaudeFrontmatterString,
|
|
344
|
+
writeGeneratedIndex
|
|
345
|
+
);
|
|
184
346
|
}
|
|
185
347
|
function generateClaudeResourcesDocs(config) {
|
|
186
|
-
const
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
348
|
+
const normalizedConfig = config.locales === void 0 ? config : {
|
|
349
|
+
...config,
|
|
350
|
+
locales: resolveLocaleDirs({
|
|
351
|
+
projectRoot: path.resolve(config.projectRoot ?? config.claudeDir),
|
|
352
|
+
docsDir: config.docsDir,
|
|
353
|
+
locales: config.locales
|
|
354
|
+
})
|
|
355
|
+
};
|
|
356
|
+
const claudemds = generateClaudemdDocs(normalizedConfig);
|
|
357
|
+
const commands = generateCommandsDocs(normalizedConfig);
|
|
358
|
+
const skills = generateSkillsDocs(normalizedConfig);
|
|
359
|
+
const agents = generateAgentsDocs(normalizedConfig);
|
|
360
|
+
generateOverviewIndex(normalizedConfig, {
|
|
191
361
|
hasClaudemd: claudemds.length > 0,
|
|
192
362
|
hasCommands: commands.length > 0,
|
|
193
363
|
hasSkills: skills.length > 0,
|