@lupinum/ginko-content 1.0.0-beta.4 → 1.0.0-beta.6

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/README.md CHANGED
@@ -15,7 +15,7 @@
15
15
  </p>
16
16
 
17
17
  > [!WARNING]
18
- > Version `1.0.0-beta.4` is a prerelease. Install it from npm's `next`
18
+ > Version `1.0.0-beta.6` is a prerelease. Install it from npm's `next`
19
19
  > channel. The stable `0.3` line remains on `latest`.
20
20
 
21
21
  ## Why use this package?
@@ -32,7 +32,7 @@ output.
32
32
 
33
33
  - Node.js 22.18–22.x, 24.11–24.x, or 26+
34
34
  - Nuxt 4.5.1 through Nuxt 4.x
35
- - Vue 3.5.35 through Vue 3.x
35
+ - Vue 3.5.40 through Vue 3.x
36
36
  - ESM; CommonJS `require()` is not supported
37
37
 
38
38
  ## Installation
@@ -40,13 +40,13 @@ output.
40
40
  Use the Nuxt CLI:
41
41
 
42
42
  ```bash
43
- npx nuxi module add @lupinum/ginko-content@1.0.0-beta.4
43
+ npx nuxi module add @lupinum/ginko-content@1.0.0-beta.6
44
44
  ```
45
45
 
46
46
  Or install and register the module by hand:
47
47
 
48
48
  ```bash
49
- pnpm add @lupinum/ginko-content@1.0.0-beta.4
49
+ pnpm add @lupinum/ginko-content@1.0.0-beta.6
50
50
  ```
51
51
 
