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

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.5` 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?
@@ -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.5
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.5
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.5",
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.5";
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")
@@ -1173,7 +1171,7 @@ ${details}` : "")
1173
1171
  }
1174
1172
  }
1175
1173
  const agentRoutes = normalizeAgentRouteOptions(options);
1176
- if (agentRoutes.routes && agentRoutes.prerender && appContentConfig.agent) {
1174
+ if (agentRoutes.routes && appContentConfig.agent) {
1177
1175
  const defaultLocale = resolvedI18n.defaultLocale;
1178
1176
  const locales = resolvedI18n.locales;
1179
1177
  const llmsRoutes = [
@@ -1306,17 +1304,33 @@ const registerContentNitroIntegrationHooks = (nitroConfig, options, contentConte
1306
1304
  };
1307
1305
  nitroConfig.hooks ||= {};
1308
1306
  const usesFilesystemProviderAtRegistration = !contentContext.provider || contentContext.provider === "filesystem";
1309
- if (usesFilesystemProviderAtRegistration) {
1307
+ if (usesFilesystemProviderAtRegistration || options.removePrerenderedHtml) {
1310
1308
  let prerenderingPublicDir;
1311
1309
  appendHook(nitroConfig.hooks, "prerender:init", async (nitro) => {
1312
1310
  prerenderingPublicDir = nitro.options.output.publicDir;
1313
1311
  });
1314
- appendHook(nitroConfig.hooks, "prerender:done", async () => {
1312
+ appendHook(nitroConfig.hooks, "prerender:done", async (result) => {
1315
1313
  if (!prerenderingPublicDir) {
1316
1314
  return;
1317
1315
  }
1318
- const staleArtifactPath = join$1(prerenderingPublicDir, options.cacheRoute);
1319
- await rm(staleArtifactPath, { recursive: true, force: true });
1316
+ const publicDir = prerenderingPublicDir;
1317
+ if (usesFilesystemProviderAtRegistration) {
1318
+ const staleArtifactPath = join$1(publicDir, options.cacheRoute);
1319
+ await rm(staleArtifactPath, { recursive: true, force: true });
1320
+ }
1321
+ if (options.removePrerenderedHtml) {
1322
+ await Promise.all((result.prerenderedRoutes || []).map(async (route) => {
1323
+ if (!route.contentType?.includes("text/html") || !route.fileName) {
1324
+ return;
1325
+ }
1326
+ const artifactPath = resolve$1(publicDir, route.fileName.replace(/^\/+/, ""));
1327
+ const relativeArtifactPath = relative$1(publicDir, artifactPath);
1328
+ if (relativeArtifactPath.startsWith("..") || isAbsolute$1(relativeArtifactPath)) {
1329
+ throw new Error(`[content] refused to remove a prerendered HTML artifact outside publicDir: ${route.fileName}`);
1330
+ }
1331
+ await rm(artifactPath, { recursive: true, force: true });
1332
+ }));
1333
+ }
1320
1334
  });
1321
1335
  }
1322
1336
  if (contentContext.sitemap && contentContext.sitemap.assert?.enabled) {
@@ -1369,6 +1383,7 @@ const registerContentNitroConfig = ({
1369
1383
  }) => {
1370
1384
  hookNuxtBoundary$1(nuxt, "nitro:config", (nitroConfig) => {
1371
1385
  const searchRuntime = getSearchRuntime();
1386
+ const agentRoutes = normalizeAgentRouteOptions(options);
1372
1387
  nitroConfig.prerender = nitroConfig.prerender || {};
1373
1388
  nitroConfig.prerender.routes = nitroConfig.prerender.routes || [];
1374
1389
  const usesFilesystemProvider = !contentContext.provider || contentContext.provider === "filesystem";
@@ -1432,6 +1447,7 @@ const registerContentNitroConfig = ({
1432
1447
  }
1433
1448
  registerContentNitroIntegrationHooks(nitroConfig, {
1434
1449
  cacheRoute,
1450
+ removePrerenderedHtml: agentRoutes.delivery === "runtime" && !("_generate" in nuxt.options && nuxt.options._generate === true),
1435
1451
  sitemapPrerenderRoutes: () => contentContext.sitemap === false ? [] : resolveNuxtSitemapPrerenderRoutes(nuxt),
1436
1452
  resolveContentContext: () => {
1437
1453
  const resolved = getResolvedContentContext();
@@ -1441,7 +1457,6 @@ const registerContentNitroConfig = ({
1441
1457
  sitemap: contentContext.sitemap,
1442
1458
  provider: contentContext.provider
1443
1459
  });
1444
- const agentRoutes = normalizeAgentRouteOptions(options);
1445
1460
  if (agentRoutes.routes && appContentConfig.agent) {
1446
1461
  nitroConfig.plugins ||= [];
1447
1462
  const agentErrorsPlugin = resolveRuntimeModule("server/plugins/agent-errors.js");
@@ -1449,7 +1464,7 @@ const registerContentNitroConfig = ({
1449
1464
  nitroConfig.plugins.push(agentErrorsPlugin);
1450
1465
  }
1451
1466
  }
1452
- if (agentRoutes.routes && agentRoutes.prerender && appContentConfig.agent) {
1467
+ if (agentRoutes.routes && appContentConfig.agent) {
1453
1468
  nitroConfig.prerender.routes.push("/llms.txt", "/llms-full.txt");
1454
1469
  for (const locale of resolvedI18n.locales || []) {
1455
1470
  if (locale && locale !== resolvedI18n.defaultLocale) {
@@ -1658,8 +1673,7 @@ const contentModuleDefaults = {
1658
1673
  agent: {
1659
1674
  routes: true,
1660
1675
  linkHeaders: true,
1661
- markdownNegotiation: true,
1662
- prerender: true
1676
+ delivery: "static"
1663
1677
  },
1664
1678
  respectPathCase: false
1665
1679
  };
@@ -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.5",
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.5",
4
4
  "description": "Collection-first content engine for Nuxt",
5
5
  "homepage": "https://ginko-content.lupinum.com",
6
6
  "bugs": {