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.
Files changed (92) hide show
  1. package/dist/compose.d.ts +6 -3
  2. package/dist/compose.js +6 -4
  3. package/dist/features/image-enlarge.d.ts +22 -12
  4. package/dist/features/image-enlarge.js +23 -191
  5. package/dist/scaffold.d.ts +11 -0
  6. package/dist/scaffold.js +51 -24
  7. package/dist/settings-gen.js +4 -0
  8. package/dist/zfb-config-gen.d.ts +16 -10
  9. package/dist/zfb-config-gen.js +34 -239
  10. package/package.json +1 -1
  11. package/templates/base/pages/_mdx-components.ts +64 -185
  12. package/templates/base/pages/index.tsx +1 -1
  13. package/templates/base/pages/lib/_body-end-islands.tsx +6 -13
  14. package/templates/base/pages/lib/_category-nav.tsx +30 -115
  15. package/templates/base/pages/lib/_category-tree-nav.tsx +38 -65
  16. package/templates/base/pages/lib/_compose-meta-title.ts +2 -6
  17. package/templates/base/pages/lib/_doc-body-end.tsx +3 -35
  18. package/templates/base/pages/lib/_doc-content-header.tsx +10 -111
  19. package/templates/base/pages/lib/_doc-history-area.tsx +19 -211
  20. package/templates/base/pages/lib/_doc-metainfo-area.tsx +10 -113
  21. package/templates/base/pages/lib/_doc-page-renderer.tsx +22 -183
  22. package/templates/base/pages/lib/_doc-page-shell.tsx +17 -251
  23. package/templates/base/pages/lib/_doc-pager.tsx +4 -74
  24. package/templates/base/pages/lib/_doc-route-entries.ts +43 -177
  25. package/templates/base/pages/lib/_doc-route-paths.ts +16 -101
  26. package/templates/base/pages/lib/_doc-tags-area.tsx +14 -77
  27. package/templates/base/pages/lib/_extract-headings.ts +21 -295
  28. package/templates/base/pages/lib/_footer-with-defaults.tsx +37 -225
  29. package/templates/base/pages/lib/_frontmatter-preview-data.ts +5 -31
  30. package/templates/base/pages/lib/_head-with-defaults.tsx +13 -138
  31. package/templates/base/pages/lib/_header-with-defaults.tsx +24 -324
  32. package/templates/base/pages/lib/_inline-version-switcher.tsx +16 -78
  33. package/templates/base/pages/lib/_math-block.tsx +4 -63
  34. package/templates/base/pages/lib/_nav-data-prep.ts +32 -51
  35. package/templates/base/pages/lib/_nav-source-cache.ts +12 -57
  36. package/templates/base/pages/lib/_nav-source-docs.ts +33 -233
  37. package/templates/base/pages/lib/_search-widget-script.ts +2 -470
  38. package/templates/base/pages/lib/_search-widget.tsx +9 -195
  39. package/templates/base/pages/lib/_sidebar-prepaint.tsx +6 -59
  40. package/templates/base/pages/lib/_sidebar-with-defaults.tsx +16 -118
  41. package/templates/base/pages/lib/_site-tree-nav.tsx +29 -80
  42. package/templates/base/pages/lib/doc-page-props.ts +26 -44
  43. package/templates/base/pages/lib/locale-merge.ts +32 -145
  44. package/templates/base/pages/lib/route-enumerators.ts +52 -286
  45. package/templates/base/src/components/ai-chat-modal.tsx +9 -8
  46. package/templates/base/src/components/content/code-group.tsx +3 -76
  47. package/templates/base/src/components/content/content-admonition.tsx +4 -56
  48. package/templates/base/src/components/doc-history.tsx +9 -8
  49. package/templates/base/src/components/image-enlarge.tsx +11 -8
  50. package/templates/base/src/components/sidebar-toggle.tsx +6 -170
  51. package/templates/base/src/components/sidebar-tree.tsx +6 -548
  52. package/templates/base/src/config/color-scheme-utils.ts +34 -158
  53. package/templates/base/src/config/i18n.ts +9 -0
  54. package/templates/base/src/config/settings-types.ts +34 -172
  55. package/templates/base/src/config/z-index-tokens.ts +5 -4
  56. package/templates/base/src/styles/global.css +40 -587
  57. package/templates/base/src/utils/base.ts +1 -1
  58. package/templates/base/src/utils/docs.ts +47 -16
  59. package/templates/base/src/utils/github.ts +12 -9
  60. package/templates/base/src/utils/nav-scope.ts +13 -42
  61. package/templates/base/src/utils/sidebar.ts +18 -86
  62. package/templates/base/src/utils/slug.ts +10 -53
  63. package/templates/base/src/utils/smart-break.tsx +12 -120
  64. package/templates/base/src/utils/tags.ts +25 -68
  65. package/templates/features/bodyFootUtil/files/src/utils/github.ts +12 -9
  66. package/templates/features/designTokenPanel/files/src/lib/design-token-panel-bootstrap.ts +13 -39
  67. package/templates/features/docHistory/files/src/components/doc-history.tsx +8 -636
  68. package/templates/features/docHistory/files/src/types/doc-history.ts +7 -23
  69. package/templates/features/docTags/files/pages/lib/_tag-pages.tsx +35 -201
  70. package/templates/features/i18n/files/pages/[locale]/index.tsx +1 -1
  71. package/templates/features/imageEnlarge/files/src/components/image-enlarge.tsx +8 -269
  72. package/templates/features/sidebarToggle/files/src/components/desktop-sidebar-toggle.tsx +6 -99
  73. package/templates/features/tagGovernance/files/scripts/tags-audit.ts +67 -515
  74. package/templates/features/versioning/files/pages/lib/_versions-page.tsx +21 -73
  75. package/templates/base/pages/404.tsx +0 -61
  76. package/templates/base/pages/robots.txt.tsx +0 -29
  77. package/templates/base/pages/sitemap.xml.tsx +0 -59
  78. package/templates/base/plugins/connect-adapter.mjs +0 -169
  79. package/templates/base/plugins/copy-public-plugin.mjs +0 -58
  80. package/templates/base/plugins/search-index-plugin.mjs +0 -66
  81. package/templates/base/scripts/gen-z-index.mjs +0 -157
  82. package/templates/base/src/components/mermaid-enlarge.tsx +0 -490
  83. package/templates/base/src/components/site-tree-nav.tsx +0 -220
  84. package/templates/features/claudeResources/files/plugins/claude-resources-plugin.mjs +0 -47
  85. package/templates/features/docHistory/files/plugins/doc-history-plugin.mjs +0 -111
  86. package/templates/features/docTags/files/pages/[locale]/docs/tags/[tag].tsx +0 -59
  87. package/templates/features/docTags/files/pages/[locale]/docs/tags/index.tsx +0 -39
  88. package/templates/features/docTags/files/pages/docs/tags/[tag].tsx +0 -43
  89. package/templates/features/docTags/files/pages/docs/tags/index.tsx +0 -20
  90. package/templates/features/llmsTxt/files/plugins/llms-txt-plugin.mjs +0 -93
  91. package/templates/features/versioning/files/pages/[locale]/docs/versions.tsx +0 -48
  92. package/templates/features/versioning/files/pages/docs/versions.tsx +0 -20
