@ox-content/vite-plugin 2.8.0 → 2.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,59 +1,22 @@
1
- import { n as transformMermaidStatic, r as importNapiModule, t as mermaidClientScript } from "./mermaid.mjs";
2
- import { n as resetTabGroupCounter, r as transformTabs, t as generateTabsCSS } from "./tabs2.mjs";
3
- import { n as transformYouTube, t as extractVideoId } from "./youtube2.mjs";
4
- import { a as transformGitHub, i as prefetchGitHubRepos, n as fetchRepoData, t as collectGitHubRepos } from "./github2.mjs";
5
- import { a as transformOgp, i as prefetchOgpData, n as fetchOgpData, t as collectOgpUrls } from "./ogp2.mjs";
1
+ import { a as importNapiModuleSync, c as __exportAll, d as __toESM, i as importNapiModule, l as __require, o as __commonJSMin, r as transformMermaidStatic, s as __esmMin, t as mermaidClientScript, u as __toCommonJS } from "./mermaid.mjs";
2
+ import { i as transformTabs, n as resetTabGroupCounter, t as generateTabsCSS } from "./tabs.mjs";
3
+ import { n as transformYouTube, t as extractVideoId } from "./youtube.mjs";
4
+ import { a as transformGitHub, i as prefetchGitHubRepos, n as fetchRepoData, t as collectGitHubRepos } from "./github.mjs";
5
+ import { a as transformOgp, i as prefetchOgpData, n as fetchOgpData, t as collectOgpUrls } from "./ogp.mjs";
6
6
  import { createRequire } from "node:module";
7
- import * as path$2 from "path";
8
- import path from "path";
7
+ import * as path$1 from "path";
9
8
  import { unified } from "unified";
10
9
  import rehypeParse from "rehype-parse";
11
10
  import rehypeStringify from "rehype-stringify";
12
11
  import { createHighlighter } from "shiki";
13
- import * as path$1 from "node:path";
12
+ import * as path from "node:path";
14
13
  import { dirname, join } from "node:path";
15
14
  import * as fs$2 from "fs";
16
- import { createHash } from "node:crypto";
17
15
  import * as fs$1 from "fs/promises";
18
16
  import { glob } from "glob";
19
17
  import * as crypto from "crypto";
20
18
  import * as fs from "node:fs/promises";
21
19
  import { mkdir, writeFile } from "node:fs/promises";
22
- //#region \0rolldown/runtime.js
23
- var __create = Object.create;
24
- var __defProp = Object.defineProperty;
25
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
26
- var __getOwnPropNames = Object.getOwnPropertyNames;
27
- var __getProtoOf = Object.getPrototypeOf;
28
- var __hasOwnProp = Object.prototype.hasOwnProperty;
29
- var __esmMin = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
30
- var __commonJSMin = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
31
- var __exportAll = (all, no_symbols) => {
32
- let target = {};
33
- for (var name in all) __defProp(target, name, {
34
- get: all[name],
35
- enumerable: true
36
- });
37
- if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
38
- return target;
39
- };
40
- var __copyProps = (to, from, except, desc) => {
41
- if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
42
- key = keys[i];
43
- if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
44
- get: ((k) => from[k]).bind(null, key),
45
- enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
46
- });
47
- }
48
- return to;
49
- };
50
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
51
- value: mod,
52
- enumerable: true
53
- }) : target, mod));
54
- var __toCommonJS = (mod) => __hasOwnProp.call(mod, "module.exports") ? mod["module.exports"] : __copyProps(__defProp({}, "__esModule", { value: true }), mod);
55
- var __require = /* @__PURE__ */ createRequire(import.meta.url);
56
- //#endregion
57
20
  //#region src/environment.ts
58
21
  /**
59
22
  * Creates the Markdown processing environment configuration.
@@ -7112,241 +7075,11 @@ if (import.meta.hot) {
7112
7075
  }
7113
7076
  //#endregion
7114
7077
  //#region src/nav-generator.ts
7115
- /**
7116
- * Navigation Metadata Generator for API Documentation
7117
- *
7118
- * This module provides utilities for generating sidebar navigation structures
7119
- * from extracted documentation. It automatically:
7120
- *
7121
- * - **Extracts file information**: Gets display names and file paths
7122
- * - **Formats names**: Converts technical names to readable titles
7123
- * - **Generates TypeScript**: Creates importable nav.ts files
7124
- * - **Maintains hierarchy**: Supports nested navigation structures
7125
- *
7126
- * ## Generated Navigation Format
7127
- *
7128
- * The generated navigation is TypeScript-based for type safety and IDE support:
7129
- *
7130
- * ```typescript
7131
- * export const apiNav: NavItem[] = [
7132
- * { title: 'Overview', path: '/api/index' },
7133
- * { title: 'Transform', path: '/api/transform' },
7134
- * { title: 'Types', path: '/api/types' },
7135
- * // ... auto-generated from documentation
7136
- * ] as const;
7137
- * ```
7138
- *
7139
- * ## Integration
7140
- *
7141
- * The generated nav.ts file can be imported directly:
7142
- *
7143
- * ```typescript
7144
- * // In your Vue/React component
7145
- * import { apiNav } from '../api/nav';
7146
- *
7147
- * const apiItems = apiNav.map(item => ({
7148
- * ...item,
7149
- * file: () => import(`../api/${item.path.split('/').pop()}.md`)
7150
- * }));
7151
- * ```
7152
- *
7153
- * @example
7154
- * ```typescript
7155
- * import { generateNavMetadata, generateNavCode } from './nav-generator';
7156
- *
7157
- * const extracted = [
7158
- * { file: 'transform.ts', entries: [...] },
7159
- * { file: 'types.ts', entries: [...] },
7160
- * ];
7161
- *
7162
- * const navItems = generateNavMetadata(extracted);
7163
- * // => [
7164
- * // { title: 'Transform', path: '/api/transform' },
7165
- * // { title: 'Types', path: '/api/types' },
7166
- * // ]
7167
- *
7168
- * const code = generateNavCode(navItems);
7169
- * // => TypeScript code ready to write to nav.ts
7170
- * ```
7171
- */
7172
- /**
7173
- * Generates sidebar navigation metadata from extracted documentation.
7174
- *
7175
- * Takes an array of extracted documentation and produces a flat navigation
7176
- * structure suitable for sidebar menus. Items are:
7177
- * - Sorted alphabetically by display name
7178
- * - Formatted with readable titles
7179
- * - Prefixed with the specified base path
7180
- *
7181
- * ## Naming Conventions
7182
- *
7183
- * - `transform.ts` → `{ title: 'Transform', path: '/api/transform' }`
7184
- * - `nav-generator.ts` → `{ title: 'Nav Generator', path: '/api/nav-generator' }`
7185
- * - `index.ts` or `index-module.ts` → `{ title: 'Overview', path: '/api/index' }`
7186
- * - `types.ts` → `{ title: 'Types', path: '/api/types' }`
7187
- *
7188
- * ## Sorting
7189
- *
7190
- * Items are sorted alphabetically by display title for consistent ordering.
7191
- * Special item 'Overview' sorts naturally with others (O comes after most letters).
7192
- *
7193
- * ## Path Generation
7194
- *
7195
- * The generated paths are used to import corresponding Markdown files:
7196
- * - Path `/api/transform` → Import from `../api/transform.md`
7197
- * - Path `/api/index` → Import from `../api/index.md`
7198
- *
7199
- * @param docs - Array of extracted documentation (file + entries)
7200
- * @param basePath - Base path prefix for navigation URLs (default: '/api')
7201
- * Use '/api' for main API docs, '/helpers' for utilities, etc.
7202
- *
7203
- * @returns Array of navigation items ready to use or export to TypeScript
7204
- *
7205
- * @example
7206
- * ```typescript
7207
- * const navItems = generateNavMetadata(
7208
- * [
7209
- * { file: 'transform.ts', entries: [...] },
7210
- * { file: 'docs.ts', entries: [...] },
7211
- * { file: 'types.ts', entries: [...] },
7212
- * ],
7213
- * '/api'
7214
- * );
7215
- *
7216
- * // Returns:
7217
- * // [
7218
- * // { title: 'Docs', path: '/api/docs' },
7219
- * // { title: 'Transform', path: '/api/transform' },
7220
- * // { title: 'Types', path: '/api/types' },
7221
- * // ]
7222
- * ```
7223
- *
7224
- * @see generateNavCode For converting these items to TypeScript code
7225
- */
7226
7078
  function generateNavMetadata(docs, basePath = "/api") {
7227
- return [...docs].sort((a, b) => {
7228
- const aName = getDocDisplayName(a.file);
7229
- const bName = getDocDisplayName(b.file);
7230
- return aName.localeCompare(bName);
7231
- }).map((doc) => ({
7232
- title: getDocDisplayName(doc.file),
7233
- path: `${basePath}/${getDocFileName(doc.file)}`
7234
- }));
7235
- }
7236
- /**
7237
- * Gets the human-readable display name for a documentation file.
7238
- *
7239
- * Transforms file paths and names into proper title case:
7240
- * - Extracts base name (e.g., 'transform.ts' → 'transform')
7241
- * - Converts kebab-case to Title Case (e.g., 'nav-generator' → 'Nav Generator')
7242
- * - Converts camelCase to Title Case (e.g., 'transformMarkdown' → 'Transform Markdown')
7243
- * - Handles special cases (index → 'Overview')
7244
- *
7245
- * ## Examples
7246
- *
7247
- * - `'/path/to/transform.ts'` → `'Transform'`
7248
- * - `'nav-generator.ts'` → `'Nav Generator'`
7249
- * - `'index.ts'` → `'Overview'`
7250
- * - `'index-module.ts'` → `'Overview'`
7251
- * - `'myFunction.ts'` → `'My Function'` (with camelCase handling)
7252
- *
7253
- * @param filePath - Full or relative file path
7254
- * @returns Formatted display name suitable for UI labels
7255
- *
7256
- * @internal
7257
- */
7258
- function getDocDisplayName(filePath) {
7259
- const fileName = path.basename(filePath, path.extname(filePath));
7260
- if (fileName === "index" || fileName === "index-module") return "Overview";
7261
- return fileName.replace(/[-_]([a-z])/g, (_, char) => " " + char.toUpperCase()).replace(/^[a-z]/, (char) => char.toUpperCase());
7079
+ return importNapiModuleSync().generateDocsNavMetadata(docs.map((doc) => doc.file), basePath);
7262
7080
  }
7263
- /**
7264
- * Gets the file name (without extension) for use in navigation paths.
7265
- *
7266
- * This handles filename conflicts that may occur during generation:
7267
- * - Preserves most names as-is
7268
- * - Special handling for index files to maintain consistency
7269
- *
7270
- * @param filePath - Source file path
7271
- * @returns File name without extension, ready for URL paths
7272
- *
7273
- * @internal
7274
- */
7275
- function getDocFileName(filePath) {
7276
- const fileName = path.basename(filePath, path.extname(filePath));
7277
- if (fileName === "index") return "index";
7278
- return fileName;
7279
- }
7280
- /**
7281
- * Generates TypeScript code for navigation metadata export.
7282
- *
7283
- * Creates a complete, self-contained TypeScript file that:
7284
- * - Defines the NavItem interface
7285
- * - Exports navigation items as a const
7286
- * - Uses `as const` for type-safe literal types
7287
- * - Includes auto-generation notice
7288
- *
7289
- * The generated code is production-ready and suitable for direct import
7290
- * in Vue, React, or vanilla TypeScript applications.
7291
- *
7292
- * ## Generated Code Example
7293
- *
7294
- * ```typescript
7295
- * export interface NavItem {
7296
- * title: string;
7297
- * path: string;
7298
- * children?: NavItem[];
7299
- * }
7300
- *
7301
- * export const apiNav: NavItem[] = [
7302
- * { "title": "Docs", "path": "/api/docs" },
7303
- * { "title": "Transform", "path": "/api/transform" },
7304
- * // ...
7305
- * ] as const;
7306
- * ```
7307
- *
7308
- * ## Features
7309
- *
7310
- * - **Type Safety**: Includes NavItem interface definition
7311
- * - **Readonly**: Uses `as const` to ensure immutability
7312
- * - **IDE Support**: Full IntelliSense and autocomplete
7313
- * - **Self-Documenting**: Includes notice that file is auto-generated
7314
- *
7315
- * @param navItems - Array of navigation items to export
7316
- * @param exportName - Name of the exported const (default: 'apiNav')
7317
- * Use custom names for different navigation sections
7318
- *
7319
- * @returns Complete TypeScript source code as string,
7320
- * ready to write to a .ts file
7321
- *
7322
- * @example
7323
- * ```typescript
7324
- * const navItems = [
7325
- * { title: 'Home', path: '/api/index' },
7326
- * { title: 'Transform', path: '/api/transform' },
7327
- * ];
7328
- *
7329
- * const code = generateNavCode(navItems, 'apiNav');
7330
- * await fs.promises.writeFile('docs/api/nav.ts', code, 'utf-8');
7331
- * ```
7332
- *
7333
- * @see generateNavMetadata For generating NavItem arrays from extracted docs
7334
- */
7335
7081
  function generateNavCode(navItems, exportName = "apiNav") {
7336
- return `/**
7337
- * Auto-generated API documentation navigation.
7338
- * This file is automatically generated by the docs plugin.
7339
- * Do not edit manually.
7340
- */
7341
-
7342
- export interface NavItem {
7343
- title: string;
7344
- path: string;
7345
- children?: NavItem[];
7346
- }
7347
-
7348
- export const ${exportName}: NavItem[] = ${JSON.stringify(navItems, null, 2)} as const;
7349
- `;
7082
+ return importNapiModuleSync().generateDocsNavCode(navItems, exportName);
7350
7083
  }