52
52
  ```ts
package/dist/module.d.mts CHANGED
@@ -850,8 +850,17 @@ type ContentLinksOptions = Record<string, Record<string, ContentLinkRouteTarget>
850
850
  interface ContentAgentRouteOptions {
851
851
  routes?: boolean;
852
852
  linkHeaders?: boolean;
853
- markdownNegotiation?: boolean;
854
- prerender?: boolean;
853
+ /**
854
+ * Select static page delivery or request-time Nitro negotiation.
855
+ *
856
+ * `static` prerenders public pages and keeps raw Markdown as the canonical
857
+ * agent representation. `runtime` lets Nitro crawl pages to retain their
858
+ * data dependencies, then removes the generated HTML so Nitro can negotiate
859
+ * each page request.
860
+ *
861
+ * @default 'static'
862
+ */
863
+ delivery?: 'static' | 'runtime';
855
864
  }
856
865
  interface ModuleOptions {
857
866
  api: {
package/dist/module.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lupinum/ginko-content",
3
- "version": "1.0.0-beta.4",
3
+ "version": "1.0.0-beta.6",
4
4
  "configKey": "content",
5
5
  "compatibility": {
6
6
  "nuxt": ">=4.5.1 <5"
package/dist/module.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import { addTypeTemplate, addImports, addServerImports, addComponentsDir, getLayerDirectories, addPlugin, addServerHandler, hasNuxtModule, defineNuxtModule, createResolver, useLogger, addTemplate, resolvePath } from '@nuxt/kit';
2
2
  import { readFile, readdir, rm } from 'node:fs/promises';
3
- import { join as join$1, basename, resolve as resolve$1, dirname, isAbsolute as isAbsolute$1 } from 'node:path';
3
+ import { join as join$1, basename, resolve as resolve$1, dirname, relative as relative$1, isAbsolute as isAbsolute$1 } from 'node:path';
4
4
  import { buildResolvedContentContract } from '../dist/cms-contract/index.js';
5
5
  import { writeResolvedContentContractArtifact } from '../dist/cms-contract-node/artifact.js';
6
6
  import fs, { existsSync } from 'fs';
@@ -34,7 +34,7 @@ import { assertCanonicalHighlightOptionNames } from '../dist/parsers/markdown-pl
34
34
  import { BUILTIN_MARKDOWN_RENDER_CONTRACTS } from '../dist/core/markdown/builtin-render-contracts.js';
35
35
 
36
36
  const name = "@lupinum/ginko-content";
37
- const version = "1.0.0-beta.4";
37
+ const version = "1.0.0-beta.6";
38
38
  const peerDependencies = {
39
39
  nuxt: ">=4.5.1 <5"};
40
40
 
@@ -439,15 +439,13 @@ const normalizeAgentRouteOptions = (options) => {
439
439
  return {
440
440
  routes: false,
441
441
  linkHeaders: false,
442
- markdownNegotiation: false,
443
- prerender: false
442
+ delivery: "static"
444
443
  };
445
444
  }
446
445
  return {
447
446
  routes: options.agent?.routes !== false,
448
447
  linkHeaders: options.agent?.linkHeaders !== false,
449
- markdownNegotiation: options.agent?.markdownNegotiation !== false,
450
- prerender: options.agent?.prerender !== false
448
+ delivery: options.agent?.delivery || "static"
451
449
  };
452
450
  };
453
451
 
@@ -540,7 +538,7 @@ const registerContentServerHandlers = (nuxt, options, resolveRuntimeModule, buil
540
538
  handler: resolveRuntimeModule("./server/api/agent-raw.js")
541
539
  });
542
540
  }
543
- if (agent.markdownNegotiation) {
541
+ if (agent.delivery === "runtime") {
544
542
  addServerHandler({
545
543
  middleware: true,
546
544
  handler: resolveRuntimeModule("./server/middleware/agent-markdown.js")
@@ -1111,6 +1109,7 @@ const collectRawMarkdownRoutesFromGeneratedFrontmatter = (markdown) => {
1111
1109
  };
1112
1110
  const publicOutputPath = (publicDir, route) => join(publicDir, normalizeStaticRoutePath(route).replace(/^\//, ""));
1113
1111
 
1112
+ const runtimeModuleImportURL = (modulePath, toFileURL = pathToFileURL) => toFileURL(modulePath).href;
1114
1113
  const assertPrerenderedContentSnapshot = (buildDir) => {
1115
1114
  const snapshotPath = resolve$1(buildDir, "content-cache/snapshot.json");
1116
1115
  let raw;
@@ -1168,12 +1167,12 @@ ${details}` : "")
1168
1167
  writeFileSync(indexPath, json, "utf8");
1169
1168
  if (searchRuntime.engine === "pagefind") {
1170
1169
  const records = JSON.parse(json);
1171
- const { writePagefindIndex } = await import(resolveRuntimeModule("./server/pagefind.js"));
1170
+ const { writePagefindIndex } = await import(runtimeModuleImportURL(resolveRuntimeModule("./server/pagefind.js")));
1172
1171
  await writePagefindIndex(records, resolve$1(publicDir, "pagefind"), resolvedI18n.defaultLocale);
1173
1172
  }
1174
1173
  }
1175
1174
  const agentRoutes = normalizeAgentRouteOptions(options);