@@ -1,85 +1,33 @@
1
1
  /** @jsxRuntime automatic */
2
2
  /** @jsxImportSource preact */
3
- // Shared renderer for the documentation-versions pages (#2010).
4
- //
5
- // Collapses the per-locale page pair:
6
- // pages/docs/versions.tsx + pages/[locale]/docs/versions.tsx
7
- // into one locale-parameterized renderer. The page files stay thin shells
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, type Locale } from "@/config/i18n";
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
- /** Versions index page for one locale. Lists the latest version and any past
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
- const labels: VersionsPageLabels = {
32
- pageTitle,
33
- latestTitle: t("version.page.latest.title", locale),
34
- latestDescription: t("version.page.latest.description", locale),
35
- latestLink: t("version.page.latest.link", locale),
36
- pastTitle: t("version.page.past.title", locale),
37
- pastDescription: t("version.page.past.description", locale),
38
- unmaintained: t("version.page.unmaintained", locale),
39
- unreleased: t("version.page.unreleased", locale),
40
- versionCol: t("version.switcher.label", locale),
41
- statusCol: t("version.page.status", locale),
42
- docsCol: t("version.page.docs", locale),
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, "&amp;")
26
- .replace(/</g, "&lt;")
27
- .replace(/>/g, "&gt;")
28
- .replace(/"/g, "&quot;")
29
- .replace(/'/g, "&apos;");
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
- }