7351
7084
  //#endregion
7352
7085
  //#region src/docs.ts
@@ -7673,86 +7406,6 @@ function buildDocsData(docs) {
7673
7406
  }))
7674
7407
  };
7675
7408
  }
7676
- function consumeJSDocType(value) {
7677
- const trimmed = value.trimStart();
7678
- if (!trimmed.startsWith("{")) return { rest: trimmed };
7679
- let depth = 0;
7680
- for (let index = 0; index < trimmed.length; index++) {
7681
- const char = trimmed[index];
7682
- if (char === "{") depth++;
7683
- else if (char === "}") {
7684
- depth--;
7685
- if (depth === 0) return {
7686
- type: trimmed.slice(1, index).trim() || void 0,
7687
- rest: trimmed.slice(index + 1).trimStart()
7688
- };
7689
- }
7690
- }
7691
- return { rest: trimmed };
7692
- }
7693
- function cleanTagDescription(value) {
7694
- return value.trim().replace(/^-\s*/, "").trim();
7695
- }
7696
- function splitTagNameAndDescription(value) {
7697
- const trimmed = value.trimStart();
7698
- if (trimmed.startsWith("[")) {
7699
- const closeIndex = trimmed.indexOf("]");
7700
- if (closeIndex >= 0) return {
7701
- name: trimmed.slice(0, closeIndex + 1),
7702
- description: trimmed.slice(closeIndex + 1).trimStart()
7703
- };
7704
- }
7705
- const match = /^(\S+)(?:\s+([\s\S]*))?$/u.exec(trimmed);
7706
- return {
7707
- name: match?.[1] ?? "",
7708
- description: match?.[2] ?? ""
7709
- };
7710
- }
7711
- function parseParamTagValue(value) {
7712
- const { type, rest } = consumeJSDocType(value);
7713
- const { name: rawName, description } = splitTagNameAndDescription(rest);
7714
- let name = rawName.trim();
7715
- if (!name) return null;
7716
- let optional = false;
7717
- let defaultValue;
7718
- const optionalMatch = /^\[(.*)\]$/u.exec(name);
7719
- if (optionalMatch) {
7720
- optional = true;
7721
- const [innerName, innerDefault] = optionalMatch[1].split(/=(.*)/su);
7722
- name = innerName.trim();
7723
- defaultValue = innerDefault?.trim() || void 0;
7724
- }
7725
- if (!name) return null;
7726
- return {
7727
- name,
7728
- type: type || "unknown",
7729
- description: cleanTagDescription(description),
7730
- optional: optional || void 0,
7731
- default: defaultValue
7732
- };
7733
- }
7734
- function parseReturnsTagValue(value) {
7735
- const { type, rest } = consumeJSDocType(value);
7736
- return {
7737
- type: type || "unknown",
7738
- description: cleanTagDescription(rest)
7739
- };
7740
- }
7741
- function normalizeReturnType(value) {
7742
- const parsed = parseReturnsTagValue(value);
7743
- return parsed.type === "unknown" ? value : parsed.type;
7744
- }
7745
- function mergeParam(params, next) {
7746
- const existing = params.find((param) => param.name === next.name);
7747
- if (!existing) {
7748
- params.push(next);
7749
- return;
7750
- }
7751
- if (next.type && (existing.type === "unknown" || next.type !== "unknown")) existing.type = next.type;
7752
- if (next.description) existing.description = next.description;
7753
- if (next.optional) existing.optional = true;
7754
- if (next.default) existing.default = next.default;
7755
- }
7756
7409
  /**
7757
7410
  * Extracts JSDoc documentation from source files in specified directories.
7758
7411
  *
@@ -7819,13 +7472,13 @@ function mergeParam(params, next) {
7819
7472
  * ```
7820
7473
  */
