create-zudo-doc 0.2.21 → 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/dist/compose.d.ts +6 -3
- package/dist/compose.js +6 -4
- package/dist/features/image-enlarge.d.ts +22 -12
- package/dist/features/image-enlarge.js +23 -191
- package/dist/scaffold.d.ts +11 -0
- package/dist/scaffold.js +51 -24
- package/dist/settings-gen.js +4 -0
- package/dist/zfb-config-gen.d.ts +16 -10
- package/dist/zfb-config-gen.js +34 -239
- package/package.json +1 -1
- package/templates/base/pages/_mdx-components.ts +64 -185
- package/templates/base/pages/index.tsx +1 -1
- package/templates/base/pages/lib/_body-end-islands.tsx +6 -13
- package/templates/base/pages/lib/_category-nav.tsx +30 -115
- package/templates/base/pages/lib/_category-tree-nav.tsx +38 -65
- package/templates/base/pages/lib/_compose-meta-title.ts +2 -6
- package/templates/base/pages/lib/_doc-body-end.tsx +3 -35
- package/templates/base/pages/lib/_doc-content-header.tsx +10 -111
- package/templates/base/pages/lib/_doc-history-area.tsx +19 -211
- package/templates/base/pages/lib/_doc-metainfo-area.tsx +10 -113
- package/templates/base/pages/lib/_doc-page-renderer.tsx +22 -183
- package/templates/base/pages/lib/_doc-page-shell.tsx +17 -251
- package/templates/base/pages/lib/_doc-pager.tsx +4 -74
- package/templates/base/pages/lib/_doc-route-entries.ts +43 -177
- package/templates/base/pages/lib/_doc-route-paths.ts +16 -101
- package/templates/base/pages/lib/_doc-tags-area.tsx +14 -77
- package/templates/base/pages/lib/_extract-headings.ts +21 -295
- package/templates/base/pages/lib/_footer-with-defaults.tsx +37 -225
- package/templates/base/pages/lib/_frontmatter-preview-data.ts +5 -31
- package/templates/base/pages/lib/_head-with-defaults.tsx +13 -138
- package/templates/base/pages/lib/_header-with-defaults.tsx +24 -324
- package/templates/base/pages/lib/_inline-version-switcher.tsx +16 -78
- package/templates/base/pages/lib/_math-block.tsx +4 -63
- package/templates/base/pages/lib/_nav-data-prep.ts +32 -51
- package/templates/base/pages/lib/_nav-source-cache.ts +12 -57
- package/templates/base/pages/lib/_nav-source-docs.ts +33 -233
- package/templates/base/pages/lib/_search-widget-script.ts +2 -470
- package/templates/base/pages/lib/_search-widget.tsx +9 -195
- package/templates/base/pages/lib/_sidebar-prepaint.tsx +6 -59
- package/templates/base/pages/lib/_sidebar-with-defaults.tsx +16 -118
- package/templates/base/pages/lib/_site-tree-nav.tsx +29 -80
- package/templates/base/pages/lib/doc-page-props.ts +26 -44
- package/templates/base/pages/lib/locale-merge.ts +32 -145
- package/templates/base/pages/lib/route-enumerators.ts +52 -286
- package/templates/base/src/components/ai-chat-modal.tsx +9 -8
- package/templates/base/src/components/content/code-group.tsx +3 -76
- package/templates/base/src/components/content/content-admonition.tsx +4 -56
- package/templates/base/src/components/doc-history.tsx +9 -8
- package/templates/base/src/components/image-enlarge.tsx +11 -8
- package/templates/base/src/components/sidebar-toggle.tsx +6 -170
- package/templates/base/src/components/sidebar-tree.tsx +6 -548
- package/templates/base/src/config/color-scheme-utils.ts +34 -158
- package/templates/base/src/config/i18n.ts +9 -0
- package/templates/base/src/config/settings-types.ts +34 -172
- package/templates/base/src/config/z-index-tokens.ts +5 -4
- package/templates/base/src/styles/global.css +40 -587
- package/templates/base/src/utils/base.ts +1 -1
- package/templates/base/src/utils/docs.ts +47 -16
- package/templates/base/src/utils/github.ts +12 -9
- package/templates/base/src/utils/nav-scope.ts +13 -42
- package/templates/base/src/utils/sidebar.ts +18 -86
- package/templates/base/src/utils/slug.ts +10 -53
- package/templates/base/src/utils/smart-break.tsx +12 -120
- package/templates/base/src/utils/tags.ts +25 -68
- package/templates/features/bodyFootUtil/files/src/utils/github.ts +12 -9
- package/templates/features/designTokenPanel/files/src/lib/design-token-panel-bootstrap.ts +13 -39
- package/templates/features/docHistory/files/src/components/doc-history.tsx +8 -636
- package/templates/features/docHistory/files/src/types/doc-history.ts +7 -23
- package/templates/features/docTags/files/pages/lib/_tag-pages.tsx +35 -201
- package/templates/features/i18n/files/pages/[locale]/index.tsx +1 -1
- package/templates/features/imageEnlarge/files/src/components/image-enlarge.tsx +8 -269
- package/templates/features/sidebarToggle/files/src/components/desktop-sidebar-toggle.tsx +6 -99
- package/templates/features/tagGovernance/files/scripts/tags-audit.ts +67 -515
- package/templates/features/versioning/files/pages/lib/_versions-page.tsx +21 -73
- package/templates/base/pages/404.tsx +0 -61
- package/templates/base/pages/robots.txt.tsx +0 -29
- package/templates/base/pages/sitemap.xml.tsx +0 -59
- package/templates/base/plugins/connect-adapter.mjs +0 -169
- package/templates/base/plugins/copy-public-plugin.mjs +0 -58
- package/templates/base/plugins/search-index-plugin.mjs +0 -66
- package/templates/base/scripts/gen-z-index.mjs +0 -157
- package/templates/base/src/components/mermaid-enlarge.tsx +0 -490
- package/templates/base/src/components/site-tree-nav.tsx +0 -220
- package/templates/features/claudeResources/files/plugins/claude-resources-plugin.mjs +0 -47
- package/templates/features/docHistory/files/plugins/doc-history-plugin.mjs +0 -111
- package/templates/features/docTags/files/pages/[locale]/docs/tags/[tag].tsx +0 -59
- package/templates/features/docTags/files/pages/[locale]/docs/tags/index.tsx +0 -39
- package/templates/features/docTags/files/pages/docs/tags/[tag].tsx +0 -43
- package/templates/features/docTags/files/pages/docs/tags/index.tsx +0 -20
- package/templates/features/llmsTxt/files/plugins/llms-txt-plugin.mjs +0 -93
- package/templates/features/versioning/files/pages/[locale]/docs/versions.tsx +0 -48
- package/templates/features/versioning/files/pages/docs/versions.tsx +0 -20
|
@@ -1,85 +1,33 @@
|
|
|
1
1
|
/** @jsxRuntime automatic */
|
|
2
2
|
/** @jsxImportSource preact */
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
// that own only their paths() param shapes; URL prefixes and the
|
|
9
|
-
// default-vs-locale href shapes live here.
|
|
3
|
+
// Thin stub — versions-page moved to the package (epic #2344, S8).
|
|
4
|
+
// Calls `createVersionsPageView(deps)` from
|
|
5
|
+
// @takazudo/zudo-doc/versions-page with host singletons injected,
|
|
6
|
+
// then re-exports the resulting component so all existing call sites continue
|
|
7
|
+
// to work unchanged.
|
|
10
8
|
|
|
9
|
+
import { createVersionsPageView } from "@takazudo/zudo-doc/versions-page";
|
|
11
10
|
import { settings } from "@/config/settings";
|
|
12
|
-
import { defaultLocale, t
|
|
11
|
+
import { defaultLocale, t } from "@/config/i18n";
|
|
13
12
|
import { withBase } from "@/utils/base";
|
|
14
|
-
import { DocLayoutWithDefaults } from "@takazudo/zudo-doc/doclayout";
|
|
15
|
-
import { VersionsPageContent } from "@takazudo/zudo-doc/nav-indexing";
|
|
16
|
-
import type { VersionPageEntry, VersionsPageLabels } from "@takazudo/zudo-doc/nav-indexing";
|
|
17
|
-
import type { JSX } from "preact";
|
|
18
13
|
import { FooterWithDefaults } from "./_footer-with-defaults";
|
|
19
14
|
import { HeaderWithDefaults } from "./_header-with-defaults";
|
|
20
15
|
import { HeadWithDefaults } from "./_head-with-defaults";
|
|
21
16
|
import { composeMetaTitle } from "./_compose-meta-title";
|
|
22
17
|
import { BodyEndIslands } from "./_body-end-islands";
|
|
23
18
|
|
|
24
|
-
|
|
25
|
-
* versions configured in settings.versions. */
|
|
26
|
-
export function VersionsPageView({ locale }: { locale: string }): JSX.Element {
|
|
27
|
-
const isDefault = locale === defaultLocale;
|
|
28
|
-
const prefix = isDefault ? "" : `/${locale}`;
|
|
29
|
-
const pageTitle = t("version.page.title", locale);
|
|
19
|
+
export { type VersionsPageViewProps } from "@takazudo/zudo-doc/versions-page";
|
|
30
20
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
// Latest docs href — points to the default docs entry point
|
|
46
|
-
const latestHref = withBase(`${prefix}/docs/getting-started`);
|
|
47
|
-
|
|
48
|
-
// Past version entries from settings
|
|
49
|
-
const versions: VersionPageEntry[] = settings.versions
|
|
50
|
-
? settings.versions.map((v) => ({
|
|
51
|
-
slug: v.slug,
|
|
52
|
-
label: v.label ?? v.slug,
|
|
53
|
-
// Version prefix comes BEFORE the locale — the only routed shape is
|
|
54
|
-
// pages/v/[version]/{locale}/docs/...; /{locale}/v/... has no route.
|
|
55
|
-
docsHref: withBase(`/v/${v.slug}${prefix}/docs/getting-started/`),
|
|
56
|
-
banner: v.banner as "unmaintained" | "unreleased" | undefined,
|
|
57
|
-
}))
|
|
58
|
-
: [];
|
|
59
|
-
|
|
60
|
-
return (
|
|
61
|
-
<DocLayoutWithDefaults
|
|
62
|
-
title={composeMetaTitle(pageTitle)}
|
|
63
|
-
head={<HeadWithDefaults title={pageTitle} />}
|
|
64
|
-
lang={locale}
|
|
65
|
-
noindex={settings.noindex}
|
|
66
|
-
hideSidebar={true}
|
|
67
|
-
hideToc={true}
|
|
68
|
-
// Empty fragment suppresses DocLayoutWithDefaults' empty-data default
|
|
69
|
-
// Sidebar island — its marker never hydrates for published-package
|
|
70
|
-
// consumers (zfb#999) and zfb >= next.38 warns about it; the sidebar is
|
|
71
|
-
// hidden on this page anyway (zudolab/zudo-doc#2057).
|
|
72
|
-
sidebarOverride={<></>}
|
|
73
|
-
headerOverride={<HeaderWithDefaults lang={locale as Locale} currentPath={withBase(`${prefix}/docs/versions`)} />}
|
|
74
|
-
footerOverride={<FooterWithDefaults lang={locale} />}
|
|
75
|
-
bodyEndComponents={<BodyEndIslands basePath={settings.base ?? "/"} />}
|
|
76
|
-
enableClientRouter={settings.dynamicPageTransition}
|
|
77
|
-
>
|
|
78
|
-
<VersionsPageContent
|
|
79
|
-
latestHref={latestHref}
|
|
80
|
-
versions={versions}
|
|
81
|
-
labels={labels}
|
|
82
|
-
/>
|
|
83
|
-
</DocLayoutWithDefaults>
|
|
84
|
-
);
|
|
85
|
-
}
|
|
21
|
+
export const VersionsPageView = createVersionsPageView({
|
|
22
|
+
settings,
|
|
23
|
+
defaultLocale,
|
|
24
|
+
t,
|
|
25
|
+
withBase,
|
|
26
|
+
composeMetaTitle,
|
|
27
|
+
components: {
|
|
28
|
+
HeadWithDefaults,
|
|
29
|
+
HeaderWithDefaults,
|
|
30
|
+
FooterWithDefaults,
|
|
31
|
+
BodyEndIslands,
|
|
32
|
+
},
|
|
33
|
+
});
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
/** @jsxRuntime automatic */
|
|
2
|
-
/** @jsxImportSource preact */
|
|
3
|
-
// Page module for the 404 route.
|
|
4
|
-
//
|
|
5
|
-
// The 404 page is a static route with no dynamic params. zfb emits it as
|
|
6
|
-
// dist/404.html so the host platform (Cloudflare Pages, Netlify, etc.) can
|
|
7
|
-
// serve it for unmatched requests. No paths() export needed.
|
|
8
|
-
//
|
|
9
|
-
// The original Astro page rendered the full html/head/body inline without
|
|
10
|
-
// DocLayout because the 404 page intentionally has no sidebar/TOC/header.
|
|
11
|
-
// This port wraps via DocLayoutWithDefaults with hideSidebar/hideToc plus
|
|
12
|
-
// a noindex meta so search engines do not index it.
|
|
13
|
-
|
|
14
|
-
import { defaultLocale } from "@/config/i18n";
|
|
15
|
-
import { settings } from "@/config/settings";
|
|
16
|
-
import { withBase } from "@/utils/base";
|
|
17
|
-
import { DocLayoutWithDefaults } from "@takazudo/zudo-doc/doclayout";
|
|
18
|
-
import type { JSX } from "preact";
|
|
19
|
-
import { FooterWithDefaults } from "./lib/_footer-with-defaults";
|
|
20
|
-
import { HeaderWithDefaults } from "./lib/_header-with-defaults";
|
|
21
|
-
import { HeadWithDefaults } from "./lib/_head-with-defaults";
|
|
22
|
-
import { composeMetaTitle } from "./lib/_compose-meta-title";
|
|
23
|
-
import { BodyEndIslands } from "./lib/_body-end-islands";
|
|
24
|
-
|
|
25
|
-
export const frontmatter = { title: "404" };
|
|
26
|
-
|
|
27
|
-
export default function NotFoundPage(): JSX.Element {
|
|
28
|
-
const locale = defaultLocale;
|
|
29
|
-
const title = "Page Not Found";
|
|
30
|
-
|
|
31
|
-
return (
|
|
32
|
-
<DocLayoutWithDefaults
|
|
33
|
-
title={composeMetaTitle(title)}
|
|
34
|
-
head={<HeadWithDefaults title={title} />}
|
|
35
|
-
lang={locale}
|
|
36
|
-
noindex={true}
|
|
37
|
-
hideSidebar={true}
|
|
38
|
-
hideToc={true}
|
|
39
|
-
// Empty fragment suppresses DocLayoutWithDefaults' empty-data default
|
|
40
|
-
// Sidebar island — its marker never hydrates for published-package
|
|
41
|
-
// consumers (zfb#999) and zfb >= next.38 warns about it; the sidebar is
|
|
42
|
-
// hidden on this page anyway (zudolab/zudo-doc#2057).
|
|
43
|
-
sidebarOverride={<></>}
|
|
44
|
-
headerOverride={<HeaderWithDefaults lang={locale} />}
|
|
45
|
-
footerOverride={<FooterWithDefaults lang={locale} />}
|
|
46
|
-
bodyEndComponents={<BodyEndIslands basePath={settings.base ?? "/"} />}
|
|
47
|
-
enableClientRouter={settings.dynamicPageTransition}
|
|
48
|
-
>
|
|
49
|
-
<div class="min-h-[60vh] flex flex-col items-center justify-center px-hsp-2xl py-vsp-xl">
|
|
50
|
-
<h1 class="text-display font-bold mb-vsp-md">404</h1>
|
|
51
|
-
<p class="text-title text-muted mb-vsp-xl">Page not found.</p>
|
|
52
|
-
<a
|
|
53
|
-
href={withBase("/")}
|
|
54
|
-
class="bg-accent px-hsp-lg py-vsp-xs font-medium text-bg hover:bg-accent-hover"
|
|
55
|
-
>
|
|
56
|
-
Back to Home
|
|
57
|
-
</a>
|
|
58
|
-
</div>
|
|
59
|
-
</DocLayoutWithDefaults>
|
|
60
|
-
);
|
|
61
|
-
}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
// Prerendered robots.txt page route, gated by settings.noindex.
|
|
2
|
-
//
|
|
3
|
-
// Filename → output extension mapping (zfb convention): the
|
|
4
|
-
// second-to-last `.`-separated segment of the stem becomes the output
|
|
5
|
-
// extension, so `robots.txt.tsx` builds `dist/robots.txt`. The
|
|
6
|
-
// explicit `contentType` export pins the dev-server Content-Type
|
|
7
|
-
// header to `text/plain` regardless of the filename hint.
|
|
8
|
-
|
|
9
|
-
import { settings } from "@/config/settings";
|
|
10
|
-
|
|
11
|
-
export const frontmatter = { title: "Robots" };
|
|
12
|
-
export const contentType = "text/plain";
|
|
13
|
-
|
|
14
|
-
export default function Robots(): string {
|
|
15
|
-
if (settings.noindex) {
|
|
16
|
-
// noindex: disallow all crawlers. Omit Sitemap: — advertising a sitemap
|
|
17
|
-
// while disallowing all crawlers is contradictory.
|
|
18
|
-
return `User-agent: *\nDisallow: /\n`;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const siteUrlBase = (settings.siteUrl ?? "").replace(/\/$/, "");
|
|
22
|
-
const hasSitemapLine = siteUrlBase !== "" && settings.sitemap;
|
|
23
|
-
|
|
24
|
-
const sitemapLine = hasSitemapLine
|
|
25
|
-
? `Sitemap: ${siteUrlBase}/sitemap.xml\n`
|
|
26
|
-
: "";
|
|
27
|
-
|
|
28
|
-
return `User-agent: *\nAllow: /\n${sitemapLine}`;
|
|
29
|
-
}
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
// Port of `src/integrations/sitemap.ts` onto zfb's pages-style filename
|
|
2
|
-
// convention.
|
|
3
|
-
//
|
|
4
|
-
// Filename → output extension mapping (zfb convention): the
|
|
5
|
-
// second-to-last `.`-separated segment of the stem becomes the output
|
|
6
|
-
// extension, so `sitemap.xml.tsx` builds `dist/sitemap.xml`. The
|
|
7
|
-
// explicit `contentType` export pins the dev-server `Content-Type`
|
|
8
|
-
// header to `application/xml` regardless of the filename hint.
|
|
9
|
-
//
|
|
10
|
-
// URL enumeration is delegated to `pages/lib/route-enumerators.ts` so
|
|
11
|
-
// the sitemap cannot drift from the actual routes the page modules build.
|
|
12
|
-
// Previously the sitemap walked raw collection slugs directly and missed:
|
|
13
|
-
// (a) tag pages (b) JA fallback URLs
|
|
14
|
-
// (c) versioned JA routes (d) wrong URL pattern for versioned-locale pages
|
|
15
|
-
// (e) emitted /index/ suffix on category pages (closes #690)
|
|
16
|
-
|
|
17
|
-
import { settings } from "@/config/settings";
|
|
18
|
-
import { enumerateAllRoutes } from "./lib/route-enumerators";
|
|
19
|
-
|
|
20
|
-
export const frontmatter = { title: "Sitemap" };
|
|
21
|
-
export const contentType = "application/xml";
|
|
22
|
-
|
|
23
|
-
function escapeXml(str: string): string {
|
|
24
|
-
return str
|
|
25
|
-
.replace(/&/g, "&")
|
|
26
|
-
.replace(/</g, "<")
|
|
27
|
-
.replace(/>/g, ">")
|
|
28
|
-
.replace(/"/g, """)
|
|
29
|
-
.replace(/'/g, "'");
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export default function Sitemap(): string {
|
|
33
|
-
// When sitemap is disabled, return an empty urlset so the route still
|
|
34
|
-
// resolves (returns XML, not 404) but contains no URLs.
|
|
35
|
-
if (!settings.sitemap) {
|
|
36
|
-
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
37
|
-
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
|
38
|
-
</urlset>`;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const routeMap = enumerateAllRoutes();
|
|
42
|
-
const siteUrlBase = (settings.siteUrl ?? "").replace(/\/$/, "");
|
|
43
|
-
|
|
44
|
-
const urlEntries = [...routeMap.entries()]
|
|
45
|
-
.sort(([a], [b]) => a.localeCompare(b))
|
|
46
|
-
.map(
|
|
47
|
-
([url, lastmod]) => ` <url>
|
|
48
|
-
<loc>${escapeXml(siteUrlBase + url)}</loc>
|
|
49
|
-
<lastmod>${lastmod}</lastmod>
|
|
50
|
-
</url>`,
|
|
51
|
-
)
|
|
52
|
-
.join("\n");
|
|
53
|
-
|
|
54
|
-
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
55
|
-
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
|
56
|
-
${urlEntries}
|
|
57
|
-
</urlset>
|
|
58
|
-
`;
|
|
59
|
-
}
|
|
@@ -1,169 +0,0 @@
|
|
|
1
|
-
// @ts-check
|
|
2
|
-
// Adapter from Connect-style middleware (`(req, res, next) => void`) to
|
|
3
|
-
// the request-response shape zfb's `devMiddleware` lifecycle hook
|
|
4
|
-
// expects (`(req: ZfbDevMiddlewareRequest) => Promise<ZfbDevMiddlewareResponse | undefined>`).
|
|
5
|
-
//
|
|
6
|
-
// zfb's plugin host runs in a separate Node subprocess and only sees a
|
|
7
|
-
// JSON envelope of the request — `{ method, url, headers, body? }` —
|
|
8
|
-
// not real Node IPC. The adapter mocks just enough of `IncomingMessage`
|
|
9
|
-
// and `ServerResponse` for the v2 integration middlewares (which were
|
|
10
|
-
// written against Node's `http` types) to think they are talking to a
|
|
11
|
-
// regular HTTP server, then captures the response status / headers /
|
|
12
|
-
// body and returns them in the shape the host expects.
|
|
13
|
-
//
|
|
14
|
-
// The adapter is shared by every plugin module under this directory so
|
|
15
|
-
// any future Connect-style middleware can be wired into zfb's
|
|
16
|
-
// devMiddleware hook without rewriting it. Lives at the host repo —
|
|
17
|
-
// the v2 integration package keeps its Connect-style API surface so
|
|
18
|
-
// non-zfb embedders (Astro, plain Vite, a unit test) continue to work.
|
|
19
|
-
|
|
20
|
-
/** @import { ZfbDevMiddlewareRequest, ZfbDevMiddlewareResponse } from "@takazudo/zfb/plugins" */
|
|
21
|
-
|
|
22
|
-
import { Buffer } from "node:buffer";
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Connect-style middleware: `(req, res, next) => void`.
|
|
26
|
-
* The req/res parameters are typed as `any` here because this adapter
|
|
27
|
-
* intentionally passes a plain-object shim that structurally satisfies the
|
|
28
|
-
* subset of `IncomingMessage` / `ServerResponse` that the v2 integration
|
|
29
|
-
* middlewares actually access — not the full Node.js types.
|
|
30
|
-
* @typedef {(req: any, res: any, next: (err?: unknown) => void) => void} ConnectMiddleware
|
|
31
|
-
*/
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Convert a Connect-style middleware to a zfb devMiddleware handler.
|
|
35
|
-
* The returned async function takes a `ZfbDevMiddlewareRequest` and
|
|
36
|
-
* returns either `undefined` (passthrough — zfb falls through to its
|
|
37
|
-
* built-in routes) or a `ZfbDevMiddlewareResponse` envelope.
|
|
38
|
-
*
|
|
39
|
-
* Behaviour:
|
|
40
|
-
*
|
|
41
|
-
* - `next()` from the middleware → resolves with `undefined`
|
|
42
|
-
* (passthrough).
|
|
43
|
-
* - `res.end(body)` → resolves with `{ status, headers, body }`.
|
|
44
|
-
* `status` defaults to 200 if the middleware didn't set one,
|
|
45
|
-
* mirroring Node's `ServerResponse` default.
|
|
46
|
-
* - `next(err)` or a thrown error → rejects so the host surfaces a
|
|
47
|
-
* 500 with the error message the same way it does for any other
|
|
48
|
-
* plugin throw.
|
|
49
|
-
* - Binary bodies (Buffer / Uint8Array) → encoded as base64 and
|
|
50
|
-
* flagged `bodyEncoding: "base64"` so the JSON envelope round-trip
|
|
51
|
-
* stays loss-less.
|
|
52
|
-
*
|
|
53
|
-
* @param {ConnectMiddleware} middleware
|
|
54
|
-
* @returns {(zfbReq: ZfbDevMiddlewareRequest) => Promise<ZfbDevMiddlewareResponse | undefined>}
|
|
55
|
-
*/
|
|
56
|
-
export function connectToZfbHandler(middleware) {
|
|
57
|
-
return (zfbReq) => {
|
|
58
|
-
return new Promise((resolveResponse, rejectResponse) => {
|
|
59
|
-
// Build a minimal `IncomingMessage` shim. Only the fields the v2
|
|
60
|
-
// integration middlewares actually read are populated — `method`,
|
|
61
|
-
// `url`, and `headers`. Body parsing is not used by any of the
|
|
62
|
-
// three middlewares (they're all GET routes), so we leave the
|
|
63
|
-
// stream surface unimplemented.
|
|
64
|
-
const req = {
|
|
65
|
-
method: zfbReq.method,
|
|
66
|
-
url: zfbReq.url,
|
|
67
|
-
headers: zfbReq.headers ?? {},
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
// Build a `ServerResponse` shim that captures status, headers,
|
|
71
|
-
// and body. We expose the API surface the v2 middlewares touch
|
|
72
|
-
// today (`statusCode`, `setHeader`, `getHeader`, `end`) — extend
|
|
73
|
-
// here if a future middleware needs more.
|
|
74
|
-
let statusCode = 200;
|
|
75
|
-
/** @type {Record<string, string>} */
|
|
76
|
-
const headers = {};
|
|
77
|
-
let settled = false;
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* @param {string | Buffer | Uint8Array | null | undefined} body
|
|
81
|
-
* @returns {void}
|
|
82
|
-
*/
|
|
83
|
-
const finish = (body) => {
|
|
84
|
-
if (settled) return;
|
|
85
|
-
settled = true;
|
|
86
|
-
// Lower-case header names so the host's response shape matches
|
|
87
|
-
// axum's expectation (`Record<string, string>` of arbitrary
|
|
88
|
-
// case). Last-wins on collision; with `setHeader` callers this
|
|
89
|
-
// shouldn't happen.
|
|
90
|
-
/** @type {Record<string, string>} */
|
|
91
|
-
const normalisedHeaders = {};
|
|
92
|
-
for (const [k, v] of Object.entries(headers)) {
|
|
93
|
-
normalisedHeaders[k.toLowerCase()] = String(v);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
if (Buffer.isBuffer(body) || body instanceof Uint8Array) {
|
|
97
|
-
resolveResponse({
|
|
98
|
-
status: statusCode,
|
|
99
|
-
headers: normalisedHeaders,
|
|
100
|
-
body: Buffer.from(body).toString("base64"),
|
|
101
|
-
bodyEncoding: "base64",
|
|
102
|
-
});
|
|
103
|
-
return;
|
|
104
|
-
}
|
|
105
|
-
resolveResponse({
|
|
106
|
-
status: statusCode,
|
|
107
|
-
headers: normalisedHeaders,
|
|
108
|
-
body: body == null ? "" : String(body),
|
|
109
|
-
bodyEncoding: "utf8",
|
|
110
|
-
});
|
|
111
|
-
};
|
|
112
|
-
|
|
113
|
-
const res = {
|
|
114
|
-
get statusCode() {
|
|
115
|
-
return statusCode;
|
|
116
|
-
},
|
|
117
|
-
/** @param {number} v */
|
|
118
|
-
set statusCode(v) {
|
|
119
|
-
statusCode = v;
|
|
120
|
-
},
|
|
121
|
-
/**
|
|
122
|
-
* @param {string} name
|
|
123
|
-
* @param {string | number | readonly string[]} value
|
|
124
|
-
*/
|
|
125
|
-
setHeader(name, value) {
|
|
126
|
-
// Store under lowercased key to avoid duplicate-case collisions
|
|
127
|
-
// when finish() merges headers — last-wins is then unambiguous.
|
|
128
|
-
headers[name.toLowerCase()] = String(value);
|
|
129
|
-
},
|
|
130
|
-
/** @param {string} name */
|
|
131
|
-
getHeader(name) {
|
|
132
|
-
// Keys are always stored lowercased (see setHeader), so a direct
|
|
133
|
-
// lowercased lookup is sufficient and avoids an O(n) scan.
|
|
134
|
-
return headers[name.toLowerCase()];
|
|
135
|
-
},
|
|
136
|
-
get headersSent() {
|
|
137
|
-
return settled;
|
|
138
|
-
},
|
|
139
|
-
/** @param {string | Buffer | Uint8Array | null | undefined} [body] */
|
|
140
|
-
end(body) {
|
|
141
|
-
finish(body);
|
|
142
|
-
},
|
|
143
|
-
};
|
|
144
|
-
|
|
145
|
-
/** @param {unknown} [err] */
|
|
146
|
-
const next = (err) => {
|
|
147
|
-
if (settled) return;
|
|
148
|
-
if (err) {
|
|
149
|
-
settled = true;
|
|
150
|
-
rejectResponse(err instanceof Error ? err : new Error(String(err)));
|
|
151
|
-
return;
|
|
152
|
-
}
|
|
153
|
-
// Connect's `next()` with no error means "I did not handle
|
|
154
|
-
// this request". Resolve with `undefined` so zfb's host
|
|
155
|
-
// surfaces a passthrough.
|
|
156
|
-
settled = true;
|
|
157
|
-
resolveResponse(undefined);
|
|
158
|
-
};
|
|
159
|
-
|
|
160
|
-
try {
|
|
161
|
-
middleware(req, res, next);
|
|
162
|
-
} catch (err) {
|
|
163
|
-
if (settled) return;
|
|
164
|
-
settled = true;
|
|
165
|
-
rejectResponse(err instanceof Error ? err : new Error(String(err)));
|
|
166
|
-
}
|
|
167
|
-
});
|
|
168
|
-
};
|
|
169
|
-
}
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
// @ts-check
|
|
2
|
-
// zfb plugin module: copy-public.
|
|
3
|
-
//
|
|
4
|
-
// Workaround for upstream zfb gap — `zfb build` does not copy `public/`
|
|
5
|
-
// contents to `outDir`. See: zudolab/zudo-doc#1394;
|
|
6
|
-
// upstream issue: https://github.com/Takazudo/zudo-front-builder/issues/158
|
|
7
|
-
//
|
|
8
|
-
// postBuild — recursively copies `<projectRoot>/public/` directly into
|
|
9
|
-
// `<outDir>/` (FLAT, matching zfb's own dist/ convention —
|
|
10
|
-
// zfb emits dist/index.html, dist/assets/..., NOT
|
|
11
|
-
// dist/<base>/index.html). Under the Workers static assets
|
|
12
|
-
// deploy (base="/"), `dist/` is served at root directly by
|
|
13
|
-
// `wrangler deploy` — no deploy-pipeline relocation step is
|
|
14
|
-
// needed. The `base` option is intentionally unused here.
|
|
15
|
-
//
|
|
16
|
-
// Example: `public/img/logo.svg` becomes `dist/img/logo.svg`,
|
|
17
|
-
// served at `/img/logo.svg` by the Workers static asset layer.
|
|
18
|
-
//
|
|
19
|
-
// Missing or empty `public/` is treated as a no-op (no error).
|
|
20
|
-
//
|
|
21
|
-
// `options` carries `{ publicDir }` from the matching entry in
|
|
22
|
-
// `zfb.config.ts`. The `base` option is intentionally unused — see
|
|
23
|
-
// rationale above.
|
|
24
|
-
|
|
25
|
-
/** @import { ZfbBuildHookContext, ZfbPlugin } from "@takazudo/zfb/plugins" */
|
|
26
|
-
|
|
27
|
-
import { cp } from "node:fs/promises";
|
|
28
|
-
import { resolve } from "node:path";
|
|
29
|
-
|
|
30
|
-
/** @type {ZfbPlugin} */
|
|
31
|
-
export default {
|
|
32
|
-
name: "copy-public",
|
|
33
|
-
|
|
34
|
-
/** @param {ZfbBuildHookContext} ctx */
|
|
35
|
-
async postBuild(ctx) {
|
|
36
|
-
const { publicDir: publicDirOption } = ctx.options;
|
|
37
|
-
const publicDir = resolve(
|
|
38
|
-
ctx.projectRoot,
|
|
39
|
-
typeof publicDirOption === "string" ? publicDirOption : "public",
|
|
40
|
-
);
|
|
41
|
-
const dest = ctx.outDir;
|
|
42
|
-
|
|
43
|
-
ctx.logger.info(`copying ${publicDir} → ${dest}`);
|
|
44
|
-
|
|
45
|
-
await cp(publicDir, dest, {
|
|
46
|
-
recursive: true,
|
|
47
|
-
force: true,
|
|
48
|
-
errorOnExist: false,
|
|
49
|
-
}).catch((/** @type {NodeJS.ErrnoException} */ err) => {
|
|
50
|
-
if (err.code === "ENOENT") {
|
|
51
|
-
// publicDir does not exist or is empty — treat as no-op.
|
|
52
|
-
ctx.logger.info("public/ not found — skipping copy");
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
throw err;
|
|
56
|
-
});
|
|
57
|
-
},
|
|
58
|
-
};
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
// @ts-check
|
|
2
|
-
// zfb plugin module: search-index.
|
|
3
|
-
//
|
|
4
|
-
// Wires two lifecycle hooks for the search-index integration:
|
|
5
|
-
//
|
|
6
|
-
// postBuild — invokes `emitSearchIndex` to write `dist/search-index.json`.
|
|
7
|
-
// There is no settings gate — the index is always emitted,
|
|
8
|
-
// matching the legacy Astro behaviour.
|
|
9
|
-
//
|
|
10
|
-
// devMiddleware — rebuilds the in-memory search index from disk on every
|
|
11
|
-
// request so authoring edits surface without a dev-server
|
|
12
|
-
// restart. Registered at `/search-index.json`.
|
|
13
|
-
//
|
|
14
|
-
// `options` carries `{ docsDir, locales, base }` from the matching entry
|
|
15
|
-
// in `zfb.config.ts`.
|
|
16
|
-
//
|
|
17
|
-
// Inline functions are not supported by zfb's plugin runtime; see the
|
|
18
|
-
// sibling `doc-history-plugin.mjs` for the rationale.
|
|
19
|
-
|
|
20
|
-
/** @import { ZfbBuildHookContext, ZfbDevMiddlewareContext, ZfbPlugin } from "@takazudo/zfb/plugins" */
|
|
21
|
-
/** @import { SearchIndexBuildOptions, SearchIndexConfig } from "@takazudo/zudo-doc/integrations/search-index" */
|
|
22
|
-
|
|
23
|
-
import { emitSearchIndex, createSearchIndexDevMiddleware } from "@takazudo/zudo-doc/integrations/search-index";
|
|
24
|
-
import { connectToZfbHandler } from "./connect-adapter.mjs";
|
|
25
|
-
|
|
26
|
-
/** @type {ZfbPlugin} */
|
|
27
|
-
export default {
|
|
28
|
-
name: "search-index",
|
|
29
|
-
|
|
30
|
-
/** @param {ZfbBuildHookContext} ctx */
|
|
31
|
-
async postBuild(ctx) {
|
|
32
|
-
await emitSearchIndex(/** @type {SearchIndexBuildOptions} */ (/** @type {unknown} */ ({
|
|
33
|
-
...ctx.options,
|
|
34
|
-
outDir: ctx.outDir,
|
|
35
|
-
logger: ctx.logger,
|
|
36
|
-
})));
|
|
37
|
-
},
|
|
38
|
-
|
|
39
|
-
/** @param {ZfbDevMiddlewareContext} ctx */
|
|
40
|
-
devMiddleware(ctx) {
|
|
41
|
-
const middleware = createSearchIndexDevMiddleware(
|
|
42
|
-
/** @type {SearchIndexConfig} */ (/** @type {unknown} */ (ctx.options)),
|
|
43
|
-
);
|
|
44
|
-
// zfb's `register(path, handler)` matches against the FULL request
|
|
45
|
-
// URL (no base-stripping). For a non-root base (e.g. "/my-docs/"),
|
|
46
|
-
// requests arrive as `/my-docs/search-index.json`, so we register
|
|
47
|
-
// the full base-prefixed route. For base="/", the prefix is empty
|
|
48
|
-
// and the route is `/search-index.json` as expected. The v2
|
|
49
|
-
// middleware itself is base-tolerant (matches via
|
|
50
|
-
// `endsWith("/search-index.json")`), so it does not need a
|
|
51
|
-
// separate base-stripping pass.
|
|
52
|
-
const basePrefix = stripTrailingSlash(
|
|
53
|
-
typeof ctx.options["base"] === "string" ? ctx.options["base"] : "",
|
|
54
|
-
);
|
|
55
|
-
ctx.register(`${basePrefix}/search-index.json`, connectToZfbHandler(middleware));
|
|
56
|
-
},
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* @param {string} s
|
|
61
|
-
* @returns {string}
|
|
62
|
-
*/
|
|
63
|
-
function stripTrailingSlash(s) {
|
|
64
|
-
if (typeof s !== "string" || s.length === 0) return "";
|
|
65
|
-
return s.endsWith("/") ? s.slice(0, -1) : s;
|
|
66
|
-
}
|