@xyd-js/vite-plugin 0.0.0-build-cdbb0d7-20260901160230

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) LiveSession Sp.z.o.o
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,124 @@
1
+ # @xyd-js/vite-plugin
2
+
3
+ Build your [xyd](https://xyd.dev) docs **inside your app's `vite build`** — plain Vite or
4
+ Vite + React Router 7 — and get one deployable output: your app at `/`, your docs under a
5
+ subpath like `/docs`.
6
+
7
+ The plugin runs `xyd build` for your docs project (in a child process) after your app's
8
+ build finishes, then merges the static docs output into your build directory:
9
+
10
+ - `assets/` → merged into your `assets/` (the docs HTML references them as `/assets/*`)
11
+ - the basename page tree (e.g. `docs/`) → mounted at that path; every `<slug>.html`
12
+ page is also mirrored as `<slug>/index.html`, so extensionless links work on
13
+ clean-URL hosts (Netlify, `serve`) AND plain static servers (express /
14
+ `react-router-serve`) via the directory-index convention
15
+ - `public/` (when the docs build emits a root one) → merged file-by-file, never clobbering yours
16
+
17
+ During `vite dev` the plugin spawns `xyd dev` on an internal port and proxies the
18
+ mount path (plus xyd's `/_xyd/*` and `/_bun/*` internals, including the live-reload
19
+ websocket) — **app and docs share one URL and port** in dev too. Requests under the
20
+ mount are held until the docs dev server finishes its cold start. The spawned dev
21
+ uses xyd's bun engine by default (`XYD_BUN=1`, a no-op for the native binary — its
22
+ URL surface is subpath-clean; override via `env`). Set `dev: false` for a
23
+ build-only plugin.
24
+
25
+ ## Usage
26
+
27
+ ```ts
28
+ // vite.config.ts
29
+ import { defineConfig } from "vite";
30
+ import xyd from "@xyd-js/vite-plugin";
31
+
32
+ export default defineConfig({
33
+ plugins: [
34
+ xyd({ docsRoot: "./docs", base: "/docs" }),
35
+ ],
36
+ });
37
+ ```
38
+
39
+ With React Router 7, just add it next to `reactRouter()` — the plugin sequences itself
40
+ after React Router's client + SSR builds and prerender:
41
+
42
+ ```ts
43
+ import { reactRouter } from "@react-router/dev/vite";
44
+ export default defineConfig({
45
+ plugins: [reactRouter(), xyd({ docsRoot: "./docs", base: "/docs" })],
46
+ });
47
+ ```
48
+
49
+ **The mount path** comes from the plugin's `base` option — it flows into the docs
50
+ build via the `XYD_BASENAME` env var, so the docs settings don't need to declare
51
+ anything. If the docs settings DO set `advanced.basename`, that value wins — and it
52
+ must equal `base` (the basename is baked into every prerendered docs link, so a
53
+ mismatch fails the build instead of silently diverging).
54
+
55
+ ```json
56
+ // docs.json — no basename needed when the plugin passes base
57
+ { "navigation": { "sidebar": ["overview"] } }
58
+ ```
59
+
60
+ ## Which xyd runs?
61
+
62
+ Resolution order:
63
+
64
+ 1. the `command` option (full argv, e.g. `["bunx", "xyd-js@1.2.3"]`)
65
+ 2. `xyd-js` or `@xyd-js/cli` installed in your project (recommended: `npm i -D xyd-js`)
66
+ 3. an `xyd` executable on PATH (the native binary)
67
+
68
+ There is deliberately no `npx xyd-js@latest` fallback — a build that silently downloads
69
+ `latest` is not reproducible.
70
+
71
+ ## Options
72
+
73
+ | Option | Default | Description |
74
+ |---|---|---|
75
+ | `docsRoot` | — (required) | Path to the docs project (the dir with `docs.json`), relative to the Vite root |
76
+ | `base` | from the build output | Expected mount path; validated against `advanced.basename` — mismatch fails the build |
77
+ | `enabled` | `true` | `false` turns the plugin into a no-op (gate docs builds behind an env var) |
78
+ | `dev` | `true` | `false` disables the `vite dev` integration (spawned `xyd dev` + same-origin proxy) |
79
+ | `outDir` | client build outDir | Override the merge target for adapter frameworks (SvelteKit: `"build"`, Nuxt: `".output/public"`); merge then runs at end of process, after the adapter |
80
+ | `command` | auto-resolved | Full CLI argv WITHOUT the `build` subcommand |
81
+ | `env` | `{}` | Extra env for the docs build child process |
82
+ | `nodeOptions` | `"--max-old-space-size=8192"` | `NODE_OPTIONS` for the child when unset (docs builds are memory-heavy); `false` disables |
83
+ | `sitemap` / `robots` | `"skip"` | Policy for the docs build's root `sitemap.xml` / `robots.txt` (`"copy"` copies when your build didn't emit one) |
84
+ | `timeoutMs` | `0` (none) | Kill the docs build after N ms and fail |
85
+ | `silent` | `false` | Buffer docs build output; replay the tail only on failure |
86
+ | `verbose` | `false` | Plugin debug logging |
87
+
88
+ ## Framework compatibility
89
+
90
+ Verified across the Vite ecosystem (each in **build and dev**, see the
91
+ `10.webframeworks-plugin` e2e matrix):
92
+
93
+ | Host | Notes |
94
+ |---|---|
95
+ | plain Vite (vanilla / vue / solid SPA) | works as-is |
96
+ | React Router 7 & 8 (`ssr` + `prerender`) | works as-is (vite 7 and 8) |
97
+ | Astro | add to `vite.plugins` in `astro.config.mjs` |
98
+ | SvelteKit + adapter-static | set `outDir: "build"` (the adapter assembles the final dir) |
99
+ | Nuxt | add to `vite.plugins` in `nuxt.config.ts` with `outDir: ".output/public"`; also set `ignore: ["docs/**"]` when the docs dir lives inside the app. Docs live-reload is auto-disabled in dev (nuxt's proxy can't forward websocket upgrades) — pages and styles proxy normally |
100
+ | custom SSR servers (Vite SSR guide) | see below |
101
+ | Next.js | not Vite — use [`@xyd-js/next-plugin`](https://www.npmjs.com/package/@xyd-js/next-plugin) |
102
+
103
+ ## Custom SSR servers (the Vite SSR guide setup)
104
+
105
+ Works out of the box in dev — the plugin's proxy lives inside `vite.middlewares`,
106
+ so an express server in `middlewareMode` serves `/docs` before its SSR catch-all.
107
+
108
+ In production the stock template serves static files with
109
+ `sirv('./dist/client', { extensions: [] })` (exact matches only, so the SSR
110
+ catch-all owns clean URLs). Docs **assets** need nothing (real extensions), but
111
+ extensionless docs **pages** need one extra mount before the catch-all:
112
+
113
+ ```js
114
+ app.use('/docs', sirv('./dist/client/docs', { extensions: ['html'] }))
115
+ ```
116
+
117
+ ## Safety properties
118
+
119
+ - Merge **conflicts fail the build**: a file that already exists with different content
120
+ (host route under the mount path, colliding asset) is reported — never silently overwritten.
121
+ - Docs output is **structurally validated** (fresh, has pages + assets) — some xyd
122
+ versions can exit 0 even when the underlying build failed.
123
+ - All failures throw from `closeBundle`, so `vite build` exits non-zero and deploy
124
+ tooling never ships a half-merged output.
@@ -0,0 +1,212 @@
1
+ import { Plugin } from 'vite';
2
+
3
+ interface XydOptions {
4
+ /** Path to the docs project (the dir containing docs.json / docs.ts), relative to the Vite root or absolute. Required. */
5
+ docsRoot: string;
6
+ /**
7
+ * Mount path, e.g. "/docs". Passed into the docs build via XYD_BASENAME, so the
8
+ * docs settings don't need to declare `advanced.basename` at all. When the docs
9
+ * settings DO declare one, it wins — and must equal `base` (the basename is baked
10
+ * into every prerendered link, so the plugin validates rather than remaps).
11
+ */
12
+ base?: string;
13
+ /** Set to false to turn the plugin into a no-op (e.g. gate docs builds behind an env var). Default true. */
14
+ enabled?: boolean;
15
+ /**
16
+ * Override where the docs merge lands, relative to the Vite root. By default the
17
+ * client build's outDir is used — correct for plain Vite and React Router. Set
18
+ * this for ADAPTER-based frameworks whose final static dir is assembled after
19
+ * the client build (SvelteKit adapter-static: "build"; Nuxt: ".output/public").
20
+ */
21
+ outDir?: string;
22
+ /**
23
+ * Dev-mode integration: during `vite dev`, spawn `xyd dev` for the docs and
24
+ * proxy the mount path (+ xyd's /_xyd and /_bun internals, incl. the
25
+ * livereload websocket) into the SAME origin — app and docs on one URL/port.
26
+ * The spawned dev defaults to xyd's bun engine (XYD_BUN=1; override via `env`)
27
+ * whose URL surface is subpath-clean. Default true; false = build-only plugin.
28
+ */
29
+ dev?: boolean;
30
+ /**
31
+ * Full CLI argv WITHOUT the `build` subcommand, e.g. ["node", "/abs/path/to/cli.js"] or
32
+ * ["bunx", "xyd-js@latest"]. A string is whitespace-split. Overrides auto-resolution.
33
+ */
34
+ command?: string | string[];
35
+ /** Extra env for the docs build child process (merged over process.env). */
36
+ env?: Record<string, string>;
37
+ /**
38
+ * NODE_OPTIONS for the child when neither process.env nor `env` provide one.
39
+ * Docs builds are memory-heavy; default "--max-old-space-size=8192". `false` disables the default.
40
+ */
41
+ nodeOptions?: string | false;
42
+ /** Policy for the docs build's root sitemap.xml. Default "skip" (its URLs currently lack the basename prefix). */
43
+ sitemap?: "skip" | "copy";
44
+ /** Policy for the docs build's root robots.txt. Default "skip". */
45
+ robots?: "skip" | "copy";
46
+ /** Kill the docs build after N ms and fail the build. Default 0 = no timeout. */
47
+ timeoutMs?: number;
48
+ /** Buffer the docs build output and replay the tail only on failure. Default false = stream live. */
49
+ silent?: boolean;
50
+ /** Plugin debug logging. */
51
+ verbose?: boolean;
52
+ }
53
+ interface ResolvedXydOptions {
54
+ docsRoot: string;
55
+ base?: string;
56
+ enabled: boolean;
57
+ dev: boolean;
58
+ outDir?: string;
59
+ command?: string[];
60
+ env: Record<string, string>;
61
+ nodeOptions: string | false;
62
+ sitemap: "skip" | "copy";
63
+ robots: "skip" | "copy";
64
+ timeoutMs: number;
65
+ silent: boolean;
66
+ verbose: boolean;
67
+ }
68
+ /** "/docs/" | "docs" -> "/docs"; undefined passes through. */
69
+ declare function normalizeBase(base?: string): string | undefined;
70
+ declare function normalizeOptions(options: XydOptions): ResolvedXydOptions;
71
+
72
+ /**
73
+ * Merge of an xyd static build (`<docsRoot>/.xyd/build/client`) into the host app's
74
+ * client outDir. Pure fs logic — no Vite, no child processes — so it is unit-testable
75
+ * and reusable.
76
+ *
77
+ * xyd output anatomy (docs.json `advanced.basename: "/docs"`):
78
+ * assets/ hashed js/css at the CLIENT ROOT — the docs HTML references them as
79
+ * absolute `/assets/*` (no basename prefix), so they must merge into
80
+ * the host's root assets/ dir
81
+ * docs/ the page tree under the basename (incl. docs/public/, docs/llms.txt)
82
+ * public/ root duplicate of the docs public dir — bundled docs JS references
83
+ * un-prefixed `/public/*` paths
84
+ * sitemap.xml URLs currently LACK the basename prefix (upstream documan issue) —
85
+ * skipped by default
86
+ * robots.txt host owns it — skipped by default
87
+ */
88
+ interface MergeOptions {
89
+ /** Expected mount path ("/docs"). When set, validated against the output tree. */
90
+ base?: string;
91
+ sitemap: "skip" | "copy";
92
+ robots: "skip" | "copy";
93
+ }
94
+ interface CopyOp {
95
+ src: string;
96
+ dest: string;
97
+ /** classification for reporting */
98
+ kind: "asset" | "public" | "page-tree" | "root-file";
99
+ }
100
+ interface MergePlan {
101
+ ops: CopyOp[];
102
+ /** conflicting dest paths (exist with DIFFERENT content) — a non-empty list must fail the merge */
103
+ conflicts: string[];
104
+ /** dest files that already exist with identical content (skipped) */
105
+ skippedIdentical: number;
106
+ /** informational notes (skipped sitemap/robots, …) */
107
+ notes: string[];
108
+ /** .html files in the page tree */
109
+ pages: number;
110
+ assets: number;
111
+ /** the resolved mount path, e.g. "/docs" */
112
+ mount: string;
113
+ }
114
+ interface MergeSummary {
115
+ pages: number;
116
+ assets: number;
117
+ skippedIdentical: number;
118
+ notes: string[];
119
+ mount: string;
120
+ }
121
+ /**
122
+ * Classify the docs client dir and produce a merge plan. Throws XydError on
123
+ * structural problems (missing basename, base mismatch). Collects ALL content
124
+ * conflicts instead of throwing on the first, so a doomed merge reports completely.
125
+ */
126
+ declare function planMerge(docsClientDir: string, hostOutDir: string, options: MergeOptions): MergePlan;
127
+ declare function executeMerge(plan: MergePlan): void;
128
+ /** plan → throw on conflicts → execute. The single entry point the plugin (and tests) use. */
129
+ declare function mergeDocsBuild(docsClientDir: string, hostOutDir: string, options: MergeOptions): MergeSummary;
130
+
131
+ interface ResolvedCli {
132
+ /** argv WITHOUT the `build` subcommand, e.g. ["node", "/…/xyd-cli/dist/index.js"] */
133
+ argv: string[];
134
+ /** where it came from — for logging */
135
+ source: "command option" | "local xyd-js" | "local @xyd-js/cli" | "PATH";
136
+ }
137
+ /**
138
+ * Resolve which xyd CLI to spawn, in order:
139
+ * 1. the `command` option (full control)
140
+ * 2. `xyd-js` / `@xyd-js/cli` installed in the HOST project
141
+ * 3. an `xyd` executable on PATH
142
+ * No `npx xyd-js@latest` auto-fallback — a build silently downloading `latest` is not reproducible.
143
+ */
144
+ declare function resolveCli(command: string[] | undefined, hostRoot: string): ResolvedCli;
145
+
146
+ interface Logger {
147
+ info(msg: string): void;
148
+ warn(msg: string): void;
149
+ debug(msg: string): void;
150
+ /** Prefix used for re-emitting the docs build's own output lines. */
151
+ child(line: string): void;
152
+ }
153
+ declare function createLogger(verbose: boolean): Logger;
154
+ /** A plugin-originated, already user-readable error (no stack noise needed). */
155
+ declare class XydError extends Error {
156
+ constructor(message: string);
157
+ }
158
+
159
+ /**
160
+ * Spawn `<cli> build` with cwd = the docs project root (the xyd CLI resolves
161
+ * everything — settings, content, .xyd output — from process.cwd(); there is no
162
+ * directory argument).
163
+ */
164
+ declare function runDocsBuild(argv: string[], docsRoot: string, options: ResolvedXydOptions, log: Logger): Promise<void>;
165
+
166
+ /**
167
+ * Dev-mode integration: spawn `xyd dev` for the docs project on an internal
168
+ * port and let the vite dev server proxy it — app and docs share one origin.
169
+ *
170
+ * The spawned dev defaults to xyd's BUN engine (XYD_BUN=1 — a no-op for the
171
+ * native binary, an opt-in for the JS CLI): its URL surface is subpath-clean —
172
+ * pages under the basename plus the /_xyd/* (css/js + livereload websocket)
173
+ * and /_bun/* internals — so a prefix proxy covers everything. The vite-engine
174
+ * dev server serves unprefixed /@vite//@fs module URLs that would collide with
175
+ * the host's own, which is why it is not the default.
176
+ */
177
+ /** xyd dev endpoints that live OUTSIDE the basename (safe, host-unused prefixes). */
178
+ declare const XYD_DEV_INTERNAL_PREFIXES: string[];
179
+ declare function pickFreePort(): Promise<number>;
180
+ interface DocsDevHandle {
181
+ /** resolves when the docs dev server answers HTTP (any status) */
182
+ ready: Promise<void>;
183
+ stop(): void;
184
+ }
185
+ declare function spawnDocsDev(argv: string[], docsRoot: string, port: number, base: string, options: ResolvedXydOptions, log: Logger): DocsDevHandle;
186
+
187
+ /**
188
+ * Vite plugin for embedding xyd docs into a host app (plain Vite or Vite +
189
+ * React Router):
190
+ *
191
+ * - `vite build`: runs `xyd build` for the docs project in a child process and
192
+ * merges its static output (`.xyd/build/client/`) into the host client outDir.
193
+ * - `vite dev`: spawns `xyd dev` on an internal port and proxies the mount path
194
+ * (+ xyd's /_xyd + /_bun internals, incl. the livereload websocket) — app and
195
+ * docs share one URL/port.
196
+ *
197
+ * The mount path comes from `base` (passed to xyd via XYD_BASENAME) or the docs'
198
+ * own `advanced.basename` — the docs side wins when both are set (must match).
199
+ */
200
+ declare function xyd(userOptions: XydOptions): Plugin;
201
+ /**
202
+ * Fail fast when the settings are statically readable (docs.json). docs.ts/tsx
203
+ * can't be parsed here — the post-build output-tree validation in merge.ts covers
204
+ * those (the output encodes the basename regardless of settings format).
205
+ *
206
+ * The mount path can come from EITHER side: the plugin's `base` option (passed
207
+ * into the docs build via XYD_BASENAME) or the docs' own `advanced.basename`.
208
+ * When both are set they must agree.
209
+ */
210
+ declare function preValidateBasename(absDocsRoot: string, base: string | undefined): void;
211
+
212
+ export { XYD_DEV_INTERNAL_PREFIXES, XydError, type XydOptions, createLogger, xyd as default, executeMerge, mergeDocsBuild, normalizeBase, normalizeOptions, pickFreePort, planMerge, preValidateBasename, resolveCli, runDocsBuild, spawnDocsDev };
package/dist/index.js ADDED
@@ -0,0 +1,635 @@
1
+ // src/index.ts
2
+ import * as fs4 from "fs";
3
+ import * as path4 from "path";
4
+
5
+ // src/log.ts
6
+ var PREFIX = "[xyd]";
7
+ function createLogger(verbose) {
8
+ return {
9
+ info: (msg) => console.log(`${PREFIX} ${msg}`),
10
+ warn: (msg) => console.warn(`${PREFIX} ${msg}`),
11
+ debug: (msg) => {
12
+ if (verbose) console.log(`${PREFIX} ${msg}`);
13
+ },
14
+ child: (line) => console.log(`${PREFIX} \u2502 ${line}`)
15
+ };
16
+ }
17
+ var XydError = class extends Error {
18
+ constructor(message) {
19
+ super(`${PREFIX} ${message}`);
20
+ this.name = "XydViteBuildError";
21
+ }
22
+ };
23
+
24
+ // src/options.ts
25
+ function normalizeBase(base) {
26
+ if (base === void 0) return void 0;
27
+ const trimmed = String(base).trim().replace(/\/+$/, "");
28
+ if (!trimmed || trimmed === "/") {
29
+ throw new XydError(`\`base\` must be a non-root mount path like "/docs" (got ${JSON.stringify(base)})`);
30
+ }
31
+ return trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
32
+ }
33
+ function normalizeOptions(options) {
34
+ if (!options || typeof options.docsRoot !== "string" || !options.docsRoot.trim()) {
35
+ throw new XydError(`\`docsRoot\` is required \u2014 the path to your docs project (the dir containing docs.json)`);
36
+ }
37
+ return {
38
+ docsRoot: options.docsRoot,
39
+ base: normalizeBase(options.base),
40
+ enabled: options.enabled !== false,
41
+ dev: options.dev !== false,
42
+ outDir: options.outDir,
43
+ command: options.command === void 0 ? void 0 : Array.isArray(options.command) ? options.command : options.command.split(/\s+/).filter(Boolean),
44
+ env: options.env || {},
45
+ nodeOptions: options.nodeOptions === void 0 ? "--max-old-space-size=8192" : options.nodeOptions,
46
+ sitemap: options.sitemap || "skip",
47
+ robots: options.robots || "skip",
48
+ timeoutMs: options.timeoutMs || 0,
49
+ silent: !!options.silent,
50
+ verbose: !!options.verbose
51
+ };
52
+ }
53
+
54
+ // src/merge.ts
55
+ import * as fs from "fs";
56
+ import * as path from "path";
57
+ function sameContent(a, b) {
58
+ const sa = fs.statSync(a);
59
+ const sb = fs.statSync(b);
60
+ if (sa.size !== sb.size) return false;
61
+ return Buffer.compare(new Uint8Array(fs.readFileSync(a)), new Uint8Array(fs.readFileSync(b))) === 0;
62
+ }
63
+ function planDir(srcDir, destDir, kind, plan) {
64
+ for (const entry of fs.readdirSync(srcDir, { withFileTypes: true })) {
65
+ const src = path.join(srcDir, entry.name);
66
+ const dest = path.join(destDir, entry.name);
67
+ if (entry.isDirectory()) {
68
+ planDir(src, dest, kind, plan);
69
+ } else {
70
+ if (fs.existsSync(dest)) {
71
+ if (sameContent(src, dest)) {
72
+ plan.skippedIdentical++;
73
+ } else {
74
+ plan.conflicts.push(dest);
75
+ }
76
+ continue;
77
+ }
78
+ plan.ops.push({ src, dest, kind });
79
+ if (kind === "asset") plan.assets++;
80
+ if (kind === "page-tree" && entry.name.endsWith(".html")) plan.pages++;
81
+ }
82
+ }
83
+ }
84
+ function planMerge(docsClientDir, hostOutDir, options) {
85
+ if (!fs.existsSync(docsClientDir)) {
86
+ throw new XydError(`docs build output not found at ${docsClientDir} \u2014 did the docs build run?`);
87
+ }
88
+ if (fs.existsSync(path.join(docsClientDir, "index.html"))) {
89
+ throw new XydError(
90
+ `the docs build has NO basename \u2014 its pages sit at the output root and would collide with your app.
91
+ ` + (options.base ? ` \`base: "${options.base}"\` was passed (via XYD_BASENAME) but the resolved xyd CLI ignored it \u2014
92
+ upgrade xyd to a version that supports XYD_BASENAME, or add to your docs settings:
93
+ "advanced": { "basename": "${options.base}" }` : ` Set the plugin's \`base\` option (e.g. base: "/docs") or add to your docs settings:
94
+ "advanced": { "basename": "/docs" }`)
95
+ );
96
+ }
97
+ const plan = { ops: [], conflicts: [], skippedIdentical: 0, notes: [], pages: 0, assets: 0, mount: "" };
98
+ const pageTreeDirs = [];
99
+ for (const entry of fs.readdirSync(docsClientDir, { withFileTypes: true })) {
100
+ const src = path.join(docsClientDir, entry.name);
101
+ if (entry.name === ".vite") continue;
102
+ if (entry.isDirectory() && entry.name === "assets") {
103
+ planDir(src, path.join(hostOutDir, "assets"), "asset", plan);
104
+ continue;
105
+ }
106
+ if (entry.isDirectory() && entry.name === "public") {
107
+ planDir(src, path.join(hostOutDir, "public"), "public", plan);
108
+ continue;
109
+ }
110
+ if (!entry.isDirectory() && (entry.name === "sitemap.xml" || entry.name === "robots.txt")) {
111
+ const policy = entry.name === "sitemap.xml" ? options.sitemap : options.robots;
112
+ if (policy === "copy") {
113
+ const dest = path.join(hostOutDir, entry.name);
114
+ if (fs.existsSync(dest)) {
115
+ plan.notes.push(`kept the host's ${entry.name} (docs copy skipped)`);
116
+ } else {
117
+ plan.ops.push({ src, dest, kind: "root-file" });
118
+ if (entry.name === "sitemap.xml") {
119
+ plan.notes.push(`copied the docs sitemap.xml \u2014 note: its URLs currently lack the basename prefix (xyd issue)`);
120
+ }
121
+ }
122
+ } else {
123
+ plan.notes.push(`skipped docs ${entry.name} (policy: skip)`);
124
+ }
125
+ continue;
126
+ }
127
+ if (entry.isDirectory()) {
128
+ pageTreeDirs.push(entry.name);
129
+ planDir(src, path.join(hostOutDir, entry.name), "page-tree", plan);
130
+ } else {
131
+ const dest = path.join(hostOutDir, entry.name);
132
+ if (fs.existsSync(dest)) {
133
+ if (sameContent(src, dest)) plan.skippedIdentical++;
134
+ else plan.conflicts.push(dest);
135
+ } else {
136
+ plan.ops.push({ src, dest, kind: "page-tree" });
137
+ if (entry.name.endsWith(".html")) plan.pages++;
138
+ }
139
+ }
140
+ }
141
+ if (!pageTreeDirs.length) {
142
+ throw new XydError(`no page tree found in the docs build output at ${docsClientDir} \u2014 the docs build produced nothing to mount`);
143
+ }
144
+ plan.mount = "/" + pageTreeDirs[0];
145
+ if (options.base) {
146
+ const baseTop = options.base.replace(/^\/+/, "").split("/")[0];
147
+ if (!pageTreeDirs.includes(baseTop)) {
148
+ throw new XydError(
149
+ `\`base: "${options.base}"\` does not match the docs build output (found: ${pageTreeDirs.map((d) => "/" + d).join(", ")}).
150
+ \`base\` must equal \`advanced.basename\` in the docs settings \u2014 the basename is baked into every prerendered link.`
151
+ );
152
+ }
153
+ plan.mount = options.base;
154
+ }
155
+ const planned = new Set(plan.ops.map((op) => op.dest));
156
+ for (const op of [...plan.ops]) {
157
+ if (op.kind !== "page-tree" || !op.dest.endsWith(".html") || path.basename(op.dest) === "index.html") continue;
158
+ const mirror = path.join(op.dest.slice(0, -".html".length), "index.html");
159
+ if (planned.has(mirror) || fs.existsSync(mirror)) continue;
160
+ planned.add(mirror);
161
+ plan.ops.push({ src: op.src, dest: mirror, kind: "page-tree" });
162
+ }
163
+ return plan;
164
+ }
165
+ function executeMerge(plan) {
166
+ for (const op of plan.ops) {
167
+ fs.mkdirSync(path.dirname(op.dest), { recursive: true });
168
+ fs.copyFileSync(op.src, op.dest);
169
+ }
170
+ }
171
+ function formatConflicts(conflicts, hostOutDir) {
172
+ const rel = conflicts.map((c) => ` - ${path.relative(hostOutDir, c)}`).join("\n");
173
+ return `the host build already emitted ${conflicts.length} file(s) with DIFFERENT content at the docs merge paths:
174
+ ${rel}
175
+ Host routes/assets must not overlap the docs mount path (\`advanced.basename\`).`;
176
+ }
177
+ function mergeDocsBuild(docsClientDir, hostOutDir, options) {
178
+ const plan = planMerge(docsClientDir, hostOutDir, options);
179
+ if (plan.conflicts.length) {
180
+ throw new XydError(formatConflicts(plan.conflicts, hostOutDir));
181
+ }
182
+ executeMerge(plan);
183
+ return {
184
+ pages: plan.pages,
185
+ assets: plan.assets,
186
+ skippedIdentical: plan.skippedIdentical,
187
+ notes: plan.notes,
188
+ mount: plan.mount
189
+ };
190
+ }
191
+
192
+ // src/resolveCli.ts
193
+ import * as fs2 from "fs";
194
+ import * as path2 from "path";
195
+ import { createRequire } from "module";
196
+ function binFromPackage(pkgJsonPath) {
197
+ try {
198
+ const pkg = JSON.parse(fs2.readFileSync(pkgJsonPath, "utf-8"));
199
+ const bin = typeof pkg.bin === "string" ? pkg.bin : pkg.bin?.xyd;
200
+ if (!bin) return null;
201
+ const abs = path2.join(path2.dirname(pkgJsonPath), bin);
202
+ return fs2.existsSync(abs) ? abs : null;
203
+ } catch {
204
+ return null;
205
+ }
206
+ }
207
+ function xydOnPath() {
208
+ const exts = process.platform === "win32" ? [".exe", ".cmd", ".bat", ""] : [""];
209
+ for (const dir of (process.env.PATH || "").split(path2.delimiter)) {
210
+ if (!dir) continue;
211
+ for (const ext of exts) {
212
+ const candidate = path2.join(dir, `xyd${ext}`);
213
+ try {
214
+ fs2.accessSync(candidate, fs2.constants.X_OK);
215
+ if (fs2.statSync(candidate).isFile()) return candidate;
216
+ } catch {
217
+ }
218
+ }
219
+ }
220
+ return null;
221
+ }
222
+ function resolveCli(command, hostRoot) {
223
+ if (command?.length) {
224
+ return { argv: command, source: "command option" };
225
+ }
226
+ const require2 = createRequire(path2.join(hostRoot, "package.json"));
227
+ for (const [pkgName, source] of [["xyd-js", "local xyd-js"], ["@xyd-js/cli", "local @xyd-js/cli"]]) {
228
+ try {
229
+ const bin = binFromPackage(require2.resolve(`${pkgName}/package.json`));
230
+ if (bin) return { argv: [process.execPath, bin], source };
231
+ } catch {
232
+ }
233
+ }
234
+ const onPath = xydOnPath();
235
+ if (onPath) return { argv: [onPath], source: "PATH" };
236
+ throw new XydError(
237
+ `could not find the xyd CLI.
238
+ Fix one of:
239
+ - npm i -D xyd-js (recommended: pin a version)
240
+ - install the \`xyd\` binary on PATH (https://xyd.dev)
241
+ - pass \`command: ["bunx", "xyd-js@<version>"]\` (or any argv) to the plugin`
242
+ );
243
+ }
244
+
245
+ // src/runBuild.ts
246
+ import { spawn } from "child_process";
247
+ import * as fs3 from "fs";
248
+ import * as path3 from "path";
249
+ import * as readline from "readline";
250
+ async function runDocsBuild(argv, docsRoot, options, log) {
251
+ try {
252
+ return await runDocsBuildOnce(argv, docsRoot, options, log);
253
+ } catch (err) {
254
+ if (!String(err?.message || "").includes("looks broken")) throw err;
255
+ log.warn(`docs build output invalid (${err.message.split("(")[1]?.split(")")[0] || "?"}) \u2014 retrying once`);
256
+ return await runDocsBuildOnce(argv, docsRoot, options, log);
257
+ }
258
+ }
259
+ async function runDocsBuildOnce(argv, docsRoot, options, log) {
260
+ const env = { ...process.env, ...options.env, NODE_ENV: "production" };
261
+ if (options.nodeOptions !== false && !env.NODE_OPTIONS) {
262
+ env.NODE_OPTIONS = options.nodeOptions;
263
+ }
264
+ if (options.base && !env.XYD_BASENAME) {
265
+ env.XYD_BASENAME = options.base;
266
+ }
267
+ const startedAt = Date.now();
268
+ const tail = [];
269
+ await new Promise((resolve2, reject) => {
270
+ const child = spawn(argv[0], [...argv.slice(1), "build"], {
271
+ cwd: docsRoot,
272
+ env,
273
+ stdio: ["ignore", "pipe", "pipe"]
274
+ });
275
+ const onLine = (line) => {
276
+ if (options.silent) {
277
+ tail.push(line);
278
+ if (tail.length > 200) tail.shift();
279
+ } else {
280
+ log.child(line);
281
+ }
282
+ };
283
+ readline.createInterface({ input: child.stdout }).on("line", onLine);
284
+ readline.createInterface({ input: child.stderr }).on("line", onLine);
285
+ let timedOut = false;
286
+ let killTimer;
287
+ let timeoutTimer;
288
+ if (options.timeoutMs > 0) {
289
+ timeoutTimer = setTimeout(() => {
290
+ timedOut = true;
291
+ child.kill("SIGTERM");
292
+ killTimer = setTimeout(() => child.kill("SIGKILL"), 5e3);
293
+ }, options.timeoutMs);
294
+ }
295
+ child.on("error", (err) => reject(new XydError(`failed to spawn the docs build (${argv.join(" ")}): ${err.message}`)));
296
+ child.on("close", (code) => {
297
+ if (timeoutTimer) clearTimeout(timeoutTimer);
298
+ if (killTimer) clearTimeout(killTimer);
299
+ if (timedOut) {
300
+ return reject(new XydError(`docs build timed out after ${options.timeoutMs}ms`));
301
+ }
302
+ if (code !== 0) {
303
+ if (options.silent && tail.length) {
304
+ for (const line of tail) log.child(line);
305
+ }
306
+ return reject(new XydError(`docs build exited with code ${code} \u2014 see the [xyd] \u2502 output above`));
307
+ }
308
+ resolve2();
309
+ });
310
+ });
311
+ validateDocsOutput(path3.join(docsRoot, ".xyd", "build", "client"), startedAt);
312
+ }
313
+ function hasHtml(dir, depth = 0) {
314
+ if (depth > 6) return false;
315
+ for (const entry of fs3.readdirSync(dir, { withFileTypes: true })) {
316
+ if (entry.isFile() && entry.name.endsWith(".html")) return true;
317
+ if (entry.isDirectory() && hasHtml(path3.join(dir, entry.name), depth + 1)) return true;
318
+ }
319
+ return false;
320
+ }
321
+ function validateDocsOutput(clientDir, sinceMs) {
322
+ const fail = (why) => {
323
+ throw new XydError(
324
+ `docs build output at ${clientDir} looks broken (${why}).
325
+ Note: some xyd versions exit 0 even when the underlying build fails \u2014 check the [xyd] \u2502 output above for errors.`
326
+ );
327
+ };
328
+ if (!fs3.existsSync(clientDir)) fail("directory missing");
329
+ const entries = fs3.readdirSync(clientDir);
330
+ if (!entries.length) fail("directory empty");
331
+ const newest = Math.max(...entries.map((e) => fs3.statSync(path3.join(clientDir, e)).mtimeMs));
332
+ if (newest < sinceMs - 5e3) fail("output predates this build \u2014 stale result from a previous run");
333
+ const assetsDir = path3.join(clientDir, "assets");
334
+ if (!fs3.existsSync(assetsDir) || !fs3.readdirSync(assetsDir).length) fail("no assets/ output");
335
+ if (!hasHtml(clientDir)) fail("no prerendered .html pages");
336
+ }
337
+
338
+ // src/devServer.ts
339
+ import { spawn as spawn2 } from "child_process";
340
+ import * as net from "net";
341
+ import * as readline2 from "readline";
342
+ var XYD_DEV_INTERNAL_PREFIXES = ["/_xyd", "/_bun"];
343
+ function pickFreePort() {
344
+ return new Promise((resolve2, reject) => {
345
+ const srv = net.createServer();
346
+ srv.listen(0, () => {
347
+ const port = srv.address().port;
348
+ srv.close(() => resolve2(port));
349
+ });
350
+ srv.on("error", reject);
351
+ });
352
+ }
353
+ function spawnDocsDev(argv, docsRoot, port, base, options, log) {
354
+ const env = {
355
+ ...process.env,
356
+ ...options.env,
357
+ XYD_PORT: String(port),
358
+ XYD_BASENAME: base
359
+ };
360
+ if (env.XYD_BUN === void 0) env.XYD_BUN = "1";
361
+ const child = spawn2(argv[0], [...argv.slice(1), "dev"], {
362
+ cwd: docsRoot,
363
+ env,
364
+ stdio: ["ignore", "pipe", "pipe"]
365
+ });
366
+ const onLine = (line) => log.child(line);
367
+ readline2.createInterface({ input: child.stdout }).on("line", onLine);
368
+ readline2.createInterface({ input: child.stderr }).on("line", onLine);
369
+ let exited = false;
370
+ let exitCode = null;
371
+ child.on("close", (code) => {
372
+ exited = true;
373
+ exitCode = code;
374
+ });
375
+ child.on("error", (err) => {
376
+ exited = true;
377
+ log.warn(`failed to spawn the docs dev server (${argv.join(" ")}): ${err.message}`);
378
+ });
379
+ const budgetMs = 5 * 60 * 1e3;
380
+ const ready = (async () => {
381
+ const started = Date.now();
382
+ while (Date.now() - started < budgetMs) {
383
+ if (exited) {
384
+ throw new XydError(`the docs dev server exited (code ${exitCode}) before becoming ready \u2014 see the [xyd] \u2502 output above`);
385
+ }
386
+ try {
387
+ await fetch(`http://localhost:${port}${base}`, { redirect: "manual" });
388
+ return;
389
+ } catch {
390
+ }
391
+ await new Promise((r) => setTimeout(r, 500));
392
+ }
393
+ throw new XydError(`the docs dev server did not answer on :${port} within ${budgetMs / 1e3}s`);
394
+ })();
395
+ ready.catch(() => {
396
+ });
397
+ const stop = () => {
398
+ if (!exited) child.kill("SIGTERM");
399
+ };
400
+ process.once("exit", stop);
401
+ for (const sig of ["SIGTERM", "SIGINT"]) {
402
+ process.once(sig, () => {
403
+ stop();
404
+ if (process.listenerCount(sig) === 0) {
405
+ process.kill(process.pid, sig);
406
+ }
407
+ });
408
+ }
409
+ return { ready, stop };
410
+ }
411
+
412
+ // src/index.ts
413
+ var SETTINGS_FILES = ["docs.json", "docs.ts", "docs.tsx"];
414
+ var states = /* @__PURE__ */ new Map();
415
+ var devPorts = /* @__PURE__ */ new Map();
416
+ function xyd(userOptions) {
417
+ const options = normalizeOptions(userOptions);
418
+ const log = createLogger(options.verbose);
419
+ let config;
420
+ let state;
421
+ let devPort;
422
+ let devBase;
423
+ let devHandle;
424
+ let disableDocsLiveReload = false;
425
+ const isDevMode = () => options.enabled && options.dev;
426
+ return {
427
+ name: "xyd",
428
+ // closeBundle must run AFTER react-router's own hooks (prerender etc.)
429
+ enforce: "post",
430
+ /** dev: inject the proxy entries BEFORE the server exists (vite's proxy
431
+ * config is static) — the target port is picked here, the child spawns
432
+ * in configureServer. */
433
+ async config(userConfig, env) {
434
+ if (env.command !== "serve" || env.isPreview || !isDevMode()) return;
435
+ const absDocsRoot = path4.resolve(userConfig.root ? path4.resolve(userConfig.root) : process.cwd(), options.docsRoot);
436
+ devBase = options.base ?? readSettingsBasename(absDocsRoot);
437
+ if (!devBase) {
438
+ throw new XydError(
439
+ `dev mode needs the docs mount path \u2014 set the plugin's \`base\` option (e.g. base: "/docs")
440
+ or \`advanced.basename\` in ${absDocsRoot}/docs.json`
441
+ );
442
+ }
443
+ devPort = devPorts.get(absDocsRoot);
444
+ if (devPort === void 0) {
445
+ devPort = await pickFreePort();
446
+ devPorts.set(absDocsRoot, devPort);
447
+ }
448
+ const target = `http://localhost:${devPort}`;
449
+ const proxy = {
450
+ [devBase]: { target }
451
+ };
452
+ for (const prefix of XYD_DEV_INTERNAL_PREFIXES) {
453
+ proxy[prefix] = { target, ws: true };
454
+ }
455
+ return { server: { proxy } };
456
+ },
457
+ /** dev: spawn `xyd dev` + gate proxied requests until it answers. */
458
+ configureServer(server) {
459
+ if (!isDevMode() || devPort === void 0 || !devBase) return;
460
+ const ensureSpawned = () => {
461
+ if (!devHandle) {
462
+ const absDocsRoot = path4.resolve(config.root, options.docsRoot);
463
+ const cli = resolveCli(options.command, config.root);
464
+ log.info(`docs dev (${cli.source}): ${cli.argv.join(" ")} dev on :${devPort} \u2192 proxied at ${devBase}`);
465
+ const spawnOptions = disableDocsLiveReload ? { ...options, env: { ...options.env, XYD_LIVERELOAD: "0" } } : options;
466
+ devHandle = spawnDocsDev(cli.argv, absDocsRoot, devPort, devBase, spawnOptions, log);
467
+ }
468
+ return devHandle;
469
+ };
470
+ if (server.httpServer) {
471
+ ensureSpawned();
472
+ server.httpServer.once("close", () => devHandle?.stop());
473
+ }
474
+ const gated = (url) => url === devBase || url.startsWith(devBase + "/") || XYD_DEV_INTERNAL_PREFIXES.some((p) => url.startsWith(p));
475
+ server.middlewares.use((req, res, next) => {
476
+ if (!req.url || !gated(req.url)) return next();
477
+ ensureSpawned().ready.then(
478
+ () => next(),
479
+ (err) => {
480
+ res.statusCode = 502;
481
+ res.setHeader("content-type", "text/plain");
482
+ res.end(String(err?.message || err));
483
+ }
484
+ );
485
+ });
486
+ },
487
+ configResolved(resolved) {
488
+ if (!options.enabled) return;
489
+ config = resolved;
490
+ if (resolved.command !== "build") {
491
+ const absDocsRoot2 = path4.resolve(resolved.root, options.docsRoot);
492
+ assertDocsProject(absDocsRoot2);
493
+ preValidateBasename(absDocsRoot2, options.base);
494
+ disableDocsLiveReload ||= resolved.plugins.some(
495
+ (p) => typeof p?.name === "string" && p.name.startsWith("nuxt:")
496
+ );
497
+ if (disableDocsLiveReload) {
498
+ log.debug("ws-hostile host detected (nuxt) \u2014 docs livereload disabled");
499
+ }
500
+ return;
501
+ }
502
+ const absDocsRoot = path4.resolve(resolved.root, options.docsRoot);
503
+ assertDocsProject(absDocsRoot);
504
+ preValidateBasename(absDocsRoot, options.base);
505
+ state = states.get(absDocsRoot);
506
+ if (!state) {
507
+ state = { docsRoot: absDocsRoot, isRR: false, merged: false };
508
+ states.set(absDocsRoot, state);
509
+ }
510
+ state.isRR ||= resolved.plugins.some((p) => typeof p?.name === "string" && p.name.startsWith("react-router"));
511
+ if (!resolved.build.ssr) {
512
+ state.clientOutDir = path4.resolve(resolved.root, resolved.build.outDir);
513
+ }
514
+ },
515
+ async closeBundle() {
516
+ if (!options.enabled || !state || config.command !== "build") return;
517
+ const environment = this.environment;
518
+ const isSSR = environment?.config ? environment.config.consumer !== "client" : !!config.build.ssr;
519
+ if (!isSSR) {
520
+ const outDir = environment?.config?.build?.outDir ?? config.build.outDir;
521
+ state.clientOutDir = path4.resolve(config.root, outDir);
522
+ }
523
+ if (state.isRR ? !isSSR : isSSR) return;
524
+ if (options.outDir) {
525
+ scheduleExitMerge();
526
+ return;
527
+ }
528
+ await mergeFlow();
529
+ }
530
+ };
531
+ function scheduleExitMerge() {
532
+ if (!state || state.exitMergeScheduled) return;
533
+ state.exitMergeScheduled = true;
534
+ log.debug(`outDir mode \u2014 docs build + merge deferred to end of process (after the adapter)`);
535
+ process.once("beforeExit", () => {
536
+ mergeFlow().catch((err) => {
537
+ console.error(String(err?.message || err));
538
+ process.exitCode = 1;
539
+ });
540
+ });
541
+ }
542
+ async function mergeFlow() {
543
+ if (!state) return;
544
+ if (state.merged) {
545
+ log.debug("docs already merged in this process \u2014 skipping");
546
+ return;
547
+ }
548
+ const cli = resolveCli(options.command, config.root);
549
+ log.info(`building docs (${cli.source}): ${cli.argv.join(" ")} build [cwd ${state.docsRoot}]`);
550
+ const startedAt = Date.now();
551
+ await runDocsBuild(cli.argv, state.docsRoot, options, log);
552
+ if (options.outDir) {
553
+ const overridden = path4.resolve(config.root, options.outDir);
554
+ if (!fs4.existsSync(overridden)) {
555
+ throw new XydError(
556
+ `outDir "${options.outDir}" does not exist after the build (${overridden}) \u2014 did the framework's adapter run?`
557
+ );
558
+ }
559
+ state.clientOutDir = overridden;
560
+ }
561
+ if (!state.clientOutDir) {
562
+ throw new XydError(`client outDir was never resolved \u2014 the client build did not run?`);
563
+ }
564
+ const docsClientDir = path4.join(state.docsRoot, ".xyd", "build", "client");
565
+ const summary = mergeDocsBuild(docsClientDir, state.clientOutDir, {
566
+ base: options.base,
567
+ sitemap: options.sitemap,
568
+ robots: options.robots
569
+ });
570
+ state.merged = true;
571
+ const secs = ((Date.now() - startedAt) / 1e3).toFixed(1);
572
+ for (const note of summary.notes) log.info(note);
573
+ log.info(
574
+ `merged docs into ${path4.relative(config.root, state.clientOutDir) || "."} \u2014 mount ${summary.mount}, ${summary.pages} pages, ${summary.assets} assets` + (summary.skippedIdentical ? ` (+${summary.skippedIdentical} identical skipped)` : "") + `, ${secs}s`
575
+ );
576
+ }
577
+ }
578
+ function readSettingsBasename(absDocsRoot) {
579
+ try {
580
+ const settings = JSON.parse(fs4.readFileSync(path4.join(absDocsRoot, "docs.json"), "utf-8"));
581
+ const basename2 = settings?.advanced?.basename;
582
+ return basename2 ? normalizeBase(String(basename2)) : void 0;
583
+ } catch {
584
+ return void 0;
585
+ }
586
+ }
587
+ function assertDocsProject(absDocsRoot) {
588
+ if (!fs4.existsSync(absDocsRoot)) {
589
+ throw new XydError(`docsRoot does not exist: ${absDocsRoot}`);
590
+ }
591
+ if (!SETTINGS_FILES.some((f) => fs4.existsSync(path4.join(absDocsRoot, f)))) {
592
+ throw new XydError(`docsRoot is not an xyd project (no ${SETTINGS_FILES.join("/")}): ${absDocsRoot}`);
593
+ }
594
+ }
595
+ function preValidateBasename(absDocsRoot, base) {
596
+ const settingsPath = path4.join(absDocsRoot, "docs.json");
597
+ if (!fs4.existsSync(settingsPath)) return;
598
+ let settings;
599
+ try {
600
+ settings = JSON.parse(fs4.readFileSync(settingsPath, "utf-8"));
601
+ } catch {
602
+ return;
603
+ }
604
+ const basename2 = settings?.advanced?.basename;
605
+ if (!basename2) {
606
+ if (base) return;
607
+ throw new XydError(
608
+ `no mount path for the docs \u2014 set the plugin's \`base\` option (e.g. base: "/docs")
609
+ or add to ${settingsPath}: "advanced": { "basename": "/docs" }`
610
+ );
611
+ }
612
+ if (base && normalizeBase(String(basename2)) !== base) {
613
+ throw new XydError(
614
+ `\`base: "${base}"\` does not match \`advanced.basename: "${basename2}"\` in ${settingsPath}.
615
+ They must be equal \u2014 the basename is baked into every prerendered docs link.`
616
+ );
617
+ }
618
+ }
619
+ export {
620
+ XYD_DEV_INTERNAL_PREFIXES,
621
+ XydError,
622
+ createLogger,
623
+ xyd as default,
624
+ executeMerge,
625
+ mergeDocsBuild,
626
+ normalizeBase,
627
+ normalizeOptions,
628
+ pickFreePort,
629
+ planMerge,
630
+ preValidateBasename,
631
+ resolveCli,
632
+ runDocsBuild,
633
+ spawnDocsDev
634
+ };
635
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/log.ts","../src/options.ts","../src/merge.ts","../src/resolveCli.ts","../src/runBuild.ts","../src/devServer.ts"],"sourcesContent":["import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\n\nimport type { Plugin, ResolvedConfig } from \"vite\";\n\nimport { createLogger, XydError } from \"./log\";\nimport { normalizeBase, normalizeOptions, XydOptions } from \"./options\";\nimport { mergeDocsBuild } from \"./merge\";\nimport { resolveCli } from \"./resolveCli\";\nimport { runDocsBuild } from \"./runBuild\";\nimport { DocsDevHandle, pickFreePort, spawnDocsDev, XYD_DEV_INTERNAL_PREFIXES } from \"./devServer\";\n\nexport type { XydOptions } from \"./options\";\nexport { normalizeBase, normalizeOptions } from \"./options\";\nexport { mergeDocsBuild, planMerge, executeMerge } from \"./merge\";\nexport { resolveCli } from \"./resolveCli\";\nexport { runDocsBuild } from \"./runBuild\";\nexport { spawnDocsDev, pickFreePort, XYD_DEV_INTERNAL_PREFIXES } from \"./devServer\";\nexport { createLogger, XydError } from \"./log\";\n\nconst SETTINGS_FILES = [\"docs.json\", \"docs.ts\", \"docs.tsx\"];\n\ninterface State {\n docsRoot: string;\n clientOutDir?: string;\n isRR: boolean;\n merged: boolean;\n exitMergeScheduled?: boolean;\n}\n\n/**\n * Module-level, keyed by absolute docsRoot: React Router 7 loads the Vite config\n * twice in one process (client build + SSR build), producing TWO plugin instances\n * that must share the client outDir and the merged-once guard.\n */\nconst states = new Map<string, State>();\n\n/**\n * Dev port per docsRoot — frameworks may run the SAME plugin instance through\n * SEVERAL vite configs (Nuxt: client + server dev servers), each invoking the\n * `config` hook. The proxy target injected on every pass and the spawned dev\n * server must agree on one port.\n */\nconst devPorts = new Map<string, number>();\n\n/**\n * Vite plugin for embedding xyd docs into a host app (plain Vite or Vite +\n * React Router):\n *\n * - `vite build`: runs `xyd build` for the docs project in a child process and\n * merges its static output (`.xyd/build/client/`) into the host client outDir.\n * - `vite dev`: spawns `xyd dev` on an internal port and proxies the mount path\n * (+ xyd's /_xyd + /_bun internals, incl. the livereload websocket) — app and\n * docs share one URL/port.\n *\n * The mount path comes from `base` (passed to xyd via XYD_BASENAME) or the docs'\n * own `advanced.basename` — the docs side wins when both are set (must match).\n */\nexport default function xyd(userOptions: XydOptions): Plugin {\n const options = normalizeOptions(userOptions);\n const log = createLogger(options.verbose);\n let config: ResolvedConfig;\n let state: State | undefined;\n let devPort: number | undefined;\n let devBase: string | undefined;\n let devHandle: DocsDevHandle | undefined;\n let disableDocsLiveReload = false;\n\n const isDevMode = () => options.enabled && options.dev;\n\n return {\n name: \"xyd\",\n // closeBundle must run AFTER react-router's own hooks (prerender etc.)\n enforce: \"post\",\n\n /** dev: inject the proxy entries BEFORE the server exists (vite's proxy\n * config is static) — the target port is picked here, the child spawns\n * in configureServer. */\n async config(userConfig, env) {\n if (env.command !== \"serve\" || (env as any).isPreview || !isDevMode()) return;\n\n const absDocsRoot = path.resolve(userConfig.root ? path.resolve(userConfig.root) : process.cwd(), options.docsRoot);\n devBase = options.base ?? readSettingsBasename(absDocsRoot);\n if (!devBase) {\n throw new XydError(\n `dev mode needs the docs mount path — set the plugin's \\`base\\` option (e.g. base: \"/docs\")\\n` +\n ` or \\`advanced.basename\\` in ${absDocsRoot}/docs.json`\n );\n }\n devPort = devPorts.get(absDocsRoot);\n if (devPort === undefined) {\n devPort = await pickFreePort();\n devPorts.set(absDocsRoot, devPort);\n }\n\n const target = `http://localhost:${devPort}`;\n const proxy: Record<string, any> = {\n [devBase]: { target },\n };\n for (const prefix of XYD_DEV_INTERNAL_PREFIXES) {\n // ws: true — /_xyd/livereload is a websocket\n proxy[prefix] = { target, ws: true };\n }\n return { server: { proxy } };\n },\n\n /** dev: spawn `xyd dev` + gate proxied requests until it answers. */\n configureServer(server) {\n if (!isDevMode() || devPort === undefined || !devBase) return;\n\n const ensureSpawned = (): DocsDevHandle => {\n if (!devHandle) {\n const absDocsRoot = path.resolve(config.root, options.docsRoot);\n const cli = resolveCli(options.command, config.root);\n log.info(`docs dev (${cli.source}): ${cli.argv.join(\" \")} dev on :${devPort} → proxied at ${devBase}`);\n const spawnOptions = disableDocsLiveReload\n ? { ...options, env: { ...options.env, XYD_LIVERELOAD: \"0\" } }\n : options;\n devHandle = spawnDocsDev(cli.argv, absDocsRoot, devPort!, devBase!, spawnOptions, log);\n }\n return devHandle;\n };\n\n // Spawn timing: a LISTENING dev server (vite dev / astro dev / react-router\n // dev) gets an eager spawn so the docs are warm. A middlewareMode server\n // (httpServer === null) spawns lazily on the first proxied request —\n // frameworks also create TRANSIENT middlewareMode servers internally\n // (astro build's config/content server) that must not fork a docs dev.\n if (server.httpServer) {\n ensureSpawned();\n server.httpServer.once(\"close\", () => devHandle?.stop());\n }\n\n // Hold proxied requests until the docs dev server is ready (cold starts\n // install the docs workspace) — registered here (pre-internal), so it\n // runs before vite's proxy middleware and the proxy never ECONNREFUSEDs.\n const gated = (url: string) =>\n url === devBase || url.startsWith(devBase + \"/\") ||\n XYD_DEV_INTERNAL_PREFIXES.some((p) => url.startsWith(p));\n server.middlewares.use((req, res, next) => {\n if (!req.url || !gated(req.url)) return next();\n ensureSpawned().ready.then(\n () => next(),\n (err) => {\n res.statusCode = 502;\n res.setHeader(\"content-type\", \"text/plain\");\n res.end(String(err?.message || err));\n }\n );\n });\n },\n\n configResolved(resolved) {\n if (!options.enabled) return;\n config = resolved;\n if (resolved.command !== \"build\") {\n // dev: validate the docs project early, skip the build-state machinery\n const absDocsRoot = path.resolve(resolved.root, options.docsRoot);\n assertDocsProject(absDocsRoot);\n preValidateBasename(absDocsRoot, options.base);\n\n // Nuxt's layered dev proxy crashes (write EPIPE → restart loop) on\n // proxied websocket upgrades — under nuxt the spawned docs dev is\n // told not to inject the livereload client at all (XYD_LIVERELOAD=0),\n // so no upgrade is ever attempted. Docs live-reload degrades\n // gracefully; pages and styles still proxy fine.\n disableDocsLiveReload ||= resolved.plugins.some(\n (p) => typeof p?.name === \"string\" && p.name.startsWith(\"nuxt:\")\n );\n if (disableDocsLiveReload) {\n log.debug(\"ws-hostile host detected (nuxt) — docs livereload disabled\");\n }\n return;\n }\n\n const absDocsRoot = path.resolve(resolved.root, options.docsRoot);\n assertDocsProject(absDocsRoot);\n preValidateBasename(absDocsRoot, options.base);\n\n state = states.get(absDocsRoot);\n if (!state) {\n state = { docsRoot: absDocsRoot, isRR: false, merged: false };\n states.set(absDocsRoot, state);\n }\n state.isRR ||= resolved.plugins.some((p) => typeof p?.name === \"string\" && p.name.startsWith(\"react-router\"));\n if (!resolved.build.ssr) {\n state.clientOutDir = path.resolve(resolved.root, resolved.build.outDir);\n }\n },\n\n async closeBundle() {\n if (!options.enabled || !state || config.command !== \"build\") return;\n\n // SSR-ness, robust across the classic config and the Vite 6+ environments API\n const environment = (this as any).environment;\n const isSSR = environment?.config\n ? environment.config.consumer !== \"client\"\n : !!config.build.ssr;\n\n // The client outDir must come from the CLIENT environment: under the\n // environments API (React Router 8 / Vite 8) the whole build runs in ONE\n // config whose root-level build.outDir is the default (\"dist\") — only\n // environments carry the real per-target outDirs. The client env's\n // closeBundle always fires before the ssr env's, so the value is set\n // by the time a later merge needs it.\n if (!isSSR) {\n const outDir = environment?.config?.build?.outDir ?? config.build.outDir;\n state.clientOutDir = path.resolve(config.root, outDir);\n }\n\n // When to merge:\n // - plain Vite (single client build): right after the client build.\n // - React Router: the client build is followed by an SSR build whose late\n // hooks (prerender) still write into the client outDir — merge only on\n // the FINAL (SSR) build.\n if (state.isRR ? !isSSR : isSSR) return;\n\n // outDir mode (adapter frameworks — SvelteKit adapter-static, Nuxt):\n // the final dir is assembled by the framework INSIDE the same build\n // lifecycle, and closeBundle hooks run in parallel across plugins —\n // a long await here deadlocks against the adapter (observed with\n // SvelteKit on Vite 8: its writeBundle and our closeBundle stall each\n // other). Defer the whole docs-build+merge to process beforeExit,\n // AFTER the entire vite lifecycle has drained.\n if (options.outDir) {\n scheduleExitMerge();\n return;\n }\n await mergeFlow();\n },\n };\n\n function scheduleExitMerge(): void {\n if (!state || state.exitMergeScheduled) return;\n state.exitMergeScheduled = true;\n log.debug(`outDir mode — docs build + merge deferred to end of process (after the adapter)`);\n process.once(\"beforeExit\", () => {\n // async work keeps the process alive; a failure must fail the build\n mergeFlow().catch((err) => {\n console.error(String(err?.message || err));\n process.exitCode = 1;\n });\n });\n }\n\n async function mergeFlow(): Promise<void> {\n if (!state) return;\n if (state.merged) {\n log.debug(\"docs already merged in this process — skipping\");\n return;\n }\n\n // The docs build runs FIRST — it takes long enough that a framework\n // adapter racing us in a parallel closeBundle has finished by the time\n // the merge target is resolved below.\n const cli = resolveCli(options.command, config.root);\n log.info(`building docs (${cli.source}): ${cli.argv.join(\" \")} build [cwd ${state.docsRoot}]`);\n const startedAt = Date.now();\n await runDocsBuild(cli.argv, state.docsRoot, options, log);\n\n // outDir option: by beforeExit the adapter has assembled its final dir —\n // a missing dir now is a real misconfiguration.\n if (options.outDir) {\n const overridden = path.resolve(config.root, options.outDir);\n if (!fs.existsSync(overridden)) {\n throw new XydError(\n `outDir \"${options.outDir}\" does not exist after the build (${overridden}) — did the framework's adapter run?`\n );\n }\n state.clientOutDir = overridden;\n }\n if (!state.clientOutDir) {\n throw new XydError(`client outDir was never resolved — the client build did not run?`);\n }\n\n const docsClientDir = path.join(state.docsRoot, \".xyd\", \"build\", \"client\");\n const summary = mergeDocsBuild(docsClientDir, state.clientOutDir, {\n base: options.base,\n sitemap: options.sitemap,\n robots: options.robots,\n });\n state.merged = true;\n\n const secs = ((Date.now() - startedAt) / 1000).toFixed(1);\n for (const note of summary.notes) log.info(note);\n log.info(\n `merged docs into ${path.relative(config.root, state.clientOutDir) || \".\"} — ` +\n `mount ${summary.mount}, ${summary.pages} pages, ${summary.assets} assets` +\n (summary.skippedIdentical ? ` (+${summary.skippedIdentical} identical skipped)` : \"\") +\n `, ${secs}s`\n );\n }\n}\n\n/** `advanced.basename` from a statically readable docs.json (normalized), else undefined. */\nfunction readSettingsBasename(absDocsRoot: string): string | undefined {\n try {\n const settings = JSON.parse(fs.readFileSync(path.join(absDocsRoot, \"docs.json\"), \"utf-8\"));\n const basename = settings?.advanced?.basename;\n return basename ? normalizeBase(String(basename)) : undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction assertDocsProject(absDocsRoot: string): void {\n if (!fs.existsSync(absDocsRoot)) {\n throw new XydError(`docsRoot does not exist: ${absDocsRoot}`);\n }\n if (!SETTINGS_FILES.some((f) => fs.existsSync(path.join(absDocsRoot, f)))) {\n throw new XydError(`docsRoot is not an xyd project (no ${SETTINGS_FILES.join(\"/\")}): ${absDocsRoot}`);\n }\n}\n\n/**\n * Fail fast when the settings are statically readable (docs.json). docs.ts/tsx\n * can't be parsed here — the post-build output-tree validation in merge.ts covers\n * those (the output encodes the basename regardless of settings format).\n *\n * The mount path can come from EITHER side: the plugin's `base` option (passed\n * into the docs build via XYD_BASENAME) or the docs' own `advanced.basename`.\n * When both are set they must agree.\n */\nexport function preValidateBasename(absDocsRoot: string, base: string | undefined): void {\n const settingsPath = path.join(absDocsRoot, \"docs.json\");\n if (!fs.existsSync(settingsPath)) return;\n\n let settings: any;\n try {\n settings = JSON.parse(fs.readFileSync(settingsPath, \"utf-8\"));\n } catch {\n return; // malformed json — let the docs build report it properly\n }\n const basename = settings?.advanced?.basename;\n if (!basename) {\n if (base) return; // the plugin supplies the mount via XYD_BASENAME\n throw new XydError(\n `no mount path for the docs — set the plugin's \\`base\\` option (e.g. base: \"/docs\")\\n` +\n ` or add to ${settingsPath}: \"advanced\": { \"basename\": \"/docs\" }`\n );\n }\n if (base && normalizeBase(String(basename)) !== base) {\n throw new XydError(\n `\\`base: \"${base}\"\\` does not match \\`advanced.basename: \"${basename}\"\\` in ${settingsPath}.\\n` +\n ` They must be equal — the basename is baked into every prerendered docs link.`\n );\n }\n}\n","const PREFIX = \"[xyd]\";\n\nexport interface Logger {\n info(msg: string): void;\n warn(msg: string): void;\n debug(msg: string): void;\n /** Prefix used for re-emitting the docs build's own output lines. */\n child(line: string): void;\n}\n\nexport function createLogger(verbose: boolean): Logger {\n return {\n info: (msg) => console.log(`${PREFIX} ${msg}`),\n warn: (msg) => console.warn(`${PREFIX} ${msg}`),\n debug: (msg) => { if (verbose) console.log(`${PREFIX} ${msg}`); },\n child: (line) => console.log(`${PREFIX} │ ${line}`),\n };\n}\n\n/** A plugin-originated, already user-readable error (no stack noise needed). */\nexport class XydError extends Error {\n constructor(message: string) {\n super(`${PREFIX} ${message}`);\n this.name = \"XydViteBuildError\";\n }\n}\n","import { XydError } from \"./log\";\n\nexport interface XydOptions {\n /** Path to the docs project (the dir containing docs.json / docs.ts), relative to the Vite root or absolute. Required. */\n docsRoot: string;\n /**\n * Mount path, e.g. \"/docs\". Passed into the docs build via XYD_BASENAME, so the\n * docs settings don't need to declare `advanced.basename` at all. When the docs\n * settings DO declare one, it wins — and must equal `base` (the basename is baked\n * into every prerendered link, so the plugin validates rather than remaps).\n */\n base?: string;\n /** Set to false to turn the plugin into a no-op (e.g. gate docs builds behind an env var). Default true. */\n enabled?: boolean;\n /**\n * Override where the docs merge lands, relative to the Vite root. By default the\n * client build's outDir is used — correct for plain Vite and React Router. Set\n * this for ADAPTER-based frameworks whose final static dir is assembled after\n * the client build (SvelteKit adapter-static: \"build\"; Nuxt: \".output/public\").\n */\n outDir?: string;\n /**\n * Dev-mode integration: during `vite dev`, spawn `xyd dev` for the docs and\n * proxy the mount path (+ xyd's /_xyd and /_bun internals, incl. the\n * livereload websocket) into the SAME origin — app and docs on one URL/port.\n * The spawned dev defaults to xyd's bun engine (XYD_BUN=1; override via `env`)\n * whose URL surface is subpath-clean. Default true; false = build-only plugin.\n */\n dev?: boolean;\n /**\n * Full CLI argv WITHOUT the `build` subcommand, e.g. [\"node\", \"/abs/path/to/cli.js\"] or\n * [\"bunx\", \"xyd-js@latest\"]. A string is whitespace-split. Overrides auto-resolution.\n */\n command?: string | string[];\n /** Extra env for the docs build child process (merged over process.env). */\n env?: Record<string, string>;\n /**\n * NODE_OPTIONS for the child when neither process.env nor `env` provide one.\n * Docs builds are memory-heavy; default \"--max-old-space-size=8192\". `false` disables the default.\n */\n nodeOptions?: string | false;\n /** Policy for the docs build's root sitemap.xml. Default \"skip\" (its URLs currently lack the basename prefix). */\n sitemap?: \"skip\" | \"copy\";\n /** Policy for the docs build's root robots.txt. Default \"skip\". */\n robots?: \"skip\" | \"copy\";\n /** Kill the docs build after N ms and fail the build. Default 0 = no timeout. */\n timeoutMs?: number;\n /** Buffer the docs build output and replay the tail only on failure. Default false = stream live. */\n silent?: boolean;\n /** Plugin debug logging. */\n verbose?: boolean;\n}\n\nexport interface ResolvedXydOptions {\n docsRoot: string;\n base?: string;\n enabled: boolean;\n dev: boolean;\n outDir?: string;\n command?: string[];\n env: Record<string, string>;\n nodeOptions: string | false;\n sitemap: \"skip\" | \"copy\";\n robots: \"skip\" | \"copy\";\n timeoutMs: number;\n silent: boolean;\n verbose: boolean;\n}\n\n/** \"/docs/\" | \"docs\" -> \"/docs\"; undefined passes through. */\nexport function normalizeBase(base?: string): string | undefined {\n if (base === undefined) return undefined;\n const trimmed = String(base).trim().replace(/\\/+$/, \"\");\n if (!trimmed || trimmed === \"/\") {\n throw new XydError(`\\`base\\` must be a non-root mount path like \"/docs\" (got ${JSON.stringify(base)})`);\n }\n return trimmed.startsWith(\"/\") ? trimmed : `/${trimmed}`;\n}\n\nexport function normalizeOptions(options: XydOptions): ResolvedXydOptions {\n if (!options || typeof options.docsRoot !== \"string\" || !options.docsRoot.trim()) {\n throw new XydError(`\\`docsRoot\\` is required — the path to your docs project (the dir containing docs.json)`);\n }\n return {\n docsRoot: options.docsRoot,\n base: normalizeBase(options.base),\n enabled: options.enabled !== false,\n dev: options.dev !== false,\n outDir: options.outDir,\n command: options.command === undefined\n ? undefined\n : Array.isArray(options.command) ? options.command : options.command.split(/\\s+/).filter(Boolean),\n env: options.env || {},\n nodeOptions: options.nodeOptions === undefined ? \"--max-old-space-size=8192\" : options.nodeOptions,\n sitemap: options.sitemap || \"skip\",\n robots: options.robots || \"skip\",\n timeoutMs: options.timeoutMs || 0,\n silent: !!options.silent,\n verbose: !!options.verbose,\n };\n}\n","import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\n\nimport { XydError } from \"./log\";\n\n/**\n * Merge of an xyd static build (`<docsRoot>/.xyd/build/client`) into the host app's\n * client outDir. Pure fs logic — no Vite, no child processes — so it is unit-testable\n * and reusable.\n *\n * xyd output anatomy (docs.json `advanced.basename: \"/docs\"`):\n * assets/ hashed js/css at the CLIENT ROOT — the docs HTML references them as\n * absolute `/assets/*` (no basename prefix), so they must merge into\n * the host's root assets/ dir\n * docs/ the page tree under the basename (incl. docs/public/, docs/llms.txt)\n * public/ root duplicate of the docs public dir — bundled docs JS references\n * un-prefixed `/public/*` paths\n * sitemap.xml URLs currently LACK the basename prefix (upstream documan issue) —\n * skipped by default\n * robots.txt host owns it — skipped by default\n */\n\nexport interface MergeOptions {\n /** Expected mount path (\"/docs\"). When set, validated against the output tree. */\n base?: string;\n sitemap: \"skip\" | \"copy\";\n robots: \"skip\" | \"copy\";\n}\n\ninterface CopyOp {\n src: string;\n dest: string;\n /** classification for reporting */\n kind: \"asset\" | \"public\" | \"page-tree\" | \"root-file\";\n}\n\nexport interface MergePlan {\n ops: CopyOp[];\n /** conflicting dest paths (exist with DIFFERENT content) — a non-empty list must fail the merge */\n conflicts: string[];\n /** dest files that already exist with identical content (skipped) */\n skippedIdentical: number;\n /** informational notes (skipped sitemap/robots, …) */\n notes: string[];\n /** .html files in the page tree */\n pages: number;\n assets: number;\n /** the resolved mount path, e.g. \"/docs\" */\n mount: string;\n}\n\nexport interface MergeSummary {\n pages: number;\n assets: number;\n skippedIdentical: number;\n notes: string[];\n mount: string;\n}\n\nfunction sameContent(a: string, b: string): boolean {\n const sa = fs.statSync(a);\n const sb = fs.statSync(b);\n if (sa.size !== sb.size) return false;\n // Uint8Array wrappers dodge the Buffer generic clash across @types/node versions\n return Buffer.compare(new Uint8Array(fs.readFileSync(a)), new Uint8Array(fs.readFileSync(b))) === 0;\n}\n\n/** Recursively plan copying `srcDir` into `destDir` with the identical-skip / different-conflict rule. */\nfunction planDir(srcDir: string, destDir: string, kind: CopyOp[\"kind\"], plan: MergePlan) {\n for (const entry of fs.readdirSync(srcDir, { withFileTypes: true })) {\n const src = path.join(srcDir, entry.name);\n const dest = path.join(destDir, entry.name);\n if (entry.isDirectory()) {\n planDir(src, dest, kind, plan);\n } else {\n if (fs.existsSync(dest)) {\n if (sameContent(src, dest)) {\n plan.skippedIdentical++;\n } else {\n plan.conflicts.push(dest);\n }\n continue;\n }\n plan.ops.push({ src, dest, kind });\n if (kind === \"asset\") plan.assets++;\n if (kind === \"page-tree\" && entry.name.endsWith(\".html\")) plan.pages++;\n }\n }\n}\n\n/**\n * Classify the docs client dir and produce a merge plan. Throws XydError on\n * structural problems (missing basename, base mismatch). Collects ALL content\n * conflicts instead of throwing on the first, so a doomed merge reports completely.\n */\nexport function planMerge(docsClientDir: string, hostOutDir: string, options: MergeOptions): MergePlan {\n if (!fs.existsSync(docsClientDir)) {\n throw new XydError(`docs build output not found at ${docsClientDir} — did the docs build run?`);\n }\n\n // The basename is baked into every prerendered link, so a docs build without one\n // would put its page tree at the client ROOT and collide with the host app.\n if (fs.existsSync(path.join(docsClientDir, \"index.html\"))) {\n throw new XydError(\n `the docs build has NO basename — its pages sit at the output root and would collide with your app.\\n` +\n (options.base\n ? ` \\`base: \"${options.base}\"\\` was passed (via XYD_BASENAME) but the resolved xyd CLI ignored it —\\n` +\n ` upgrade xyd to a version that supports XYD_BASENAME, or add to your docs settings:\\n` +\n ` \"advanced\": { \"basename\": \"${options.base}\" }`\n : ` Set the plugin's \\`base\\` option (e.g. base: \"/docs\") or add to your docs settings:\\n` +\n ` \"advanced\": { \"basename\": \"/docs\" }`)\n );\n }\n\n const plan: MergePlan = { ops: [], conflicts: [], skippedIdentical: 0, notes: [], pages: 0, assets: 0, mount: \"\" };\n const pageTreeDirs: string[] = [];\n\n for (const entry of fs.readdirSync(docsClientDir, { withFileTypes: true })) {\n const src = path.join(docsClientDir, entry.name);\n\n // .vite/ is Vite's own build metadata (manifest.json) — not servable content,\n // and the host build may emit its own (React Router does) — never merge it.\n if (entry.name === \".vite\") continue;\n\n if (entry.isDirectory() && entry.name === \"assets\") {\n planDir(src, path.join(hostOutDir, \"assets\"), \"asset\", plan);\n continue;\n }\n if (entry.isDirectory() && entry.name === \"public\") {\n // never rm -rf — merge file-by-file so host-owned public files survive\n planDir(src, path.join(hostOutDir, \"public\"), \"public\", plan);\n continue;\n }\n if (!entry.isDirectory() && (entry.name === \"sitemap.xml\" || entry.name === \"robots.txt\")) {\n const policy = entry.name === \"sitemap.xml\" ? options.sitemap : options.robots;\n if (policy === \"copy\") {\n const dest = path.join(hostOutDir, entry.name);\n if (fs.existsSync(dest)) {\n plan.notes.push(`kept the host's ${entry.name} (docs copy skipped)`);\n } else {\n plan.ops.push({ src, dest, kind: \"root-file\" });\n if (entry.name === \"sitemap.xml\") {\n plan.notes.push(`copied the docs sitemap.xml — note: its URLs currently lack the basename prefix (xyd issue)`);\n }\n }\n } else {\n plan.notes.push(`skipped docs ${entry.name} (policy: skip)`);\n }\n continue;\n }\n\n // Everything else is the basename page tree (a \"docs/\" dir, flatten artifacts\n // like a root \"docs.html\", or multi-segment basenames like \"help/docs/\").\n if (entry.isDirectory()) {\n pageTreeDirs.push(entry.name);\n planDir(src, path.join(hostOutDir, entry.name), \"page-tree\", plan);\n } else {\n const dest = path.join(hostOutDir, entry.name);\n if (fs.existsSync(dest)) {\n if (sameContent(src, dest)) plan.skippedIdentical++;\n else plan.conflicts.push(dest);\n } else {\n plan.ops.push({ src, dest, kind: \"page-tree\" });\n if (entry.name.endsWith(\".html\")) plan.pages++;\n }\n }\n }\n\n if (!pageTreeDirs.length) {\n throw new XydError(`no page tree found in the docs build output at ${docsClientDir} — the docs build produced nothing to mount`);\n }\n plan.mount = \"/\" + pageTreeDirs[0];\n\n if (options.base) {\n const baseTop = options.base.replace(/^\\/+/, \"\").split(\"/\")[0];\n if (!pageTreeDirs.includes(baseTop)) {\n throw new XydError(\n `\\`base: \"${options.base}\"\\` does not match the docs build output (found: ${pageTreeDirs.map((d) => \"/\" + d).join(\", \")}).\\n` +\n ` \\`base\\` must equal \\`advanced.basename\\` in the docs settings — the basename is baked into every prerendered link.`\n );\n }\n plan.mount = options.base;\n }\n\n // Pretty-URL portability: xyd emits flat `<slug>.html` pages, which clean-URL\n // hosts (Netlify, `serve`) map from extensionless links — but express-style\n // static servers (react-router-serve) don't. Mirror every page as\n // `<slug>/index.html` too, so `/docs/overview` resolves everywhere via the\n // universal directory-index convention (express 301s to the trailing slash).\n const planned = new Set(plan.ops.map((op) => op.dest));\n for (const op of [...plan.ops]) {\n if (op.kind !== \"page-tree\" || !op.dest.endsWith(\".html\") || path.basename(op.dest) === \"index.html\") continue;\n const mirror = path.join(op.dest.slice(0, -\".html\".length), \"index.html\");\n if (planned.has(mirror) || fs.existsSync(mirror)) continue;\n planned.add(mirror);\n plan.ops.push({ src: op.src, dest: mirror, kind: \"page-tree\" });\n }\n\n return plan;\n}\n\nexport function executeMerge(plan: MergePlan): void {\n for (const op of plan.ops) {\n fs.mkdirSync(path.dirname(op.dest), { recursive: true });\n fs.copyFileSync(op.src, op.dest);\n }\n}\n\nexport function formatConflicts(conflicts: string[], hostOutDir: string): string {\n const rel = conflicts.map((c) => ` - ${path.relative(hostOutDir, c)}`).join(\"\\n\");\n return (\n `the host build already emitted ${conflicts.length} file(s) with DIFFERENT content at the docs merge paths:\\n${rel}\\n` +\n ` Host routes/assets must not overlap the docs mount path (\\`advanced.basename\\`).`\n );\n}\n\n/** plan → throw on conflicts → execute. The single entry point the plugin (and tests) use. */\nexport function mergeDocsBuild(docsClientDir: string, hostOutDir: string, options: MergeOptions): MergeSummary {\n const plan = planMerge(docsClientDir, hostOutDir, options);\n if (plan.conflicts.length) {\n throw new XydError(formatConflicts(plan.conflicts, hostOutDir));\n }\n executeMerge(plan);\n return {\n pages: plan.pages,\n assets: plan.assets,\n skippedIdentical: plan.skippedIdentical,\n notes: plan.notes,\n mount: plan.mount,\n };\n}\n","import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport { createRequire } from \"node:module\";\n\nimport { XydError } from \"./log\";\n\nexport interface ResolvedCli {\n /** argv WITHOUT the `build` subcommand, e.g. [\"node\", \"/…/xyd-cli/dist/index.js\"] */\n argv: string[];\n /** where it came from — for logging */\n source: \"command option\" | \"local xyd-js\" | \"local @xyd-js/cli\" | \"PATH\";\n}\n\nfunction binFromPackage(pkgJsonPath: string): string | null {\n try {\n const pkg = JSON.parse(fs.readFileSync(pkgJsonPath, \"utf-8\"));\n const bin = typeof pkg.bin === \"string\" ? pkg.bin : pkg.bin?.xyd;\n if (!bin) return null;\n const abs = path.join(path.dirname(pkgJsonPath), bin);\n return fs.existsSync(abs) ? abs : null;\n } catch {\n return null;\n }\n}\n\n/** Scan PATH for an executable named `xyd` (covers the compiled native binary + global installs). */\nfunction xydOnPath(): string | null {\n const exts = process.platform === \"win32\" ? [\".exe\", \".cmd\", \".bat\", \"\"] : [\"\"];\n for (const dir of (process.env.PATH || \"\").split(path.delimiter)) {\n if (!dir) continue;\n for (const ext of exts) {\n const candidate = path.join(dir, `xyd${ext}`);\n try {\n fs.accessSync(candidate, fs.constants.X_OK);\n if (fs.statSync(candidate).isFile()) return candidate;\n } catch { /* not here */ }\n }\n }\n return null;\n}\n\n/**\n * Resolve which xyd CLI to spawn, in order:\n * 1. the `command` option (full control)\n * 2. `xyd-js` / `@xyd-js/cli` installed in the HOST project\n * 3. an `xyd` executable on PATH\n * No `npx xyd-js@latest` auto-fallback — a build silently downloading `latest` is not reproducible.\n */\nexport function resolveCli(command: string[] | undefined, hostRoot: string): ResolvedCli {\n if (command?.length) {\n return { argv: command, source: \"command option\" };\n }\n\n const require = createRequire(path.join(hostRoot, \"package.json\"));\n for (const [pkgName, source] of [[\"xyd-js\", \"local xyd-js\"], [\"@xyd-js/cli\", \"local @xyd-js/cli\"]] as const) {\n try {\n const bin = binFromPackage(require.resolve(`${pkgName}/package.json`));\n if (bin) return { argv: [process.execPath, bin], source };\n } catch { /* not installed */ }\n }\n\n const onPath = xydOnPath();\n if (onPath) return { argv: [onPath], source: \"PATH\" };\n\n throw new XydError(\n `could not find the xyd CLI.\\n` +\n ` Fix one of:\\n` +\n ` - npm i -D xyd-js (recommended: pin a version)\\n` +\n ` - install the \\`xyd\\` binary on PATH (https://xyd.dev)\\n` +\n ` - pass \\`command: [\"bunx\", \"xyd-js@<version>\"]\\` (or any argv) to the plugin`\n );\n}\n","import { spawn } from \"node:child_process\";\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport * as readline from \"node:readline\";\n\nimport { Logger, XydError } from \"./log\";\nimport { ResolvedXydOptions } from \"./options\";\n\n/**\n * Spawn `<cli> build` with cwd = the docs project root (the xyd CLI resolves\n * everything — settings, content, .xyd output — from process.cwd(); there is no\n * directory argument).\n */\nexport async function runDocsBuild(\n argv: string[],\n docsRoot: string,\n options: ResolvedXydOptions,\n log: Logger\n): Promise<void> {\n try {\n return await runDocsBuildOnce(argv, docsRoot, options, log);\n } catch (err: any) {\n // The docs builder intermittently skips its prerender step (client build\n // succeeds, zero pages emitted, exit 0) — the structural validation catches\n // it, and one retry reliably recovers. Genuine breakage fails again.\n if (!String(err?.message || \"\").includes(\"looks broken\")) throw err;\n log.warn(`docs build output invalid (${err.message.split(\"(\")[1]?.split(\")\")[0] || \"?\"}) — retrying once`);\n return await runDocsBuildOnce(argv, docsRoot, options, log);\n }\n}\n\nasync function runDocsBuildOnce(\n argv: string[],\n docsRoot: string,\n options: ResolvedXydOptions,\n log: Logger\n): Promise<void> {\n const env: NodeJS.ProcessEnv = { ...process.env, ...options.env, NODE_ENV: \"production\" };\n if (options.nodeOptions !== false && !env.NODE_OPTIONS) {\n // docs builds are memory-heavy (two full Vite builds)\n env.NODE_OPTIONS = options.nodeOptions;\n }\n // The mount path flows into the docs build via XYD_BASENAME, so the docs\n // settings don't have to duplicate it — a docs-side `advanced.basename`\n // still wins inside xyd (and a mismatch fails validation here).\n if (options.base && !env.XYD_BASENAME) {\n env.XYD_BASENAME = options.base;\n }\n\n const startedAt = Date.now();\n const tail: string[] = [];\n\n await new Promise<void>((resolve, reject) => {\n const child = spawn(argv[0], [...argv.slice(1), \"build\"], {\n cwd: docsRoot,\n env,\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n\n const onLine = (line: string) => {\n if (options.silent) {\n tail.push(line);\n if (tail.length > 200) tail.shift();\n } else {\n log.child(line);\n }\n };\n readline.createInterface({ input: child.stdout! }).on(\"line\", onLine);\n readline.createInterface({ input: child.stderr! }).on(\"line\", onLine);\n\n let timedOut = false;\n let killTimer: ReturnType<typeof setTimeout> | undefined;\n let timeoutTimer: ReturnType<typeof setTimeout> | undefined;\n if (options.timeoutMs > 0) {\n timeoutTimer = setTimeout(() => {\n timedOut = true;\n child.kill(\"SIGTERM\");\n killTimer = setTimeout(() => child.kill(\"SIGKILL\"), 5000);\n }, options.timeoutMs);\n }\n\n child.on(\"error\", (err) => reject(new XydError(`failed to spawn the docs build (${argv.join(\" \")}): ${err.message}`)));\n child.on(\"close\", (code) => {\n if (timeoutTimer) clearTimeout(timeoutTimer);\n if (killTimer) clearTimeout(killTimer);\n if (timedOut) {\n return reject(new XydError(`docs build timed out after ${options.timeoutMs}ms`));\n }\n if (code !== 0) {\n if (options.silent && tail.length) {\n for (const line of tail) log.child(line);\n }\n return reject(new XydError(`docs build exited with code ${code} — see the [xyd] │ output above`));\n }\n resolve();\n });\n });\n\n validateDocsOutput(path.join(docsRoot, \".xyd\", \"build\", \"client\"), startedAt);\n}\n\nfunction hasHtml(dir: string, depth = 0): boolean {\n if (depth > 6) return false;\n for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {\n if (entry.isFile() && entry.name.endsWith(\".html\")) return true;\n if (entry.isDirectory() && hasHtml(path.join(dir, entry.name), depth + 1)) return true;\n }\n return false;\n}\n\n/**\n * The docs builder can swallow Vite build failures and still exit 0 (its build\n * pipeline logs \"Build failed\" without rethrowing), so a zero exit code is not\n * proof of success — validate the output structurally.\n */\nexport function validateDocsOutput(clientDir: string, sinceMs: number): void {\n const fail = (why: string) => {\n throw new XydError(\n `docs build output at ${clientDir} looks broken (${why}).\\n` +\n ` Note: some xyd versions exit 0 even when the underlying build fails — check the [xyd] │ output above for errors.`\n );\n };\n if (!fs.existsSync(clientDir)) fail(\"directory missing\");\n const entries = fs.readdirSync(clientDir);\n if (!entries.length) fail(\"directory empty\");\n const newest = Math.max(...entries.map((e) => fs.statSync(path.join(clientDir, e)).mtimeMs));\n if (newest < sinceMs - 5000) fail(\"output predates this build — stale result from a previous run\");\n const assetsDir = path.join(clientDir, \"assets\");\n if (!fs.existsSync(assetsDir) || !fs.readdirSync(assetsDir).length) fail(\"no assets/ output\");\n if (!hasHtml(clientDir)) fail(\"no prerendered .html pages\");\n}\n","import { ChildProcess, spawn } from \"node:child_process\";\nimport * as net from \"node:net\";\nimport * as readline from \"node:readline\";\n\nimport { Logger, XydError } from \"./log\";\nimport { ResolvedXydOptions } from \"./options\";\n\n/**\n * Dev-mode integration: spawn `xyd dev` for the docs project on an internal\n * port and let the vite dev server proxy it — app and docs share one origin.\n *\n * The spawned dev defaults to xyd's BUN engine (XYD_BUN=1 — a no-op for the\n * native binary, an opt-in for the JS CLI): its URL surface is subpath-clean —\n * pages under the basename plus the /_xyd/* (css/js + livereload websocket)\n * and /_bun/* internals — so a prefix proxy covers everything. The vite-engine\n * dev server serves unprefixed /@vite//@fs module URLs that would collide with\n * the host's own, which is why it is not the default.\n */\n\n/** xyd dev endpoints that live OUTSIDE the basename (safe, host-unused prefixes). */\nexport const XYD_DEV_INTERNAL_PREFIXES = [\"/_xyd\", \"/_bun\"];\n\nexport function pickFreePort(): Promise<number> {\n return new Promise((resolve, reject) => {\n const srv = net.createServer();\n srv.listen(0, () => {\n const port = (srv.address() as net.AddressInfo).port;\n srv.close(() => resolve(port));\n });\n srv.on(\"error\", reject);\n });\n}\n\nexport interface DocsDevHandle {\n /** resolves when the docs dev server answers HTTP (any status) */\n ready: Promise<void>;\n stop(): void;\n}\n\nexport function spawnDocsDev(\n argv: string[],\n docsRoot: string,\n port: number,\n base: string,\n options: ResolvedXydOptions,\n log: Logger\n): DocsDevHandle {\n const env: NodeJS.ProcessEnv = {\n ...process.env,\n ...options.env,\n XYD_PORT: String(port),\n XYD_BASENAME: base,\n };\n // bun engine by default (see module doc); an explicit env wins\n if (env.XYD_BUN === undefined) env.XYD_BUN = \"1\";\n\n const child: ChildProcess = spawn(argv[0], [...argv.slice(1), \"dev\"], {\n cwd: docsRoot,\n env,\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n const onLine = (line: string) => log.child(line);\n readline.createInterface({ input: child.stdout! }).on(\"line\", onLine);\n readline.createInterface({ input: child.stderr! }).on(\"line\", onLine);\n\n let exited = false;\n let exitCode: number | null = null;\n child.on(\"close\", (code) => {\n exited = true;\n exitCode = code;\n });\n child.on(\"error\", (err) => {\n exited = true;\n log.warn(`failed to spawn the docs dev server (${argv.join(\" \")}): ${err.message}`);\n });\n\n // Readiness: ANY http response (even a 404) means the server is up. Budget is\n // generous — a cold start installs the docs workspace (.xyd/host) first.\n const budgetMs = 5 * 60 * 1000;\n const ready = (async () => {\n const started = Date.now();\n while (Date.now() - started < budgetMs) {\n if (exited) {\n throw new XydError(`the docs dev server exited (code ${exitCode}) before becoming ready — see the [xyd] │ output above`);\n }\n try {\n await fetch(`http://localhost:${port}${base}`, { redirect: \"manual\" });\n return;\n } catch {\n /* not up yet */\n }\n await new Promise((r) => setTimeout(r, 500));\n }\n throw new XydError(`the docs dev server did not answer on :${port} within ${budgetMs / 1000}s`);\n })();\n ready.catch(() => { /* surfaced via the gate middleware; avoid unhandled rejection */ });\n\n const stop = () => {\n if (!exited) child.kill(\"SIGTERM\");\n };\n // Lifecycle safety net. Interactive Ctrl-C already reaches the child (same\n // process group), and a graceful vite shutdown triggers the httpServer close\n // handler — but a bare SIGTERM/SIGINT to the vite process runs NO exit\n // handlers, orphaning the docs dev server. Kill the child, then re-raise the\n // signal's default when nobody else handles it (vite's own handlers, when\n // present, proceed normally).\n process.once(\"exit\", stop);\n for (const sig of [\"SIGTERM\", \"SIGINT\"] as const) {\n process.once(sig, () => {\n stop();\n if (process.listenerCount(sig) === 0) {\n process.kill(process.pid, sig);\n }\n });\n }\n\n return { ready, stop };\n}\n"],"mappings":"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","names":["fs","path","fs","path","require","fs","path","resolve","spawn","readline","resolve","spawn","absDocsRoot","basename"]}
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@xyd-js/vite-plugin",
3
+ "version": "0.0.0-build-cdbb0d7-20260901160230",
4
+ "description": "Vite plugin that builds an xyd docs project during `vite build` and merges the static docs output into the host app's build",
5
+ "main": "./dist/index.js",
6
+ "types": "./dist/index.d.ts",
7
+ "type": "module",
8
+ "license": "MIT",
9
+ "files": [
10
+ "dist"
11
+ ],
12
+ "exports": {
13
+ "./package.json": "./package.json",
14
+ ".": {
15
+ "types": "./dist/index.d.ts",
16
+ "import": "./dist/index.js"
17
+ }
18
+ },
19
+ "keywords": [
20
+ "xyd",
21
+ "vite",
22
+ "vite-plugin",
23
+ "docs",
24
+ "documentation"
25
+ ],
26
+ "peerDependencies": {
27
+ "vite": ">=5"
28
+ },
29
+ "peerDependenciesMeta": {
30
+ "vite": {
31
+ "optional": true
32
+ }
33
+ },
34
+ "devDependencies": {
35
+ "rimraf": "^3.0.2",
36
+ "tsup": "^8.3.0",
37
+ "vite": "^7.0.0",
38
+ "vitest": "^2.1.8"
39
+ },
40
+ "scripts": {
41
+ "clean": "rimraf dist",
42
+ "prebuild": "pnpm clean",
43
+ "build": "tsup",
44
+ "test": "vitest",
45
+ "ci:test": "vitest run"
46
+ }
47
+ }