7821
7474
  async function extractDocs(srcDirs, options) {
7822
- const extractFileDocs = (await importNapiModule()).extractFileDocs;
7823
- if (!extractFileDocs) throw new Error("[ox-content] extractFileDocs is not available from @ox-content/napi.");
7475
+ const extractFileDocEntries = (await importNapiModule()).extractFileDocEntries;
7476
+ if (!extractFileDocEntries) throw new Error("[ox-content] extractFileDocEntries is not available from @ox-content/napi.");
7824
7477
  const results = [];
7825
7478
  for (const srcDir of srcDirs) {
7826
7479
  const files = await findFiles(srcDir, options);
7827
7480
  for (const file of files) {
7828
- const entries = extractFileDocs(file, options.private).map(parseNapiDocItem).filter((entry) => Boolean(entry));
7481
+ const entries = extractFileDocEntries(file, options.private);
7829
7482
  if (entries.length > 0) results.push({
7830
7483
  file,
7831
7484
  entries
@@ -7849,7 +7502,7 @@ async function findFiles(dir, options) {
7849
7502
  return;
7850
7503
  }
7851
7504
  for (const entry of entries) {
7852
- const fullPath = path$2.join(currentDir, entry.name);
7505
+ const fullPath = path$1.join(currentDir, entry.name);
7853
7506
  if (entry.isDirectory()) {
7854
7507
  if (!isExcluded(fullPath, options.exclude)) await walk(fullPath);
7855
7508
  } else if (entry.isFile()) {
@@ -7876,132 +7529,6 @@ function isExcluded(file, patterns) {
7876
7529
  return false;
7877
7530
  });
7878
7531
  }
7879
- function parseNapiDocItem(item) {
7880
- const kind = normalizeNapiKind(item.kind);
7881
- if (!kind) return null;
7882
- const params = [];
7883
- const examples = [];
7884
- const tags = {};
7885
- let description = "";
7886
- let returns;
7887
- let isPrivate = false;
7888
- const rawLines = (item.jsdoc ?? "").split("\n").map((line) => {
7889
- const trimmedStart = line.trimStart();
7890
- const withoutStar = trimmedStart.startsWith("*") ? trimmedStart.slice(1) : trimmedStart;
7891
- return withoutStar.startsWith(" ") ? withoutStar.slice(1) : withoutStar;
7892
- });
7893
- const cleanedLines = rawLines.map((line) => line.trim()).filter(Boolean);
7894
- let currentExample = "";
7895
- let inExample = false;
7896
- let rawLineIndex = 0;
7897
- for (const lineText of cleanedLines) {
7898
- while (rawLineIndex < rawLines.length && rawLines[rawLineIndex].trim() !== lineText) rawLineIndex++;
7899
- const rawLine = rawLineIndex < rawLines.length ? rawLines[rawLineIndex] : lineText;
7900
- rawLineIndex++;
7901
- if (lineText.startsWith("@")) {
7902
- if (inExample) {
7903
- examples.push(currentExample.trim());
7904
- currentExample = "";
7905
- inExample = false;
7906
- }
7907
- const tagMatch = /^@(\S+)\s*([\s\S]*)$/u.exec(lineText);
7908
- if (tagMatch) {
7909
- const [, tagName, tagValue = ""] = tagMatch;
7910
- switch (tagName) {
7911
- case "param":
7912
- case "arg":
7913
- case "argument": {
7914
- const param = parseParamTagValue(tagValue);
7915
- if (param) mergeParam(params, param);
7916
- break;
7917
- }
7918
- case "returns":
7919
- case "return":
7920
- returns = parseReturnsTagValue(tagValue);
7921
- break;
7922
- case "example":
7923
- inExample = true;
7924
- currentExample = tagValue.trim() ? `${tagValue.trim()}\n` : "";
7925
- break;
7926
- case "private":
7927
- isPrivate = true;
7928
- break;
7929
- default: tags[tagName] = tagValue.trim();
7930
- }
7931
- }
7932
- } else if (inExample) currentExample += rawLine + "\n";
7933
- else if (!description) description = lineText;
7934
- else description += "\n" + lineText;
7935
- }
7936
- if (inExample && currentExample) examples.push(currentExample.trim());
7937
- for (const param of item.params) {
7938
- if (params.length > 0 && param.name === "param" && !param.typeAnnotation && !param.description && !param.defaultValue) continue;
7939
- mergeParam(params, {
7940
- name: param.name,
7941
- type: param.typeAnnotation ?? "unknown",
7942
- description: param.description ?? "",
7943
- optional: param.optional || void 0,
7944
- default: param.defaultValue
7945
- });
7946
- }
7947
- if (!returns && item.returnType) returns = {
7948
- type: normalizeReturnType(item.returnType),
7949
- description: ""
7950
- };
7951
- else if (returns && item.returnType) returns.type = normalizeReturnType(item.returnType);
7952
- if (!description) description = item.doc ?? "";
7953
- for (const tag of item.tags) {
7954
- if (tag.tag === "param" || tag.tag === "arg" || tag.tag === "argument" || tag.tag === "returns" || tag.tag === "return") {
7955
- if (tag.tag === "param" || tag.tag === "arg" || tag.tag === "argument") {
7956
- const param = parseParamTagValue(tag.value);
7957
- if (param) mergeParam(params, param);
7958
- } else {
7959
- const parsedReturns = parseReturnsTagValue(tag.value);
7960
- if (!returns) returns = parsedReturns;
7961
- else {
7962
- returns.type = returns.type === "unknown" ? parsedReturns.type : returns.type;
7963
- returns.description ||= parsedReturns.description;
7964
- }
7965
- }
7966
- continue;
7967
- }
7968
- if (tag.tag === "example") {
7969
- if (tag.value && !examples.includes(tag.value)) examples.push(tag.value);
7970
- continue;
7971
- }
7972
- if (tag.tag === "private") {
7973
- isPrivate = true;
7974
- continue;
7975
- }
7976
- if (!tags[tag.tag]) tags[tag.tag] = tag.value;
7977
- }
7978
- return {
7979
- name: item.name,
7980
- kind,
7981
- description,
7982
- params: params.length > 0 ? params : void 0,
7983
- returns,
7984
- examples: examples.length > 0 ? examples : void 0,
7985
- tags: Object.keys(tags).length > 0 ? tags : void 0,
7986
- private: isPrivate,
7987
- file: item.sourcePath,
7988
- line: item.line,
7989
- endLine: item.endLine,
7990
- signature: item.signature
7991
- };
7992
- }
7993
- function normalizeNapiKind(kind) {
7994
- switch (kind) {
7995
- case "function":
7996
- case "class":
7997
- case "interface":
7998
- case "type":
7999
- case "variable":
8000
- case "module": return kind;
8001
- case "enum": return "type";
8002
- default: return null;
8003
- }
8004
- }
8005
7532
  /**
8006
7533
  * Generates Markdown documentation from extracted docs.
8007
7534
  */
@@ -8012,7 +7539,7 @@ function generateMarkdown(docs, options) {
8012
7539
  if (options.groupBy === "file") {
8013
7540
  const docToFile = /* @__PURE__ */ new Map();
8014
7541
  for (const doc of sortedDocs) {
8015
- let fileName = path$2.basename(doc.file, path$2.extname(doc.file));
7542
+ let fileName = path$1.basename(doc.file, path$1.extname(doc.file));
8016
7543
  if (fileName === "index") fileName = "index-module";
8017
7544
  docToFile.set(doc, fileName);
8018
7545
  const markdown = generateFileMarkdown(doc, options, fileName, symbolMap);
@@ -8045,10 +7572,10 @@ function sortExtractedDocs(docs) {
8045
7572
  return [...docs].map((doc) => ({
8046
7573
  ...doc,
8047
7574
  entries: [...doc.entries].sort(compareEntriesByName)
8048
- })).sort((a, b) => compareStrings(path$2.basename(a.file), path$2.basename(b.file)));
7575
+ })).sort((a, b) => compareStrings(path$1.basename(a.file), path$1.basename(b.file)));
8049
7576
  }
8050
7577
  function generateFileMarkdown(doc, options, currentFileName, symbolMap) {
8051
- let md = `# ${path$2.basename(doc.file)}\n\n`;
7578
+ let md = `# ${path$1.basename(doc.file)}\n\n`;
8052
7579
  if (options.githubUrl) {
8053
7580
  const sourceLink = generateSourceLink(doc.file, options.githubUrl);
8054
7581
  if (sourceLink) md += sourceLink + "\n\n";
@@ -8200,7 +7727,7 @@ function generateIndex(docs, docToFile) {
8200
7727
  md += "## Modules\n\n";
8201
7728
  if (docs.length > 1) md += renderDetailsControlsHtml(".ox-api-module") + "\n\n";
8202
7729
  for (const doc of docs) {
8203
- const displayName = path$2.basename(doc.file, path$2.extname(doc.file));
7730
+ const displayName = path$1.basename(doc.file, path$1.extname(doc.file));
8204
7731
  let fileName = displayName;
8205
7732
  if (docToFile && docToFile.has(doc)) fileName = docToFile.get(doc);
8206
7733
  else if (fileName === "index") fileName = "index-module";
@@ -8283,7 +7810,7 @@ function convertSymbolLinks(text, currentFileName, symbolMap) {
8283
7810
  function buildSymbolMap(docs) {
8284
7811
  const map = /* @__PURE__ */ new Map();
8285
7812
  for (const doc of docs) {
8286
- let fileName = path$2.basename(doc.file, path$2.extname(doc.file));
7813
+ let fileName = path$1.basename(doc.file, path$1.extname(doc.file));
8287
7814
  if (fileName === "index") fileName = "index-module";
8288
7815
  for (const entry of doc.entries) map.set(entry.name, {
8289
7816
  name: entry.name,
@@ -8301,7 +7828,7 @@ async function writeDocs(docs, outDir, extractedDocs, options) {
8301
7828
  const generatedFiles = new Set(Object.keys(docs));
8302
7829
  if (extractedDocs && options?.generateNav && options.groupBy === "file") generatedFiles.add("nav.ts");
8303
7830
  if (extractedDocs) generatedFiles.add(DOCS_DATA_FILE);
8304
- const manifestPath = path$2.join(outDir, DOCS_MANIFEST_FILE);
7831
+ const manifestPath = path$1.join(outDir, DOCS_MANIFEST_FILE);
8305
7832
  let previousFiles = [];
8306
7833
  try {
8307
7834
  previousFiles = JSON.parse(await fs$2.promises.readFile(manifestPath, "utf-8"));
@@ -8310,18 +7837,18 @@ async function writeDocs(docs, outDir, extractedDocs, options) {
8310
7837
  }
8311
7838
  for (const staleFile of previousFiles) {
8312
7839
  if (generatedFiles.has(staleFile)) continue;
8313
- await fs$2.promises.rm(path$2.join(outDir, staleFile), { force: true });
7840
+ await fs$2.promises.rm(path$1.join(outDir, staleFile), { force: true });
8314
7841
  }
8315
7842
  for (const [fileName, content] of Object.entries(docs)) {
8316
- const filePath = path$2.join(outDir, fileName);
7843
+ const filePath = path$1.join(outDir, fileName);
8317
7844
  await fs$2.promises.writeFile(filePath, content, "utf-8");
8318
7845
  }
8319
7846
  if (extractedDocs && options?.generateNav && options.groupBy === "file") {
8320
7847
  const navCode = generateNavCode(generateNavMetadata(extractedDocs, "/api"), "apiNav");
8321
- const navFilePath = path$2.join(outDir, "nav.ts");
7848
+ const navFilePath = path$1.join(outDir, "nav.ts");
8322
7849
  await fs$2.promises.writeFile(navFilePath, navCode, "utf-8");
8323
7850
  }
8324
- if (extractedDocs) await fs$2.promises.writeFile(path$2.join(outDir, DOCS_DATA_FILE), JSON.stringify(buildDocsData(extractedDocs), null, 2), "utf-8");
7851
+ if (extractedDocs) await fs$2.promises.writeFile(path$1.join(outDir, DOCS_DATA_FILE), JSON.stringify(buildDocsData(extractedDocs), null, 2), "utf-8");
8325
7852
  await fs$2.promises.writeFile(manifestPath, JSON.stringify([...generatedFiles].sort(), null, 2), "utf-8");
8326
7853
  }
8327
7854
  /**
@@ -8407,10 +7934,10 @@ async function renderHtmlToPng(page, html, width, height, publicDir) {
8407
7934
  await route.continue();
8408
7935
  return;
8409
7936
  }
8410
- const filePath = path$2.join(publicDir, url.pathname);
7937
+ const filePath = path$1.join(publicDir, url.pathname);
8411
7938
  try {
8412
7939
  const body = await fs.readFile(filePath);
8413
- const ext = path$2.extname(filePath).toLowerCase();
7940
+ const ext = path$1.extname(filePath).toLowerCase();
8414
7941
  await route.fulfill({
8415
7942
  body,
8416
7943
  contentType: {
@@ -8595,7 +8122,7 @@ function computeCacheKey(templateSource, props, width, height) {
8595
8122
  * Returns the cached file path if found, null otherwise.
8596
8123
  */
8597
8124
  async function getCached(cacheDir, key) {
8598
- const filePath = path$2.join(cacheDir, `${key}.png`);
8125
+ const filePath = path$1.join(cacheDir, `${key}.png`);
8599
8126
  try {
8600
8127
  return await fs$1.readFile(filePath);
8601
8128
  } catch {
@@ -8607,7 +8134,7 @@ async function getCached(cacheDir, key) {
8607
8134
  */
8608
8135
  async function writeCache(cacheDir, key, png) {
8609
8136
  await fs$1.mkdir(cacheDir, { recursive: true });
8610
- const filePath = path$2.join(cacheDir, `${key}.png`);
8137
+ const filePath = path$1.join(cacheDir, `${key}.png`);
8611
8138
  await fs$1.writeFile(filePath, png);
8612
8139
  }
8613
8140
  //#endregion
@@ -8698,14 +8225,14 @@ function resolveOgImageOptions(options) {
8698
8225
  */
8699
8226
  async function resolveTemplate(options, root) {
8700
8227
  if (!options.template) return getDefaultTemplate();
8701
- const templatePath = path$2.resolve(root, options.template);
8228
+ const templatePath = path$1.resolve(root, options.template);
8702
8229
  const fs = await import("fs/promises");
8703
8230
  try {
8704
8231
  await fs.access(templatePath);
8705
8232
  } catch {
8706
8233
  throw new Error(`[ox-content:og-image] Template file not found: ${templatePath}`);
8707
8234
  }
8708
- switch (path$2.extname(templatePath).toLowerCase()) {
8235
+ switch (path$1.extname(templatePath).toLowerCase()) {
8709
8236
  case ".vue": return resolveVueTemplate(templatePath, options, root);
8710
8237
  case ".svelte": return resolveSvelteTemplate(templatePath, root);
8711
8238
  case ".tsx":
@@ -8719,9 +8246,9 @@ async function resolveTemplate(options, root) {
8719
8246
  async function resolveTsTemplate(templatePath, options, root) {
8720
8247
  const fs = await import("fs/promises");
8721
8248
  const { rolldown } = await import("rolldown");
8722
- const cacheDir = path$2.join(root, ".cache", "og-images");
8249
+ const cacheDir = path$1.join(root, ".cache", "og-images");
8723
8250
  await fs.mkdir(cacheDir, { recursive: true });
8724
- const outfile = path$2.join(cacheDir, "_template.mjs");
8251
+ const outfile = path$1.join(cacheDir, "_template.mjs");
8725
8252
  const bundle = await rolldown({
8726
8253
  input: templatePath,
8727
8254
  platform: "node"
@@ -8744,9 +8271,9 @@ async function resolveTsTemplate(templatePath, options, root) {
8744
8271
  async function resolveVueTemplate(templatePath, options, root) {
8745
8272
  const fs = await import("fs/promises");
8746
8273
  const { rolldown } = await import("rolldown");
8747
- const cacheDir = path$2.join(root, ".cache", "og-images");
8274
+ const cacheDir = path$1.join(root, ".cache", "og-images");
8748
8275
  await fs.mkdir(cacheDir, { recursive: true });
8749
- const outfile = path$2.join(cacheDir, "_template_vue.mjs");
8276
+ const outfile = path$1.join(cacheDir, "_template_vue.mjs");
8750
8277
  const bundle = await rolldown({
8751
8278
  input: templatePath,
8752
8279
  platform: "node",
@@ -8842,9 +8369,9 @@ async function getVizejsPlugin() {
8842
8369
  async function resolveSvelteTemplate(templatePath, root) {
8843
8370
  const fs = await import("fs/promises");
8844
8371
  const { rolldown } = await import("rolldown");
8845
- const cacheDir = path$2.join(root, ".cache", "og-images");
8372
+ const cacheDir = path$1.join(root, ".cache", "og-images");
8846
8373
  await fs.mkdir(cacheDir, { recursive: true });
8847
- const outfile = path$2.join(cacheDir, "_template_svelte.mjs");
8374
+ const outfile = path$1.join(cacheDir, "_template_svelte.mjs");
8848
8375
  const bundle = await rolldown({
8849
8376
  input: templatePath,
8850
8377
  platform: "node",
@@ -8900,9 +8427,9 @@ function createSvelteCompilerPlugin() {
8900
8427
  async function resolveReactTemplate(templatePath, root) {
8901
8428
  const fs = await import("fs/promises");
8902
8429
  const { rolldown } = await import("rolldown");
8903
- const cacheDir = path$2.join(root, ".cache", "og-images");
8430
+ const cacheDir = path$1.join(root, ".cache", "og-images");
8904
8431
  await fs.mkdir(cacheDir, { recursive: true });
8905
- const outfile = path$2.join(cacheDir, "_template_react.mjs");
8432
+ const outfile = path$1.join(cacheDir, "_template_react.mjs");
8906
8433
  const bundle = await rolldown({
8907
8434
  input: templatePath,
8908
8435
  platform: "node",
@@ -8952,7 +8479,7 @@ async function resolveReactTemplate(templatePath, root) {
8952
8479
  async function computeTemplateSource(options, root) {
8953
8480
  if (!options.template) return "__default__";
8954
8481
  const fs = await import("fs/promises");
8955
- const templatePath = path$2.resolve(root, options.template);
8482
+ const templatePath = path$1.resolve(root, options.template);
8956
8483
  const content = await fs.readFile(templatePath, "utf-8");
8957
8484
  return crypto.createHash("sha256").update(content).digest("hex");
8958
8485
  }
@@ -8970,7 +8497,7 @@ async function generateOgImages(pages, options, root) {
8970
8497
  if (pages.length === 0) return [];
8971
8498
  const templateFn = await resolveTemplate(options, root);
8972
8499
  const templateSource = await computeTemplateSource(options, root);
8973
- const cacheDir = path$2.join(root, ".cache", "og-images");
8500
+ const cacheDir = path$1.join(root, ".cache", "og-images");
8974
8501
  if (options.cache) {
8975
8502
  const allCached = await tryServeAllFromCache(pages, templateSource, options, cacheDir);
8976
8503
  if (allCached) return allCached;
@@ -8982,7 +8509,7 @@ async function generateOgImages(pages, options, root) {
8982
8509
  error: "Chromium not available"
8983
8510
  }));
8984
8511
  const results = [];
8985
- const publicDir = path$2.join(root, "public");
8512
+ const publicDir = path$1.join(root, "public");
8986
8513
  const concurrency = Math.max(1, options.concurrency);
8987
8514
  for (let i = 0; i < pages.length; i += concurrency) {
8988
8515
  const batch = pages.slice(i, i + concurrency);
@@ -9006,7 +8533,7 @@ async function tryServeAllFromCache(pages, templateSource, options, cacheDir) {
9006
8533
  for (const entry of pages) {
9007
8534
  const cached = await getCached(cacheDir, computeCacheKey(templateSource, entry.props, options.width, options.height));
9008
8535
  if (!cached) return null;
9009
- await fs.mkdir(path$2.dirname(entry.outputPath), { recursive: true });
8536
+ await fs.mkdir(path$1.dirname(entry.outputPath), { recursive: true });
9010
8537
  await fs.writeFile(entry.outputPath, cached);
9011
8538
  results.push({
9012
8539
  outputPath: entry.outputPath,
@@ -9024,7 +8551,7 @@ async function renderSinglePage(entry, templateFn, templateSource, options, cach
9024
8551
  if (options.cache) {
9025
8552
  const cached = await getCached(cacheDir, computeCacheKey(templateSource, entry.props, options.width, options.height));
9026
8553
  if (cached) {
9027
- await fs.mkdir(path$2.dirname(entry.outputPath), { recursive: true });
8554
+ await fs.mkdir(path$1.dirname(entry.outputPath), { recursive: true });
9028
8555
  await fs.writeFile(entry.outputPath, cached);
9029
8556
  return {
9030
8557
  outputPath: entry.outputPath,
@@ -9034,7 +8561,7 @@ async function renderSinglePage(entry, templateFn, templateSource, options, cach
9034
8561
  }
9035
8562
  const html = await templateFn(entry.props);
9036
8563
  const png = await session.renderPage(html, options.width, options.height, publicDir);
9037
- await fs.mkdir(path$2.dirname(entry.outputPath), { recursive: true });
8564
+ await fs.mkdir(path$1.dirname(entry.outputPath), { recursive: true });
9038
8565
  await fs.writeFile(entry.outputPath, png);
9039
8566
  if (options.cache) await writeCache(cacheDir, computeCacheKey(templateSource, entry.props, options.width, options.height), png);
9040
8567
  return {
@@ -9058,23 +8585,23 @@ async function transformAllPlugins(html, options = {}) {
9058
8585
  const { tabs = true, youtube = true, github = true, ogp = true, mermaid = true, githubToken } = options;
9059
8586
  let result = html;
9060
8587
  if (tabs) {
9061
- const { transformTabs } = await import("./tabs.mjs");
8588
+ const { transformTabs } = await import("./tabs.mjs").then((n) => n.r);
9062
8589
  result = await transformTabs(result);
9063
8590
  }
9064
8591
  if (youtube) {
9065
- const { transformYouTube } = await import("./youtube.mjs");
8592
+ const { transformYouTube } = await import("./youtube.mjs").then((n) => n.r);
9066
8593
  result = await transformYouTube(result);
9067
8594
  }
9068
8595
  if (github) {
9069
- const { transformGitHub } = await import("./github.mjs");
8596
+ const { transformGitHub } = await import("./github.mjs").then((n) => n.r);
9070
8597
  result = await transformGitHub(result, void 0, { token: githubToken });
9071
8598
  }
9072
8599
  if (ogp) {
9073
- const { transformOgp } = await import("./ogp.mjs");
8600
+ const { transformOgp } = await import("./ogp.mjs").then((n) => n.r);
9074
8601
  result = await transformOgp(result);
9075
8602
  }
9076
8603
  if (mermaid) {
9077
- const { transformMermaidStatic } = await import("./mermaid2.mjs");
8604
+ const { transformMermaidStatic } = await import("./mermaid.mjs").then((n) => n.n);
9078
8605
  result = await transformMermaidStatic(result);
9079
8606
  }
9080
8607
  return result;
@@ -10882,10 +10409,7 @@ function renderTemplate(template, data) {
10882
10409
  * Extracts title from content or frontmatter.
10883
10410
  */
10884
10411
  function extractTitle$1(content, frontmatter) {
10885
- if (frontmatter.title && typeof frontmatter.title === "string") return frontmatter.title;
10886
- const h1Match = content.match(/<h1[^>]*>([^<]+)<\/h1>/i);
10887
- if (h1Match) return h1Match[1].trim();
10888
- return "Untitled";
10412
+ return importNapiModuleSync().extractSsgTitle(content, typeof frontmatter.title === "string" ? frontmatter.title : void 0);
10889
10413
  }
10890
10414
  /**
10891
10415
  * Generates bare HTML page (no navigation, no styles).
@@ -10971,229 +10495,41 @@ async function generateHtmlPage(pageData, navGroups, siteName, base, ogImage, th
10971
10495
  }))
10972
10496
  });
10973
10497
  }
10974
- const SSG_STYLE_BLOCK_RE = /[ \t]*<!-- ox-content:styles:start -->\s*<style>([\s\S]*?)<\/style>\s*<!-- ox-content:styles:end -->/;
10975
- const SSG_SCRIPT_BLOCK_RE = /[ \t]*<!-- ox-content:scripts:start -->\s*<script>([\s\S]*?)<\/script>\s*<!-- ox-content:scripts:end -->/;
10976
- const FIRST_INLINE_STYLE_RE = /[ \t]*<style>([\s\S]*?)<\/style>/;
10977
- const LAST_INLINE_BODY_SCRIPT_RE = /[ \t]*<script>([\s\S]*?)<\/script>\s*<\/body>/;
10978
- const CSS_SECTION_RE = /\/\* ox-content:css:([a-z0-9-]+):start \*\/\s*([\s\S]*?)\s*\/\* ox-content:css:\1:end \*\//g;
10979
- const SEARCH_CHUNK_RE = /\/\/ ox-content:search:start\s*([\s\S]*?)\s*\/\/ ox-content:search:end/;
10980
- const SEARCH_CHUNK_PLACEHOLDER = "__OX_CONTENT_SEARCH_CHUNK__";
10981
- const CORE_CSS_SECTION_NAMES = new Set(["base", "footer"]);
10982
- const THEME_INLINE_CSS_MAX_BYTES = 2048;
10983
- function createContentHash(content) {
10984
- return createHash("sha256").update(content).digest("hex").slice(0, 10);
10985
- }
10986
- function sanitizeChunkLabel(label) {
10987
- return label.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "asset";
10988
- }
10989
- function toPublicAssetPath(base, fileName) {
10990
- return `${base.endsWith("/") ? base : `${base}/`}assets/${fileName}`;
10991
- }
10992
- function hasRelativeCssUrls(css) {
10993
- let cursor = 0;
10994
- while (cursor < css.length) {
10995
- const urlIndex = css.indexOf("url(", cursor);
10996
- if (urlIndex === -1) return false;
10997
- let valueStart = urlIndex + 4;
10998
- while (valueStart < css.length && /\s/.test(css[valueStart])) valueStart++;
10999
- const quote = css[valueStart] === "\"" || css[valueStart] === "'" ? css[valueStart] : "";
11000
- if (quote) valueStart++;
11001
- let valueEnd = valueStart;
11002
- while (valueEnd < css.length) {
11003
- const char = css[valueEnd];
11004
- if (quote) {
11005
- if (char === "\\") {
11006
- valueEnd += 2;
11007
- continue;
11008
- }
11009
- if (char === quote) break;
11010
- } else if (char === ")") break;
11011
- valueEnd++;
11012
- }
11013
- const value = css.slice(valueStart, valueEnd).trim();
11014
- if (value && !value.startsWith("data:") && !value.startsWith("http:") && !value.startsWith("https:") && !value.startsWith("//") && !value.startsWith("/") && !value.startsWith("#") && !value.startsWith("blob:") && !value.startsWith("var(")) return true;
11015
- cursor = valueEnd + 1;
11016
- }
11017
- return false;
11018
- }
11019
- function createSharedAssetChunk(type, label, content, outDir, base) {
11020
- const hash = createContentHash(content);
11021
- const fileName = `ox-content-${sanitizeChunkLabel(label)}-${hash}.${type}`;
11022
- return {
11023
- outputPath: path$2.join(outDir, "assets", fileName),
11024
- publicPath: toPublicAssetPath(base, fileName),
11025
- content
11026
- };
11027
- }
11028
- function extractCssSections(cssContent) {
11029
- return Array.from(cssContent.matchAll(CSS_SECTION_RE)).map(([, name, content]) => ({
11030
- name,
11031
- content: content.trim()
11032
- })).filter((section) => section.content.length > 0);
11033
- }
11034
- function getOrCreateSharedChunk(chunks, type, label, content, outDir, base) {
11035
- let chunk = chunks.get(content);
11036
- if (!chunk) {
11037
- chunk = createSharedAssetChunk(type, label, content, outDir, base);
11038
- chunks.set(content, chunk);
11039
- }
11040
- return chunk;
11041
- }
11042
- function buildStyleReplacement(cssContent, cssChunks, outDir, base) {
11043
- const sections = extractCssSections(cssContent);
11044
- const effectiveSections = sections.length > 0 ? sections : [{
11045
- name: "css",
11046
- content: cssContent.trim()
11047
- }];
11048
- const coreContent = effectiveSections.filter((section) => CORE_CSS_SECTION_NAMES.has(section.name)).map((section) => section.content).join("\n").trim();
11049
- const fragments = [];
11050
- if (coreContent) {
11051
- const coreChunk = getOrCreateSharedChunk(cssChunks, "css", "core", coreContent, outDir, base);
11052
- fragments.push(` <link rel="stylesheet" href="${coreChunk.publicPath}">`);
11053
- }
11054
- for (const section of effectiveSections) {
11055
- if (CORE_CSS_SECTION_NAMES.has(section.name)) continue;
11056
- if (section.name === "theme" && (hasRelativeCssUrls(section.content) || section.content.length <= THEME_INLINE_CSS_MAX_BYTES) || hasRelativeCssUrls(section.content)) {
11057
- fragments.push(` <style>${section.content}</style>`);
11058
- continue;
11059
- }
11060
- const chunk = getOrCreateSharedChunk(cssChunks, "css", section.name, section.content, outDir, base);
11061
- fragments.push(` <link rel="stylesheet" href="${chunk.publicPath}">`);
11062
- }
11063
- return fragments.join("\n");
11064
- }
11065
- function buildScriptReplacement(jsContent, jsChunks, outDir, base) {
11066
- const searchMatch = jsContent.match(SEARCH_CHUNK_RE);
11067
- if (searchMatch && jsContent.includes(SEARCH_CHUNK_PLACEHOLDER)) {
11068
- const searchContent = searchMatch[1].trim();
11069
- if (searchContent) {
11070
- const searchChunk = getOrCreateSharedChunk(jsChunks, "js", "search", searchContent, outDir, base);
11071
- const coreContent = jsContent.replace(SEARCH_CHUNK_RE, "").replaceAll(SEARCH_CHUNK_PLACEHOLDER, searchChunk.publicPath).trim();
11072
- if (coreContent) return ` <script defer src="${getOrCreateSharedChunk(jsChunks, "js", "core", coreContent, outDir, base).publicPath}"><\/script>`;
11073
- }
11074
- }
11075
- const fallbackContent = jsContent.trim();
11076
- if (!fallbackContent) return "";
11077
- return ` <script defer src="${getOrCreateSharedChunk(jsChunks, "js", "js", fallbackContent, outDir, base).publicPath}"><\/script>`;
11078
- }
11079
10498
  async function externalizeSharedPageAssets(pages, outDir, base) {
11080
- const cssChunks = /* @__PURE__ */ new Map();
11081
- const jsChunks = /* @__PURE__ */ new Map();
11082
- const optimizedPages = pages.map((page) => {
11083
- let html = page.html;
11084
- const styleMatch = html.match(SSG_STYLE_BLOCK_RE);
11085
- if (styleMatch) {
11086
- const replacement = buildStyleReplacement(styleMatch[1], cssChunks, outDir, base);
11087
- html = html.replace(SSG_STYLE_BLOCK_RE, replacement);
11088
- } else {
11089
- const inlineStyleMatch = html.match(FIRST_INLINE_STYLE_RE);
11090
- if (inlineStyleMatch) {
11091
- const replacement = buildStyleReplacement(inlineStyleMatch[1], cssChunks, outDir, base);
11092
- html = html.replace(FIRST_INLINE_STYLE_RE, replacement);
11093
- }
11094
- }
11095
- const scriptMatch = html.match(SSG_SCRIPT_BLOCK_RE);
11096
- if (scriptMatch) {
11097
- const replacement = buildScriptReplacement(scriptMatch[1], jsChunks, outDir, base);
11098
- html = html.replace(SSG_SCRIPT_BLOCK_RE, replacement);
11099
- } else {
11100
- const inlineScriptMatch = html.match(LAST_INLINE_BODY_SCRIPT_RE);
11101
- if (inlineScriptMatch) {
11102
- const replacement = buildScriptReplacement(inlineScriptMatch[1], jsChunks, outDir, base);
11103
- html = html.replace(LAST_INLINE_BODY_SCRIPT_RE, replacement ? `${replacement}\n</body>` : "</body>");
11104
- }
11105
- }
11106
- return {
11107
- ...page,
11108
- html
11109
- };
11110
- });
11111
- const chunks = [...cssChunks.values(), ...jsChunks.values()];
11112
- await Promise.all(chunks.map(async (chunk) => {
11113
- await fs$1.mkdir(path$2.dirname(chunk.outputPath), { recursive: true });
11114
- await fs$1.writeFile(chunk.outputPath, chunk.content, "utf-8");
10499
+ const optimized = (await importNapiModule()).externalizeSsgAssets(pages, outDir, base);
10500
+ await Promise.all(optimized.assets.map(async (asset) => {
10501
+ await fs$1.mkdir(path$1.dirname(asset.outputPath), { recursive: true });
10502
+ await fs$1.writeFile(asset.outputPath, asset.content, "utf-8");
11115
10503
  }));
11116
10504
  return {
11117
- pages: optimizedPages,
11118
- assets: chunks.map((chunk) => chunk.outputPath)
10505
+ pages: optimized.pages,
10506
+ assets: optimized.assets.map((asset) => asset.outputPath)
11119
10507
  };
11120
10508
  }
11121
10509
  /**
11122
- * Converts a markdown file path to its corresponding HTML output path.
11123
- */
11124
- function getOutputPath(inputPath, srcDir, outDir, extension) {
11125
- const baseName = path$2.relative(srcDir, inputPath).replace(/\.(?:md|markdown)$/i, extension);
11126
- if (baseName.endsWith(`index${extension}`)) return path$2.join(outDir, baseName);
11127
- const dirName = baseName.replace(new RegExp(`\\${extension}$`), "");
11128
- return path$2.join(outDir, dirName, `index${extension}`);
11129
- }
11130
- /**
11131
10510
  * Converts a markdown file path to a relative URL path.
11132
10511
  */
11133
10512
  function getUrlPath$1(inputPath, srcDir) {
11134
- const baseName = path$2.relative(srcDir, inputPath).replace(/\.(?:md|markdown)$/i, "");
11135
- if (baseName === "index" || baseName.endsWith("/index")) return baseName.replace(/\/?index$/, "") || "/";
11136
- return baseName;
11137
- }
11138
- /**
11139
- * Converts a markdown file path to an href.
11140
- */
11141
- function getHref(inputPath, srcDir, base, extension) {
11142
- const urlPath = getUrlPath$1(inputPath, srcDir);
11143
- if (urlPath === "/" || urlPath === "") return `${base}index${extension}`;
11144
- return `${base}${urlPath}/index${extension}`;
10513
+ return importNapiModuleSync().getSsgUrlPath(inputPath, srcDir);
11145
10514
  }
11146
10515
  function getPageLocale(urlPath, i18n) {
11147
10516
  if (!i18n) return void 0;
11148
- const firstSegment = urlPath.split("/").filter(Boolean)[0];
11149
- return i18n.locales.some((l) => l.code === firstSegment) ? firstSegment : i18n.defaultLocale;
11150
- }
11151
- /**
11152
- * Gets the OG image output path for a given markdown file.
11153
- */
11154
- function getOgImagePath(inputPath, srcDir, outDir) {
11155
- const baseName = path$2.relative(srcDir, inputPath).replace(/\.(?:md|markdown)$/i, "");
11156
- if (baseName === "index" || baseName.endsWith("/index")) {
11157
- const dirPath = baseName.replace(/\/?index$/, "") || "";
11158
- return path$2.join(outDir, dirPath, "og-image.png");
11159
- }
11160
- return path$2.join(outDir, baseName, "og-image.png");
11161
- }
11162
- /**
11163
- * Gets the OG image URL for use in meta tags.
11164
- * If siteUrl is provided, returns an absolute URL (required for SNS sharing).
11165
- */
11166
- function getOgImageUrl(inputPath, srcDir, base, siteUrl) {
11167
- const urlPath = getUrlPath$1(inputPath, srcDir);
11168
- let relativePath;
11169
- if (urlPath === "/" || urlPath === "") relativePath = `${base}og-image.png`;
11170
- else relativePath = `${base}${urlPath}/og-image.png`;
11171
- if (siteUrl) return `${siteUrl.replace(/\/$/, "")}${relativePath}`;
11172
- return relativePath;
10517
+ return importNapiModuleSync().getSsgPageLocale(urlPath, i18n.defaultLocale, i18n.locales.map((locale) => locale.code)) ?? void 0;
11173
10518
  }
11174
- /**
11175
- * Gets display title from file path.
11176
- */
11177
- function getDisplayTitle(filePath) {
11178
- const fileName = path$2.basename(filePath, path$2.extname(filePath));
11179
- if (fileName === "index") {
11180
- const dirName = path$2.basename(path$2.dirname(filePath));
11181
- if (dirName && dirName !== ".") return formatTitle(dirName);
11182
- return "Home";
11183
- }
11184
- return formatTitle(fileName);
10519
+ function getRoutePaths(inputPath, srcDir, outDir, base, extension, siteUrl) {
10520
+ return importNapiModuleSync().resolveSsgRoutePaths(inputPath, srcDir, outDir, base, extension, siteUrl);
11185
10521
  }
11186
10522
  /**
11187
10523
  * Formats a file/dir name as a title.
11188
10524
  */
11189
10525
  function formatTitle(name) {
11190
- return name.replace(/[-_]([a-z])/g, (_, char) => " " + char.toUpperCase()).replace(/^[a-z]/, (char) => char.toUpperCase());
10526
+ return importNapiModuleSync().formatSsgTitle(name);
11191
10527
  }
11192
10528
  /**
11193
10529
  * Collects all markdown files from the source directory.
11194
10530
  */
11195
10531
  async function collectMarkdownFiles$1(srcDir) {
11196
- return (await glob(path$2.join(srcDir, "**/*.{md,markdown}"), {
10532
+ return (await glob(path$1.join(srcDir, "**/*.{md,markdown}"), {
11197
10533
  nodir: true,
11198
10534
  ignore: [
11199
10535
  "**/node_modules/**",
@@ -11206,110 +10542,13 @@ async function collectMarkdownFiles$1(srcDir) {
11206
10542
  * Builds navigation items from markdown files, grouped by directory.
11207
10543
  */
11208
10544
  function buildNavItems(markdownFiles, srcDir, base, extension) {
11209
- const groups = /* @__PURE__ */ new Map();
11210
- const groupOrder = [
11211
- "",
11212
- "examples",
11213
- "packages",
11214
- "api"
11215
- ];
11216
- for (const file of markdownFiles) {
11217
- const parts = path$2.relative(srcDir, file).split(path$2.sep);
11218
- let groupKey = "";
11219
- if (parts.length > 1) groupKey = parts[0];
11220
- if (!groups.has(groupKey)) groups.set(groupKey, []);
11221
- const urlPath = getUrlPath$1(file, srcDir);
11222
- let title;
11223
- if (urlPath === "/" || urlPath === "") title = "Overview";
11224
- else title = getDisplayTitle(file);
11225
- groups.get(groupKey).push({
11226
- title,
11227
- path: urlPath,
11228
- href: getHref(file, srcDir, base, extension)
11229
- });
11230
- }
11231
- const sortItems = (items) => {
11232
- return items.sort((a, b) => {
11233
- const aIsRoot = a.path === "/" || a.path === "";
11234
- const bIsRoot = b.path === "/" || b.path === "";
11235
- if (aIsRoot && !bIsRoot) return -1;
11236
- if (!aIsRoot && bIsRoot) return 1;
11237
- return a.title.localeCompare(b.title);
11238
- });
11239
- };
11240
- const result = [];
11241
- for (const key of groupOrder) {
11242
- const items = groups.get(key);
11243
- if (items && items.length > 0) {
11244
- result.push({
11245
- title: key === "" ? "Guide" : formatTitle(key),
11246
- items: sortItems(items)
11247
- });
11248
- groups.delete(key);
11249
- }
11250
- }
11251
- for (const [key, items] of groups) if (items.length > 0) result.push({
11252
- title: formatTitle(key),
11253
- items: sortItems(items)
11254
- });
11255
- return result;
11256
- }
11257
- function isSafeSidebarLink(link) {
11258
- const trimmed = link.trim();
11259
- if (trimmed.startsWith("//")) return false;
11260
- return !/^[a-z][a-z0-9+.-]*:/i.test(trimmed) || /^(https?:|mailto:)/i.test(trimmed);
11261
- }
11262
- function sidebarPath(link) {
11263
- if (!link || !isSafeSidebarLink(link)) return "";
11264
- if (/^(https?:|mailto:|#)/i.test(link.trim())) return "";
11265
- const bare = link.trim().split("#", 1)[0].split("?", 1)[0].replace(/^\/+/, "").replace(/\/$/, "").replace(/\.(md|markdown)$/i, "");
11266
- if (!bare || bare === "index") return "/";
11267
- return bare.replace(/\/index$/, "");
11268
- }
11269
- function sidebarHref(link, base, extension) {
11270
- if (!link) return "#";
11271
- const trimmed = link.trim();
11272
- if (!isSafeSidebarLink(trimmed)) return "#";
11273
- if (/^(https?:|mailto:|#)/i.test(trimmed)) return trimmed;
11274
- const hash = trimmed.includes("#") ? `#${trimmed.split("#").slice(1).join("#")}` : "";
11275
- const withoutExt = trimmed.split("#", 1)[0].replace(/^\/+/, "").replace(/\/$/, "").replace(/\.(md|markdown)$/i, "");
11276
- return `${base}${!withoutExt || withoutExt === "index" ? "index" : `${withoutExt.replace(/\/index$/, "")}/index`}${extension}${hash}`;
10545
+ return importNapiModuleSync().buildSsgNavItems(markdownFiles, srcDir, base, extension);
11277
10546
  }
11278
10547
  /**
11279
10548
  * Builds navigation items from an explicit theme sidebar tree.
11280
10549
  */
11281
10550
  function buildThemeNavItems(sidebar, base, extension) {
11282
- const toNavItem = (item) => {
11283
- const navItem = {
11284
- title: item.text ?? item.link ?? "Untitled",
11285
- path: sidebarPath(item.link),
11286
- href: sidebarHref(item.link, base, extension)
11287
- };
11288
- if (item.items?.length) navItem.children = item.items.map(toNavItem);
11289
- if (item.collapsed !== void 0) navItem.collapsed = item.collapsed;
11290
- return navItem;
11291
- };
11292
- const groups = [];
11293
- let looseItems = [];
11294
- const flushLooseItems = () => {
11295
- if (looseItems.length > 0) {
11296
- groups.push({
11297
- title: "Guide",
11298
- items: looseItems
11299
- });
11300
- looseItems = [];
11301
- }
11302
- };
11303
- for (const item of sidebar) if (item.items?.length && !item.link) {
11304
- flushLooseItems();
11305
- groups.push({
11306
- title: item.text ?? "Guide",
11307
- items: item.items.map(toNavItem),
11308
- collapsed: item.collapsed
11309
- });
11310
- } else looseItems.push(toNavItem(item));
11311
- flushLooseItems();
11312
- return groups;
10551
+ return importNapiModuleSync().buildSsgThemeNavItems(sidebar, base, extension);
11313
10552
  }
11314
10553
  /**
11315
10554
  * Builds all markdown files to static HTML.
@@ -11320,8 +10559,8 @@ async function buildSsg(options, root) {
11320
10559
  files: [],
11321
10560
  errors: []
11322
10561
  };
11323
- const srcDir = path$2.resolve(root, options.srcDir);
11324
- const outDir = path$2.resolve(root, options.outDir);
10562
+ const srcDir = path$1.resolve(root, options.srcDir);
10563
+ const outDir = path$1.resolve(root, options.outDir);
11325
10564
  const base = options.base.endsWith("/") ? options.base : options.base + "/";
11326
10565
  const generatedFiles = [];
11327
10566
  const generatedPages = [];
@@ -11336,7 +10575,7 @@ async function buildSsg(options, root) {
11336
10575
  const navItems = ssgOptions.theme?.sidebar.length ? buildThemeNavItems(ssgOptions.theme.sidebar, base, ssgOptions.extension) : buildNavItems(markdownFiles, srcDir, base, ssgOptions.extension);
11337
10576
  let siteName = ssgOptions.siteName ?? "Documentation";
11338
10577
  if (!ssgOptions.siteName) try {
11339
- const pkgPath = path$2.join(root, "package.json");
10578
+ const pkgPath = path$1.join(root, "package.json");
11340
10579
  const pkg = JSON.parse(await fs$1.readFile(pkgPath, "utf-8"));
11341
10580
  if (pkg.name) siteName = formatTitle(pkg.name);
11342
10581
  } catch {}
@@ -11368,8 +10607,10 @@ async function buildSsg(options, root) {
11368
10607
  transformedHtml = restoreMermaidSvgs(transformedHtml, mermaidSvgs);
11369
10608
  const title = extractTitle$1(transformedHtml, result.frontmatter);
11370
10609
  const description = result.frontmatter.description;
10610
+ const routePaths = getRoutePaths(inputPath, srcDir, outDir, base, ssgOptions.extension, ssgOptions.siteUrl);
11371
10611
  pageResults.push({
11372
10612
  inputPath,
10613
+ routePaths,
11373
10614
  transformedHtml,
11374
10615
  title,
11375
10616
  description,
@@ -11378,7 +10619,6 @@ async function buildSsg(options, root) {
11378
10619
  toc: result.toc
11379
10620
  });
11380
10621
  if (shouldGenerateOgImages) {
11381
- const ogImageOutputPath = getOgImagePath(inputPath, srcDir, outDir);
11382
10622
  const { layout: _layout, ...frontmatterRest } = result.frontmatter;
11383
10623
  ogImageEntries.push({
11384
10624
  props: {
@@ -11387,10 +10627,10 @@ async function buildSsg(options, root) {
11387
10627
  description,
11388
10628
  siteName
11389
10629
  },
11390
- outputPath: ogImageOutputPath
10630
+ outputPath: routePaths.ogImagePath
11391
10631
  });
11392
10632
  ogImageInputPaths.push(inputPath);
11393
- ogImageUrlMap.set(inputPath, getOgImageUrl(inputPath, srcDir, base, ssgOptions.siteUrl));
10633
+ ogImageUrlMap.set(inputPath, routePaths.ogImageUrl);
11394
10634
  }
11395
10635
  } catch (err) {
11396
10636
  const errorMessage = err instanceof Error ? err.message : String(err);
@@ -11419,7 +10659,7 @@ async function buildSsg(options, root) {
11419
10659
  ogImageUrlMap.clear();
11420
10660
  }
11421
10661
  for (const pageResult of pageResults) try {
11422
- const { inputPath, transformedHtml, title, description, lastUpdated, frontmatter, toc } = pageResult;
10662
+ const { inputPath, routePaths, transformedHtml, title, description, lastUpdated, frontmatter, toc } = pageResult;
11423
10663
  let pageOgImage = ssgOptions.ogImage;
11424
10664
  if (shouldGenerateOgImages && ogImageUrlMap.has(inputPath)) pageOgImage = ogImageUrlMap.get(inputPath);
11425
10665
  let entryPage;
@@ -11437,16 +10677,15 @@ async function buildSsg(options, root) {
11437
10677
  toc,
11438
10678
  lastUpdated,
11439
10679
  frontmatter,
11440
- path: getUrlPath$1(inputPath, srcDir),
11441
- href: getHref(inputPath, srcDir, base, ssgOptions.extension),
10680
+ path: routePaths.urlPath,
10681
+ href: routePaths.href,
11442
10682
  entryPage
11443
10683
  };
11444
10684
  html = await generateHtmlPage(pageData, navItems, siteName, base, pageOgImage, ssgOptions.theme, getPageLocale(pageData.path, options.i18n), options.i18n ? options.i18n.locales : void 0);
11445
10685
  }
11446
- const outputPath = getOutputPath(inputPath, srcDir, outDir, ssgOptions.extension);
11447
10686
  generatedPages.push({
11448
10687
  inputPath,
11449
- outputPath,
10688
+ outputPath: routePaths.outputPath,
11450
10689
  html
11451
10690
  });
11452
10691
  } catch (err) {
@@ -11456,7 +10695,7 @@ async function buildSsg(options, root) {
11456
10695
  const optimizedOutput = await externalizeSharedPageAssets(generatedPages, outDir, base);
11457
10696
  generatedFiles.push(...optimizedOutput.assets);
11458
10697
  for (const page of optimizedOutput.pages) {
11459
- await fs$1.mkdir(path$2.dirname(page.outputPath), { recursive: true });
10698
+ await fs$1.mkdir(path$1.dirname(page.outputPath), { recursive: true });
11460
10699
  await fs$1.writeFile(page.outputPath, page.html, "utf-8");
11461
10700
  generatedFiles.push(page.outputPath);
11462
10701
  }
@@ -11511,7 +10750,7 @@ async function collectMarkdownFiles(dir) {
11511
10750
  try {
11512
10751
  const entries = await fs$1.readdir(currentDir, { withFileTypes: true });
11513
10752
  for (const entry of entries) {
11514
- const fullPath = path$2.join(currentDir, entry.name);
10753
+ const fullPath = path$1.join(currentDir, entry.name);
11515
10754
  if (entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== "node_modules") await walk(fullPath);
11516
10755
  else if (entry.isFile() && entry.name.endsWith(".md")) files.push(fullPath);
11517
10756
  }
@@ -11536,7 +10775,7 @@ async function buildSearchIndex(srcDir, base) {
11536
10775
  const documents = [];
11537
10776
  for (const file of files) try {
11538
10777
  const content = await fs$1.readFile(file, "utf-8");
11539
- const relativePath = path$2.relative(srcDir, file);
10778
+ const relativePath = path$1.relative(srcDir, file);
11540
10779
  const url = base + relativePath.replace(/\.md$/, "").replace(/\\/g, "/");
11541
10780
  const id = relativePath.replace(/\.md$/, "").replace(/\\/g, "/");
11542
10781
  const extractSearchContent = napi.extractSearchContent;
@@ -11565,7 +10804,7 @@ async function buildSearchIndex(srcDir, base) {
11565
10804
  * Writes the search index to a file.
11566
10805
  */
11567
10806
  async function writeSearchIndex(indexJson, outDir) {
11568
- const indexPath = path$2.join(outDir, "search-index.json");
10807
+ const indexPath = path$1.join(outDir, "search-index.json");
11569
10808
  await fs$1.mkdir(outDir, { recursive: true });
11570
10809
  await fs$1.writeFile(indexPath, indexJson, "utf-8");
11571
10810
  }
@@ -11574,234 +10813,7 @@ async function writeSearchIndex(indexJson, outDir) {
11574
10813
  * This is injected into the bundle as a virtual module.
11575
10814
  */
11576
10815
  function generateSearchModule(options, indexPath) {
11577
- return `
11578
- // Search module generated by ox-content
11579
- const searchOptions = ${JSON.stringify(options)};
11580
-
11581
- let searchIndex = null;
11582
- let indexPromise = null;
11583
-
11584
- function parseScopedQuery(query) {
11585
- const scopes = [];
11586
- const terms = [];
11587
-
11588
- for (const part of query.trim().split(/\\s+/).filter(Boolean)) {
11589
- if (part.startsWith('@') && part.length > 1) {
11590
- scopes.push(part.slice(1).toLowerCase());
11591
- } else {
11592
- terms.push(part);
11593
- }
11594
- }
11595
-
11596
- return {
11597
- text: terms.join(' ').trim(),
11598
- scopes: [...new Set(scopes)],
11599
- };
11600
- }
11601
-
11602
- function getScopesForDoc(doc) {
11603
- const source = (doc.id || doc.url || '').replace(/^\\/+/, '').toLowerCase();
11604
- const segments = source.split('/').filter(Boolean);
11605
-
11606
- if (segments.length <= 1) {
11607
- return [];
11608
- }
11609
-
11610
- const scopes = [];
11611
- let current = '';
11612
- for (const segment of segments.slice(0, -1)) {
11613
- current = current ? current + '/' + segment : segment;
11614
- scopes.push(current);
11615
- }
11616
-
11617
- return scopes;
11618
- }
11619
-
11620
- function matchesScopes(doc, scopes) {
11621
- if (!scopes.length) {
11622
- return true;
11623
- }
11624
-
11625
- const docScopes = new Set(getScopesForDoc(doc));
11626
- return scopes.some(scope => docScopes.has(scope));
11627
- }
11628
-
11629
- // Tokenizer for queries
11630
- function tokenizeQuery(text) {
11631
- const tokens = [];
11632
- let current = '';
11633
-
11634
- for (const char of text) {
11635
- const isCjk = /[\\u4E00-\\u9FFF\\u3400-\\u4DBF\\u3040-\\u309F\\u30A0-\\u30FF\\uAC00-\\uD7AF]/.test(char);
11636
-
11637
- if (isCjk) {
11638
- if (current) {
11639
- tokens.push(current.toLowerCase());
11640
- current = '';
11641
- }
11642
- tokens.push(char);
11643
- } else if (/[a-zA-Z0-9_]/.test(char)) {
11644
- current += char;
11645
- } else if (current) {
11646
- tokens.push(current.toLowerCase());
11647
- current = '';
11648
- }
11649
- }
11650
-
11651
- if (current) {
11652
- tokens.push(current.toLowerCase());
11653
- }
11654
-
11655
- return tokens;
11656
- }
11657
-
11658
- // BM25 scoring
11659
- function computeIdf(df, docCount) {
11660
- return Math.log((docCount - df + 0.5) / (df + 0.5) + 1.0);
11661
- }
11662
-
11663
- function getFieldBoost(field) {
11664
- switch (field) {
11665
- case 'Title': return 10.0;
11666
- case 'Heading': return 5.0;
11667
- case 'Body': return 1.0;
11668
- case 'Code': return 0.5;
11669
- default: return 1.0;
11670
- }
11671
- }
11672
-
11673
- // Load the index
11674
- async function loadIndex() {
11675
- if (searchIndex) return searchIndex;
11676
- if (indexPromise) return indexPromise;
11677
-
11678
- indexPromise = fetch('${indexPath}')
11679
- .then(res => res.json())
11680
- .then(data => {
11681
- searchIndex = data;
11682
- return data;
11683
- })
11684
- .catch(err => {
11685
- console.error('[ox-content] Failed to load search index:', err);
11686
- return null;
11687
- });
11688
-
11689
- return indexPromise;
11690
- }
11691
-
11692
- // Search function
11693
- export async function search(query, options = {}) {
11694
- const index = await loadIndex();
11695
-
11696
- if (!index) {
11697
- return [];
11698
- }
11699
-
11700
- const parsedQuery = parseScopedQuery(query);
11701
-
11702
- if (!parsedQuery.text && parsedQuery.scopes.length === 0) {
11703
- return [];
11704
- }
11705
-
11706
- const limit = options.limit ?? searchOptions.limit;
11707
- const prefix = options.prefix ?? searchOptions.prefix;
11708
- const tokens = tokenizeQuery(parsedQuery.text);
11709
-
11710
- const k1 = 1.2;
11711
- const b = 0.75;
11712
- const docScores = new Map();
11713
-
11714
- if (tokens.length === 0) {
11715
- index.documents.forEach((doc, docIdx) => {
11716
- if (matchesScopes(doc, parsedQuery.scopes)) {
11717
- docScores.set(docIdx, { score: 0, matches: new Set() });
11718
- }
11719
- });
11720
- }
11721
-
11722
- for (let i = 0; i < tokens.length; i++) {
11723
- const token = tokens[i];
11724
- const isLast = i === tokens.length - 1;
11725
-
11726
- // Find matching terms
11727
- let matchingTerms = [];
11728
- if (prefix && isLast && token.length >= 2) {
11729
- matchingTerms = Object.keys(index.index).filter(term => term.startsWith(token));
11730
- } else if (index.index[token]) {
11731
- matchingTerms = [token];
11732
- }
11733
-
11734
- for (const term of matchingTerms) {
11735
- const postings = index.index[term] || [];
11736
- const df = index.df[term] || 1;
11737
- const idf = computeIdf(df, index.doc_count);
11738
-
11739
- for (const posting of postings) {
11740
- const doc = index.documents[posting.doc_idx];
11741
- if (!doc) continue;
11742
- if (!matchesScopes(doc, parsedQuery.scopes)) continue;
11743
-
11744
- const docLen = doc.body.length;
11745
- const tf = posting.tf;
11746
- const boost = getFieldBoost(posting.field);
11747
-
11748
- const score = idf * ((tf * (k1 + 1.0)) / (tf + k1 * (1.0 - b + b * docLen / index.avg_dl))) * boost;
11749
-
11750
- if (!docScores.has(posting.doc_idx)) {
11751
- docScores.set(posting.doc_idx, { score: 0, matches: new Set() });
11752
- }
11753
- const entry = docScores.get(posting.doc_idx);
11754
- entry.score += score;
11755
- entry.matches.add(term);
11756
- }
11757
- }
11758
- }
11759
-
11760
- // Convert to results
11761
- const results = Array.from(docScores.entries())
11762
- .map(([docIdx, data]) => {
11763
- const doc = index.documents[docIdx];
11764
- const matches = Array.from(data.matches);
11765
- const scopes = getScopesForDoc(doc);
11766
-
11767
- // Generate snippet
11768
- let snippet = '';
11769
- if (doc.body) {
11770
- const bodyLower = doc.body.toLowerCase();
11771
- let firstPos = -1;
11772
- for (const match of matches) {
11773
- const pos = bodyLower.indexOf(match);
11774
- if (pos !== -1 && (firstPos === -1 || pos < firstPos)) {
11775
- firstPos = pos;
11776
- }
11777
- }
11778
-
11779
- const start = firstPos === -1 ? 0 : Math.max(0, firstPos - 50);
11780
- const end = Math.min(doc.body.length, start + 150);
11781
- snippet = doc.body.slice(start, end);
11782
- if (start > 0) snippet = '...' + snippet;
11783
- if (end < doc.body.length) snippet = snippet + '...';
11784
- }
11785
-
11786
- return {
11787
- id: doc.id,
11788
- title: doc.title,
11789
- url: doc.url,
11790
- score: data.score,
11791
- matches,
11792
- snippet,
11793
- scopes,
11794
- };
11795
- })
11796
- .sort((a, b) => b.score - a.score || a.title.localeCompare(b.title))
11797
- .slice(0, limit);
11798
-
11799
- return results;
11800
- }
11801
-
11802
- export { searchOptions };
11803
- export default { search, searchOptions, loadIndex };
11804
- `;
10816
+ return importNapiModuleSync().generateSearchModule(JSON.stringify(options), indexPath);
11805
10817
  }
11806
10818
  //#endregion
11807
10819
  //#region src/dev-server.ts
@@ -11867,12 +10879,12 @@ async function resolveMarkdownFile(url, srcDir) {
11867
10879
  let relativePath;
11868
10880
  if (pathname === "/") relativePath = "index.md";
11869
10881
  else relativePath = pathname.slice(1) + ".md";
11870
- const filePath = path$2.join(srcDir, relativePath);
10882
+ const filePath = path$1.join(srcDir, relativePath);
11871
10883
  try {
11872
10884
  await fs$1.access(filePath);
11873
10885
  return filePath;
11874
10886
  } catch {
11875
- const indexPath = path$2.join(srcDir, pathname === "/" ? "" : pathname.slice(1), "index.md");
10887
+ const indexPath = path$1.join(srcDir, pathname === "/" ? "" : pathname.slice(1), "index.md");
11876
10888
  try {
11877
10889
  await fs$1.access(indexPath);
11878
10890
  return indexPath;
@@ -11916,7 +10928,7 @@ function invalidatePageCache(cache, filePath) {
11916
10928
  async function resolveSiteName(options, root) {
11917
10929
  if (options.ssg.siteName) return options.ssg.siteName;
11918
10930
  try {
11919
- const pkgPath = path$2.join(root, "package.json");
10931
+ const pkgPath = path$1.join(root, "package.json");
11920
10932
  const pkg = JSON.parse(await fs$1.readFile(pkgPath, "utf-8"));
11921
10933
  if (pkg.name) return formatTitle(pkg.name);
11922
10934
  } catch {}
@@ -11926,7 +10938,7 @@ async function resolveSiteName(options, root) {
11926
10938
  * Render a single markdown page to full HTML.
11927
10939
  */
11928
10940
  async function renderPage$1(filePath, options, navGroups, siteName, base, root) {
11929
- const srcDir = path$2.resolve(root, options.srcDir);
10941
+ const srcDir = path$1.resolve(root, options.srcDir);
11930
10942
  resetTabGroupCounter();
11931
10943
  resetIslandCounter();
11932
10944
  const result = await transformMarkdown(await fs$1.readFile(filePath, "utf-8"), filePath, options, {
@@ -11971,7 +10983,7 @@ async function renderPage$1(filePath, options, navGroups, siteName, base, root)
11971
10983
  * Create the dev server middleware for SSG page serving.
11972
10984
  */
11973
10985
  function createDevServerMiddleware(options, root, cache) {
11974
- const srcDir = path$2.resolve(root, options.srcDir);
10986
+ const srcDir = path$1.resolve(root, options.srcDir);
11975
10987
  const base = options.base.endsWith("/") ? options.base : options.base + "/";
11976
10988
  return async (req, res, next) => {
11977
10989
  const url = req.url;
@@ -12036,7 +11048,7 @@ function extractTitle(content, frontmatter) {
12036
11048
  return match ? match[1].trim() : "";
12037
11049
  }
12038
11050
  function getUrlPath(filePath, srcDir) {
12039
- let rel = path$2.relative(srcDir, filePath).replace(/\\/g, "/");
11051
+ let rel = path$1.relative(srcDir, filePath).replace(/\\/g, "/");
12040
11052
  rel = rel.replace(/\.md$/, "");
12041
11053
  if (rel === "index") return "/";
12042
11054
  if (rel.endsWith("/index")) rel = rel.slice(0, -6);
@@ -12076,7 +11088,7 @@ function validatePage(page, options) {
12076
11088
  return warnings;
12077
11089
  }
12078
11090
  async function collectPages(options, root) {
12079
- const srcDir = path$2.resolve(root, options.srcDir);
11091
+ const srcDir = path$1.resolve(root, options.srcDir);
12080
11092
  const files = await glob("**/*.md", {
12081
11093
  cwd: srcDir,
12082
11094
  absolute: true
@@ -12094,7 +11106,7 @@ async function collectPages(options, root) {
12094
11106
  const urlPath = getUrlPath(file, srcDir);
12095
11107
  const ogImageUrl = computeOgImageUrl(urlPath, options.base, options.ssg.siteUrl, generateOgImage, options.ssg.ogImage);
12096
11108
  const page = {
12097
- path: path$2.relative(srcDir, file),
11109
+ path: path$1.relative(srcDir, file),
12098
11110
  urlPath,
12099
11111
  title,
12100
11112
  description,
@@ -12407,7 +11419,7 @@ function createI18nPlugin(resolvedOptions) {
12407
11419
  },
12408
11420
  async buildStart() {
12409
11421
  if (!i18nOptions || !i18nOptions.check) return;
12410
- const dictDir = path$2.resolve(root, i18nOptions.dir);
11422
+ const dictDir = path$1.resolve(root, i18nOptions.dir);
12411
11423
  if (!fs$2.existsSync(dictDir)) {
12412
11424
  console.warn(`[ox-content:i18n] Dictionary directory not found: ${dictDir}`);
12413
11425
  return;
@@ -12429,7 +11441,7 @@ function createI18nPlugin(resolvedOptions) {
12429
11441
  },
12430
11442
  configureServer(server) {
12431
11443
  if (!i18nOptions) return;
12432
- const dictDir = path$2.resolve(root, i18nOptions.dir);
11444
+ const dictDir = path$1.resolve(root, i18nOptions.dir);
12433
11445
  if (fs$2.existsSync(dictDir)) {
12434
11446
  server.watcher.add(dictDir);
12435
11447
  server.watcher.on("change", (filePath) => {
@@ -12456,206 +11468,31 @@ function createI18nPlugin(resolvedOptions) {
12456
11468
  * Generates the virtual module for i18n configuration.
12457
11469
  */
12458
11470
  function generateI18nModule(options, root) {
12459
- const dictDir = path$2.resolve(root, options.dir);
12460
- const localesJson = JSON.stringify(options.locales);
12461
- const defaultLocale = JSON.stringify(options.defaultLocale);
12462
- let dictionariesCode = "{}";
11471
+ const dictDir = path$1.resolve(root, options.dir);
11472
+ const config = {
11473
+ defaultLocale: options.defaultLocale,
11474
+ locales: options.locales,
11475
+ hideDefaultLocale: options.hideDefaultLocale
11476
+ };
12463
11477
  try {
12464
11478
  const napi = __require("@ox-content/napi");
12465
- if (napi.loadDictionariesFlat) {
12466
- const dictData = napi.loadDictionariesFlat(dictDir);
12467
- dictionariesCode = JSON.stringify(dictData);
12468
- } else dictionariesCode = JSON.stringify(loadDictionariesFallback(options, dictDir));
12469
- } catch {
12470
- try {
12471
- dictionariesCode = JSON.stringify(loadDictionariesFallback(options, dictDir));
12472
- } catch {}
12473
- }
12474
- return `
12475
- export const i18nConfig = {
12476
- enabled: true,
12477
- defaultLocale: ${defaultLocale},
12478
- locales: ${localesJson},
12479
- hideDefaultLocale: ${JSON.stringify(options.hideDefaultLocale)},
12480
- };
12481
-
12482
- export const dictionaries = ${dictionariesCode};
12483
-
12484
- export function t(key, params, locale) {
12485
- const dict = dictionaries[locale || i18nConfig.defaultLocale] || {};
12486
- let message = dict[key];
12487
- if (!message) {
12488
- const fallback = dictionaries[i18nConfig.defaultLocale] || {};
12489
- message = fallback[key] || key;
12490
- }
12491
- if (params) {
12492
- for (const [k, v] of Object.entries(params)) {
12493
- message = message.split('{$' + k + '}').join(String(v));
12494
- }
12495
- }
12496
- return message;
12497
- }
12498
-
12499
- export function getLocaleFromPath(pathname) {
12500
- const match = pathname.match(new RegExp('^/([A-Za-z]{2,3}(?:-[A-Za-z0-9]+)*)(/|$)'));
12501
- if (match) {
12502
- const code = match[1];
12503
- if (i18nConfig.locales.some(l => l.code === code)) {
12504
- return code;
12505
- }
12506
- }
12507
- return i18nConfig.defaultLocale;
12508
- }
12509
-
12510
- export function localePath(pathname, locale) {
12511
- const current = getLocaleFromPath(pathname);
12512
- let clean = pathname;
12513
- if (current !== i18nConfig.defaultLocale || !i18nConfig.hideDefaultLocale) {
12514
- const prefix = '/' + current;
12515
- if (clean === prefix) clean = '/';
12516
- else if (clean.startsWith(prefix + '/')) clean = clean.slice(prefix.length);
12517
- }
12518
- if (locale === i18nConfig.defaultLocale && i18nConfig.hideDefaultLocale) {
12519
- return clean || '/';
12520
- }
12521
- return '/' + locale + (clean.startsWith('/') ? clean : '/' + clean);
12522
- }
12523
-
12524
- const formatterCache = new Map();
12525
-
12526
- function getFormatter(kind, locale, options) {
12527
- const key = kind + ':' + locale + ':' + JSON.stringify(options || {});
12528
- if (!formatterCache.has(key)) {
12529
- formatterCache.set(key, new Intl[kind](locale, options));
12530
- }
12531
- return formatterCache.get(key);
12532
- }
12533
-
12534
- export function getLocaleMeta(locale) {
12535
- const code = locale || i18nConfig.defaultLocale;
12536
- return i18nConfig.locales.find(l => l.code === code) || { code, name: code, dir: 'ltr' };
12537
- }
12538
-
12539
- export function formatDate(value, options, locale) {
12540
- return getFormatter('DateTimeFormat', locale || i18nConfig.defaultLocale, options).format(
12541
- value instanceof Date ? value : new Date(value),
12542
- );
12543
- }
12544
-
12545
- export function formatDateParts(value, options, locale) {
12546
- return getFormatter('DateTimeFormat', locale || i18nConfig.defaultLocale, options).formatToParts(
12547
- value instanceof Date ? value : new Date(value),
12548
- );
12549
- }
12550
-
12551
- export function formatNumber(value, options, locale) {
12552
- return getFormatter('NumberFormat', locale || i18nConfig.defaultLocale, options).format(value);
12553
- }
12554
-
12555
- export function formatNumberParts(value, options, locale) {
12556
- return getFormatter('NumberFormat', locale || i18nConfig.defaultLocale, options).formatToParts(value);
12557
- }
12558
-
12559
- export function formatRelativeTime(value, unit, options, locale) {
12560
- return getFormatter('RelativeTimeFormat', locale || i18nConfig.defaultLocale, options).format(value, unit);
12561
- }
12562
-
12563
- export function formatList(values, options, locale) {
12564
- return getFormatter('ListFormat', locale || i18nConfig.defaultLocale, options).format(values);
12565
- }
12566
-
12567
- export function formatListParts(values, options, locale) {
12568
- return getFormatter('ListFormat', locale || i18nConfig.defaultLocale, options).formatToParts(values);
12569
- }
12570
-
12571
- export function formatDisplayName(value, type, options, locale) {
12572
- if (!Intl.DisplayNames) return String(value);
12573
- const displayType = type || 'language';
12574
- return getFormatter('DisplayNames', locale || i18nConfig.defaultLocale, { type: displayType, ...options }).of(value) || String(value);
12575
- }
12576
-
12577
- export function createIntl(locale, defaults = {}) {
12578
- const meta = getLocaleMeta(locale);
12579
- const code = meta.code;
12580
- return {
12581
- locale: code,
12582
- meta,
12583
- dir: meta.dir || 'ltr',
12584
- date: (value, options) => formatDate(value, { ...defaults.date, ...options }, code),
12585
- dateParts: (value, options) => formatDateParts(value, { ...defaults.date, ...options }, code),
12586
- number: (value, options) => formatNumber(value, { ...defaults.number, ...options }, code),
12587
- numberParts: (value, options) => formatNumberParts(value, { ...defaults.number, ...options }, code),
12588
- relativeTime: (value, unit, options) => formatRelativeTime(value, unit, { ...defaults.relativeTime, ...options }, code),
12589
- list: (values, options) => formatList(values, { ...defaults.list, ...options }, code),
12590
- listParts: (values, options) => formatListParts(values, { ...defaults.list, ...options }, code),
12591
- displayName: (value, type, options) => formatDisplayName(value, type, { ...defaults.displayName, ...options }, code),
12592
- };
12593
- }
12594
-
12595
- export default {
12596
- i18nConfig,
12597
- dictionaries,
12598
- t,
12599
- getLocaleFromPath,
12600
- localePath,
12601
- getLocaleMeta,
12602
- createIntl,
12603
- formatDate,
12604
- formatDateParts,
12605
- formatNumber,
12606
- formatNumberParts,
12607
- formatRelativeTime,
12608
- formatList,
12609
- formatListParts,
12610
- formatDisplayName,
12611
- };
12612
- `;
12613
- }
12614
- /**
12615
- * Flattens a nested object into dot-separated keys.
12616
- */
12617
- function flattenObject(obj, prefix, result) {
12618
- for (const [key, value] of Object.entries(obj)) {
12619
- const fullKey = `${prefix}.${key}`;
12620
- if (typeof value === "string") result[fullKey] = value;
12621
- else if (typeof value === "object" && value !== null && !Array.isArray(value)) flattenObject(value, fullKey, result);
12622
- else result[fullKey] = String(value);
12623
- }
12624
- }
12625
- /**
12626
- * Fallback dictionary loading using TS-based JSON file reading.
12627
- */
12628
- function loadDictionariesFallback(options, dictDir) {
12629
- const dictData = {};
12630
- for (const locale of options.locales) {
12631
- const localeDir = path$2.join(dictDir, locale.code);
12632
- if (!fs$2.existsSync(localeDir)) continue;
12633
- const files = fs$2.readdirSync(localeDir);
12634
- const localeDict = {};
12635
- for (const file of files) {
12636
- if (!file.endsWith(".json")) continue;
12637
- const filePath = path$2.join(localeDir, file);
12638
- const content = fs$2.readFileSync(filePath, "utf-8");
12639
- const namespace = path$2.basename(file, ".json");
12640
- try {
12641
- flattenObject(JSON.parse(content), namespace, localeDict);
12642
- } catch {}
12643
- }
12644
- dictData[locale.code] = localeDict;
11479
+ if (typeof napi.generateI18nModule === "function") return napi.generateI18nModule(dictDir, config);
11480
+ } catch (error) {
11481
+ throw new Error(`[ox-content:i18n] Failed to load @ox-content/napi for i18n module generation: ${String(error)}`);
12645
11482
  }
12646
- return dictData;
11483
+ throw new Error("[ox-content:i18n] @ox-content/napi does not expose generateI18nModule. Please rebuild the NAPI package.");
12647
11484
  }
12648
11485
  /**
12649
11486
  * Collects translation keys from source files using NAPI extractTranslationKeys.
12650
11487
  */
12651
11488
  function collectKeysFromSource(root, extractTranslationKeys, options) {
12652
- const srcDir = path$2.resolve(root, "src");
11489
+ const srcDir = path$1.resolve(root, "src");
12653
11490
  const keys = /* @__PURE__ */ new Set();
12654
11491
  if (fs$2.existsSync(srcDir)) walkDir(srcDir, /\.(ts|tsx|js|jsx)$/, (filePath) => {
12655
11492
  const usages = extractTranslationKeys(fs$2.readFileSync(filePath, "utf-8"), filePath, options.functionNames);
12656
11493
  for (const usage of usages) keys.add(usage.key);
12657
11494
  });
12658
- const contentDir = path$2.resolve(root, "content");
11495
+ const contentDir = path$1.resolve(root, "content");
12659
11496
  if (fs$2.existsSync(contentDir)) {
12660
11497
  const tPattern = /\{\{t\(['"]([^'"]+)['"]\)\}\}/g;
12661
11498
  walkDir(contentDir, /\.(md|mdx)$/, (filePath) => {
@@ -12673,7 +11510,7 @@ function collectKeysFromSource(root, extractTranslationKeys, options) {
12673
11510
  function walkDir(dir, pattern, callback) {
12674
11511
  const entries = fs$2.readdirSync(dir, { withFileTypes: true });
12675
11512
  for (const entry of entries) {
12676
- const fullPath = path$2.join(dir, entry.name);
11513
+ const fullPath = path$1.join(dir, entry.name);
12677
11514
  if (entry.isDirectory()) {
12678
11515
  if (entry.name === "node_modules" || entry.name === ".git") continue;
12679
11516
  walkDir(fullPath, pattern, callback);
@@ -12926,7 +11763,7 @@ const DEFAULT_LINT_FILE_EXCLUDE = [
12926
11763
  */
12927
11764
  function shouldLintMarkdownFile(filePath, options = {}) {
12928
11765
  const resolvedOptions = resolveMarkdownLintFileOptions(options);
12929
- return shouldLintAbsoluteFile(path$1.resolve(resolvedOptions.cwd, filePath), resolvedOptions);
11766
+ return shouldLintAbsoluteFile(path.resolve(resolvedOptions.cwd, filePath), resolvedOptions);
12930
11767
  }
12931
11768
  /**
12932
11769
  * Lints a single Markdown file using project-style include/exclude settings.
@@ -12936,7 +11773,7 @@ function shouldLintMarkdownFile(filePath, options = {}) {
12936
11773
  */
12937
11774
  async function lintMarkdownFile(filePath, options = {}) {
12938
11775
  const resolvedOptions = resolveMarkdownLintFileOptions(options);
12939
- return lintMarkdownFileWithResolvedOptions(path$1.resolve(resolvedOptions.cwd, filePath), resolvedOptions);
11776
+ return lintMarkdownFileWithResolvedOptions(path.resolve(resolvedOptions.cwd, filePath), resolvedOptions);
12940
11777
  }
12941
11778
  /**
12942
11779
  * Lints all Markdown files matched by the configured include/exclude patterns.
@@ -12967,7 +11804,7 @@ async function lintMarkdownFiles(options = {}) {
12967
11804
  }
12968
11805
  function resolveMarkdownLintFileOptions(options) {
12969
11806
  return {
12970
- cwd: path$1.resolve(options.cwd ?? process.cwd()),
11807
+ cwd: path.resolve(options.cwd ?? process.cwd()),
12971
11808
  exclude: [...new Set([...options.exclude ?? DEFAULT_LINT_FILE_EXCLUDE, ...options.ignore ?? []])],
12972
11809
  include: [...new Set(options.include ?? DEFAULT_LINT_FILE_INCLUDE)],
12973
11810
  lintOptions: {
@@ -12978,8 +11815,8 @@ function resolveMarkdownLintFileOptions(options) {
12978
11815
  };
12979
11816
  }
12980
11817
  async function lintMarkdownFileWithResolvedOptions(filePath, options) {
12981
- const absoluteFilePath = path$1.resolve(filePath);
12982
- const relativePath = normalizePath(path$1.relative(options.cwd, absoluteFilePath));
11818
+ const absoluteFilePath = path.resolve(filePath);
11819
+ const relativePath = normalizePath(path.relative(options.cwd, absoluteFilePath));
12983
11820
  if (!shouldLintAbsoluteFile(absoluteFilePath, options)) return {
12984
11821
  ...createEmptyLintResult(),
12985
11822
  filePath: absoluteFilePath,
@@ -13003,26 +11840,26 @@ async function collectMarkdownLintFileEntries(options) {
13003
11840
  nodir: true
13004
11841
  });
13005
11842
  for (const filePath of matches) {
13006
- const absoluteFilePath = path$1.resolve(filePath);
11843
+ const absoluteFilePath = path.resolve(filePath);
13007
11844
  if (shouldLintAbsoluteFile(absoluteFilePath, options)) files.set(absoluteFilePath, {
13008
11845
  filePath: absoluteFilePath,
13009
- relativePath: normalizePath(path$1.relative(options.cwd, absoluteFilePath))
11846
+ relativePath: normalizePath(path.relative(options.cwd, absoluteFilePath))
13010
11847
  });
13011
11848
  }
13012
11849
  }
13013
11850
  return [...files.values()].sort((left, right) => left.filePath.localeCompare(right.filePath));
13014
11851
  }
13015
11852
  function shouldLintAbsoluteFile(filePath, options) {
13016
- const absolutePath = normalizePath(path$1.resolve(filePath));
13017
- const relativePath = normalizePath(path$1.relative(options.cwd, absolutePath));
11853
+ const absolutePath = normalizePath(path.resolve(filePath));
11854
+ const relativePath = normalizePath(path.relative(options.cwd, absolutePath));
13018
11855
  const matches = (patterns) => patterns.some((pattern) => {
13019
11856
  const normalizedPattern = normalizePath(pattern);
13020
- return path$1.matchesGlob(relativePath, normalizedPattern) || path$1.matchesGlob(absolutePath, normalizedPattern);
11857
+ return path.matchesGlob(relativePath, normalizedPattern) || path.matchesGlob(absolutePath, normalizedPattern);
13021
11858
  });
13022
11859
  return matches(options.include) && !matches(options.exclude);
13023
11860
  }
13024
11861
  function normalizePath(value) {
13025
- return value.split(path$1.sep).join("/");
11862
+ return value.split(path.sep).join("/");
13026
11863
  }
13027
11864
  function createEmptyLintResult() {
13028
11865
  return {
@@ -13607,8 +12444,8 @@ function oxContent(options = {}) {
13607
12444
  async function regenerateDocs(root) {
13608
12445
  const docsOptions = resolvedOptions.docs;
13609
12446
  if (!docsOptions || !docsOptions.enabled) return 0;
13610
- const srcDirs = docsOptions.src.map((src) => path$2.resolve(root, src));
13611
- const outDir = path$2.resolve(root, docsOptions.out);
12447
+ const srcDirs = docsOptions.src.map((src) => path$1.resolve(root, src));
12448
+ const outDir = path$1.resolve(root, docsOptions.out);
13612
12449
  const extracted = await extractDocs(srcDirs, docsOptions);
13613
12450
  const generated = generateMarkdown(extracted, docsOptions);
13614
12451
  await writeDocs(generated, outDir, extracted, docsOptions);
@@ -13677,7 +12514,7 @@ function oxContent(options = {}) {
13677
12514
  const docsOptions = resolvedOptions.docs;
13678
12515
  if (!docsOptions || !docsOptions.enabled) return;
13679
12516
  const root = config?.root || process.cwd();
13680
- const srcDirs = docsOptions.src.map((src) => path$2.resolve(root, src));
12517
+ const srcDirs = docsOptions.src.map((src) => path$1.resolve(root, src));
13681
12518
  for (const srcDir of srcDirs) devServer.watcher.add(srcDir);
13682
12519
  devServer.watcher.on("all", async (event, file) => {
13683
12520
  if (event !== "add" && event !== "change" && event !== "unlink") return;
@@ -13693,7 +12530,7 @@ function oxContent(options = {}) {
13693
12530
  configureServer(devServer) {
13694
12531
  if (!resolvedOptions.ssg.enabled) return;
13695
12532
  const root = config?.root || process.cwd();
13696
- const srcDir = path$2.resolve(root, resolvedOptions.srcDir);
12533
+ const srcDir = path$1.resolve(root, resolvedOptions.srcDir);
13697
12534
  devServer.middlewares.use(createDevServerMiddleware(resolvedOptions, root, ssgDevCache));
13698
12535
  devServer.watcher.on("add", (file) => {
13699
12536
  if (file.startsWith(srcDir) && file.endsWith(".md")) {
@@ -13760,7 +12597,7 @@ function oxContent(options = {}) {
13760
12597
  async buildStart() {
13761
12598
  if (!resolvedOptions.search.enabled) return;
13762
12599
  const root = config?.root || process.cwd();
13763
- const srcDir = path$2.resolve(root, resolvedOptions.srcDir);
12600
+ const srcDir = path$1.resolve(root, resolvedOptions.srcDir);
13764
12601
  try {
13765
12602
  searchIndexJson = await buildSearchIndex(srcDir, resolvedOptions.base);
13766
12603
  console.log("[ox-content] Search index built");
@@ -13771,10 +12608,10 @@ function oxContent(options = {}) {
13771
12608
  async closeBundle() {
13772
12609
  if (!resolvedOptions.search.enabled || !searchIndexJson) return;
13773
12610
  const root = config?.root || process.cwd();
13774
- const outDir = path$2.resolve(root, resolvedOptions.outDir);
12611
+ const outDir = path$1.resolve(root, resolvedOptions.outDir);
13775
12612
  try {
13776
12613
  await writeSearchIndex(searchIndexJson, outDir);
13777
- console.log("[ox-content] Search index written to", path$2.join(outDir, "search-index.json"));
12614
+ console.log("[ox-content] Search index written to", path$1.join(outDir, "search-index.json"));
13778
12615
  } catch (err) {
13779
12616
  console.warn("[ox-content] Failed to write search index:", err);
13780
12617
  }