1176
- if (agentRoutes.routes && agentRoutes.prerender && appContentConfig.agent) {
1175
+ if (agentRoutes.routes && appContentConfig.agent) {
1177
1176
  const defaultLocale = resolvedI18n.defaultLocale;
1178
1177
  const locales = resolvedI18n.locales;
1179
1178
  const llmsRoutes = [
@@ -1306,17 +1305,33 @@ const registerContentNitroIntegrationHooks = (nitroConfig, options, contentConte
1306
1305
  };
1307
1306
  nitroConfig.hooks ||= {};
1308
1307
  const usesFilesystemProviderAtRegistration = !contentContext.provider || contentContext.provider === "filesystem";
1309
- if (usesFilesystemProviderAtRegistration) {
1308
+ if (usesFilesystemProviderAtRegistration || options.removePrerenderedHtml) {
1310
1309
  let prerenderingPublicDir;
1311
1310
  appendHook(nitroConfig.hooks, "prerender:init", async (nitro) => {
1312
1311
  prerenderingPublicDir = nitro.options.output.publicDir;
1313
1312
  });
1314
- appendHook(nitroConfig.hooks, "prerender:done", async () => {
1313
+ appendHook(nitroConfig.hooks, "prerender:done", async (result) => {
1315
1314
  if (!prerenderingPublicDir) {
1316
1315
  return;
1317
1316
  }
1318
- const staleArtifactPath = join$1(prerenderingPublicDir, options.cacheRoute);
1319
- await rm(staleArtifactPath, { recursive: true, force: true });
1317
+ const publicDir = prerenderingPublicDir;
1318
+ if (usesFilesystemProviderAtRegistration) {
1319
+ const staleArtifactPath = join$1(publicDir, options.cacheRoute);
1320
+ await rm(staleArtifactPath, { recursive: true, force: true });
1321
+ }
1322
+ if (options.removePrerenderedHtml) {
1323
+ await Promise.all((result.prerenderedRoutes || []).map(async (route) => {
1324
+ if (!route.contentType?.includes("text/html") || !route.fileName) {
1325
+ return;
1326
+ }
1327
+ const artifactPath = resolve$1(publicDir, route.fileName.replace(/^\/+/, ""));
1328
+ const relativeArtifactPath = relative$1(publicDir, artifactPath);
1329
+ if (relativeArtifactPath.startsWith("..") || isAbsolute$1(relativeArtifactPath)) {
1330
+ throw new Error(`[content] refused to remove a prerendered HTML artifact outside publicDir: ${route.fileName}`);
1331
+ }
1332
+ await rm(artifactPath, { recursive: true, force: true });
1333
+ }));
1334
+ }
1320
1335
  });
1321
1336
  }
1322
1337
  if (contentContext.sitemap && contentContext.sitemap.assert?.enabled) {
@@ -1350,6 +1365,29 @@ const registerContentNitroIntegrationHooks = (nitroConfig, options, contentConte
1350
1365
  });
1351
1366
  };
1352
1367
 
1368
+ const NUXT_NITRO_CACHE_DRIVER_SUFFIX = "/node_modules/@nuxt/nitro-server/dist/runtime/utils/cache-driver.mjs";
1369
+ const createNuxtWindowsCacheDriverResolver = (platform = process.platform, fromFileURL = fileURLToPath) => {
1370
+ if (platform !== "win32") {
1371
+ return void 0;
1372
+ }
1373
+ return {
1374
+ name: "ginko-content:nuxt-windows-cache-driver",
1375
+ resolveId(id) {
1376
+ if (!id.startsWith("file:")) {
1377
+ return null;
1378
+ }
1379
+ try {
1380
+ const url = new URL(id);
1381
+ if (url.protocol !== "file:" || url.hostname !== "" || url.search !== "" || url.hash !== "" || !url.pathname.toLowerCase().endsWith(NUXT_NITRO_CACHE_DRIVER_SUFFIX)) {
1382
+ return null;
1383
+ }
1384
+ return fromFileURL(url);
1385
+ } catch {
1386
+ return null;
1387
+ }
1388
+ }
1389
+ };
1390
+ };
1353
1391
  const hookNuxtBoundary$1 = (nuxt, name, callback) => {
1354
1392
  const hook = nuxt.hook;
1355
1393
  hook(name, callback);
@@ -1369,6 +1407,7 @@ const registerContentNitroConfig = ({
1369
1407
  }) => {
1370
1408
  hookNuxtBoundary$1(nuxt, "nitro:config", (nitroConfig) => {
1371
1409
  const searchRuntime = getSearchRuntime();
1410
+ const agentRoutes = normalizeAgentRouteOptions(options);
1372
1411
  nitroConfig.prerender = nitroConfig.prerender || {};
1373
1412
  nitroConfig.prerender.routes = nitroConfig.prerender.routes || [];
1374
1413
  const usesFilesystemProvider = !contentContext.provider || contentContext.provider === "filesystem";
@@ -1399,6 +1438,12 @@ const registerContentNitroConfig = ({
1399
1438
  }
1400
1439
  nitroConfig.bundledStorage = nitroConfig.bundledStorage || [];
1401
1440
  nitroConfig.bundledStorage.push("cache:content");
1441
+ const windowsCacheDriverResolver = createNuxtWindowsCacheDriverResolver();
1442
+ if (windowsCacheDriverResolver) {
1443
+ nitroConfig.rollupConfig ||= {};
1444
+ nitroConfig.rollupConfig.plugins ||= [];
1445
+ nitroConfig.rollupConfig.plugins.push(windowsCacheDriverResolver);
1446
+ }
1402
1447
  nitroConfig.externals = defu(typeof nitroConfig.externals === "object" ? nitroConfig.externals : {}, {
1403
1448
  inline: [
1404
1449
  resolveModuleFile("."),
@@ -1432,6 +1477,7 @@ const registerContentNitroConfig = ({
1432
1477
  }
1433
1478
  registerContentNitroIntegrationHooks(nitroConfig, {
1434
1479
  cacheRoute,
1480
+ removePrerenderedHtml: agentRoutes.delivery === "runtime" && !("_generate" in nuxt.options && nuxt.options._generate === true),
1435
1481
  sitemapPrerenderRoutes: () => contentContext.sitemap === false ? [] : resolveNuxtSitemapPrerenderRoutes(nuxt),
1436
1482
  resolveContentContext: () => {
1437
1483
  const resolved = getResolvedContentContext();
@@ -1441,7 +1487,6 @@ const registerContentNitroConfig = ({
1441
1487
  sitemap: contentContext.sitemap,
1442
1488
  provider: contentContext.provider
1443
1489
  });
1444
- const agentRoutes = normalizeAgentRouteOptions(options);
1445
1490
  if (agentRoutes.routes && appContentConfig.agent) {
1446
1491
  nitroConfig.plugins ||= [];
1447
1492
  const agentErrorsPlugin = resolveRuntimeModule("server/plugins/agent-errors.js");
@@ -1449,7 +1494,7 @@ const registerContentNitroConfig = ({
1449
1494
  nitroConfig.plugins.push(agentErrorsPlugin);
1450
1495
  }
1451
1496
  }
1452
- if (agentRoutes.routes && agentRoutes.prerender && appContentConfig.agent) {
1497
+ if (agentRoutes.routes && appContentConfig.agent) {
1453
1498
  nitroConfig.prerender.routes.push("/llms.txt", "/llms-full.txt");
1454
1499
  for (const locale of resolvedI18n.locales || []) {
1455
1500
  if (locale && locale !== resolvedI18n.defaultLocale) {
@@ -1658,8 +1703,7 @@ const contentModuleDefaults = {
1658
1703
  agent: {
1659
1704
  routes: true,
1660
1705
  linkHeaders: true,
1661
- markdownNegotiation: true,
1662
- prerender: true
1706
+ delivery: "static"
1663
1707
  },
1664
1708
  respectPathCase: false
1665
1709
  };
@@ -1,6 +1,6 @@
1
- import { createError, defineEventHandler } from "h3";
1
+ import { createError, defineEventHandler, setResponseStatus } from "h3";
2
2
  import { localeFromAgentPath, resolveMarkdownForPublicRoute } from "../agent-site.js";
3
- import { setAgentMarkdownHeaders } from "../agent-http.js";
3
+ import { addVaryHeader, renderAgentNotFoundMarkdown, setAgentMarkdownHeaders } from "../agent-http.js";
4
4
  import { agentRoutePathFromRawSlug, isUnsafeAgentRoutePath } from "../../../features/agent/agent-paths.js";
5
5
  export default defineEventHandler(async (event) => {
6
6
  const routePath = agentRoutePathFromRawSlug(event.context.params?.slug);
@@ -10,8 +10,12 @@ export default defineEventHandler(async (event) => {
10
10
  const locale = localeFromAgentPath(routePath);
11
11
  const page = await resolveMarkdownForPublicRoute(event, routePath, locale);
12
12
  if (!page) {
13
- throw createError({ statusCode: 404, statusMessage: "Markdown page not found" });
13
+ setResponseStatus(event, 404, "Markdown page not found");
14
+ addVaryHeader(event, "accept");
15
+ setAgentMarkdownHeaders(event, { noindex: true });
16
+ return renderAgentNotFoundMarkdown(routePath);
14
17
  }
18
+ addVaryHeader(event, "accept");
15
19
  setAgentMarkdownHeaders(event, { noindex: true });
16
20
  return page.markdown;
17
21
  });
@@ -7,17 +7,14 @@ import {
7
7
  shouldSkipAgentMarkdownPath
8
8
  } from "../agent-http.js";
9
9
  export default defineNitroPlugin((nitro) => {
10
- nitro.hooks.hook("render:response", (response, { event }) => {
10
+ nitro.hooks.hook("beforeResponse", (event, response) => {
11
11
  const { pathname } = getRequestURL(event);
12
- if (response.statusCode !== 404 || shouldSkipAgentMarkdownPath(pathname) || !acceptsMarkdown(event)) {
12
+ if (event.node.res.statusCode !== 404 || shouldSkipAgentMarkdownPath(pathname) || !acceptsMarkdown(event)) {
13
13
  return;
14
14
  }
15
- response.headers = {
16
- ...response.headers,
17
- "content-type": "text/markdown; charset=utf-8",
18
- "x-robots-tag": "noindex",
19
- "vary": mergeVaryHeader(response.headers?.vary, "accept")
20
- };
15
+ event.node.res.setHeader("content-type", "text/markdown; charset=utf-8");
16
+ event.node.res.setHeader("x-robots-tag", "noindex");
17
+ event.node.res.setHeader("vary", mergeVaryHeader(event.node.res.getHeader("vary"), "accept"));
21
18
  response.body = renderAgentNotFoundMarkdown(pathname);
22
19
  });
23
20
  });
@@ -296,8 +296,17 @@ export interface ContentValidationRouteFacts {
296
296
  export interface ContentAgentRouteOptions {
297
297
  routes?: boolean;
298
298
  linkHeaders?: boolean;
299
- markdownNegotiation?: boolean;
300
- prerender?: boolean;
299
+ /**
300
+ * Select static page delivery or request-time Nitro negotiation.
301
+ *
302
+ * `static` prerenders public pages and keeps raw Markdown as the canonical
303
+ * agent representation. `runtime` lets Nitro crawl pages to retain their
304
+ * data dependencies, then removes the generated HTML so Nitro can negotiate
305
+ * each page request.
306
+ *
307
+ * @default 'static'
308
+ */
309
+ delivery?: 'static' | 'runtime';
301
310
  }
302
311
  export interface ModuleOptions {
303
312
  api: {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "framework": "vue",
3
3
  "name": "@lupinum/ginko-content",
4
- "version": "1.0.0-beta.4",
4
+ "version": "1.0.0-beta.6",
5
5
  "contributions": {
6
6
  "html": {
7
7
  "description-markup": "markdown",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lupinum/ginko-content",
3
- "version": "1.0.0-beta.4",
3
+ "version": "1.0.0-beta.6",
4
4
  "description": "Collection-first content engine for Nuxt",
5
5
  "homepage": "https://ginko-content.lupinum.com",
6
6
  "bugs": {
@@ -172,7 +172,7 @@
172
172
  "nuxt": ">=4.5.1 <5",
173
173
  "pagefind": "^1.5.2",
174
174
  "vitest": ">=4.1.6 <5",
175
- "vue": "^3.5.35"
175
+ "vue": "^3.5.40"
176
176
  },
177
177
  "peerDependenciesMeta": {
178
178
  "beautiful-mermaid": {