@farming-labs/next 0.1.77 → 0.1.79

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.
@@ -438,7 +438,7 @@ function createNextChangelogIndexMetadata(config) {
438
438
  return createPageMetadata(config, {
439
439
  title: changelog.title,
440
440
  description: changelog.description
441
- });
441
+ }, void 0, getListingUrl(config));
442
442
  }
443
443
  function createNextChangelogEntryMetadata(config, entries) {
444
444
  return async function generateMetadata(props) {
@@ -449,12 +449,12 @@ function createNextChangelogEntryMetadata(config, entries) {
449
449
  return createPageMetadata(config, {
450
450
  title: changelog.title,
451
451
  description: changelog.description
452
- });
452
+ }, void 0, getListingUrl(config));
453
453
  }
454
454
  return createPageMetadata(config, {
455
455
  title: entry.title,
456
456
  description: entry.description
457
- });
457
+ }, void 0, `${getListingUrl(config)}/${entry.slug}`);
458
458
  };
459
459
  }
460
460
 
package/dist/config.mjs CHANGED
@@ -92,6 +92,44 @@ export const { GET, POST } = createDocsAPI({
92
92
  ai: docsConfig.ai,
93
93
  });
94
94
 
95
+ export const revalidate = false;
96
+ `;
97
+ const DOCS_MARKDOWN_ROUTE_TEMPLATE = `\
98
+ ${GENERATED_BANNER}
99
+ import docsConfig from "@/docs.config";
100
+ import { createDocsAPI } from "@farming-labs/next/api";
101
+
102
+ const docsApi = createDocsAPI({
103
+ entry: docsConfig.entry,
104
+ contentDir: docsConfig.contentDir,
105
+ i18n: docsConfig.i18n,
106
+ changelog: docsConfig.changelog,
107
+ feedback: docsConfig.feedback,
108
+ mcp: docsConfig.mcp,
109
+ sitemap: docsConfig.sitemap,
110
+ search: docsConfig.search,
111
+ analytics: docsConfig.analytics,
112
+ observability: docsConfig.observability,
113
+ ai: docsConfig.ai,
114
+ });
115
+
116
+ type MarkdownRouteContext = {
117
+ params?: Promise<{ slug?: string[] }> | { slug?: string[] };
118
+ };
119
+
120
+ export async function GET(request: Request, context: MarkdownRouteContext = {}) {
121
+ const params = context.params ? await context.params : undefined;
122
+ const slug = params?.slug?.join("/") ?? "";
123
+ const url = new URL(request.url);
124
+
125
+ url.pathname = "/api/docs";
126
+ url.search = "";
127
+ url.searchParams.set("format", "markdown");
128
+ if (slug) url.searchParams.set("path", slug);
129
+
130
+ return docsApi.GET(new Request(url.toString(), request));
131
+ }
132
+
95
133
  export const revalidate = false;
96
134
  `;
97
135
  const DOCS_MCP_ROUTE_TEMPLATE = `\
@@ -231,6 +269,7 @@ function createDocsWorkspaceAliases() {
231
269
  "@farming-labs/next/mdx-plugins/rehype-toc": "./packages/next/src/mdx-plugins/rehype-toc.ts",
232
270
  "@farming-labs/next/mdx-plugins/remark-heading": "./packages/next/src/mdx-plugins/remark-heading.ts",
233
271
  "@farming-labs/next/mdx-plugins/remark-og": "./packages/next/src/mdx-plugins/remark-og.ts",
272
+ "@farming-labs/next/mdx-plugins/remark-markdown-alternate": "./packages/next/src/mdx-plugins/remark-markdown-alternate.ts",
234
273
  "@farming-labs/theme": "./packages/fumadocs/src/index.ts",
235
274
  "@farming-labs/theme/api": "./packages/fumadocs/src/docs-api.ts",
236
275
  "@farming-labs/theme/client-hooks": "./packages/fumadocs/src/docs-client-hooks.tsx",
@@ -815,6 +854,10 @@ function buildDocsMarkdownRewrites(entry) {
815
854
  key: "accept",
816
855
  value: MARKDOWN_ACCEPT_HEADER_VALUE
817
856
  };
857
+ const markdownSignatureAgentHeader = {
858
+ type: "header",
859
+ key: "signature-agent"
860
+ };
818
861
  return [
819
862
  {
820
863
  source: `/${normalizedEntry}.md`,
@@ -833,6 +876,16 @@ function buildDocsMarkdownRewrites(entry) {
833
876
  source: `/${normalizedEntry}/:slug*`,
834
877
  has: [markdownAcceptHeader],
835
878
  destination: "/api/docs?format=markdown&path=:slug*"
879
+ },
880
+ {
881
+ source: `/${normalizedEntry}`,
882
+ has: [markdownSignatureAgentHeader],
883
+ destination: "/api/docs/markdown"
884
+ },
885
+ {
886
+ source: `/${normalizedEntry}/:slug*`,
887
+ has: [markdownSignatureAgentHeader],
888
+ destination: "/api/docs/markdown/:slug*"
836
889
  }
837
890
  ];
838
891
  }
@@ -920,7 +973,12 @@ function dedupeRewrites(rewrites) {
920
973
  const seen = /* @__PURE__ */ new Set();
921
974
  const result = [];
922
975
  for (const rewrite of rewrites) {
923
- const key = `${rewrite.source}=>${rewrite.destination}`;
976
+ const key = JSON.stringify({
977
+ source: rewrite.source,
978
+ destination: rewrite.destination,
979
+ has: rewrite.has ?? [],
980
+ missing: rewrite.missing ?? []
981
+ });
924
982
  if (seen.has(key)) continue;
925
983
  seen.add(key);
926
984
  result.push(rewrite);
@@ -1073,6 +1131,12 @@ function withDocs(nextConfig = {}) {
1073
1131
  mkdirSync(docsApiRouteDir, { recursive: true });
1074
1132
  writeFileSync(join(docsApiRouteDir, "route.ts"), DOCS_API_ROUTE_TEMPLATE);
1075
1133
  }
1134
+ const docsMarkdownRouteDir = join(root, appDir, "api", "docs", "markdown", "[[...slug]]");
1135
+ const docsMarkdownRoutePath = join(docsMarkdownRouteDir, "route.ts");
1136
+ if (!isStaticExport && (!hasFile(docsMarkdownRouteDir, "route") || isManagedGeneratedFile(docsMarkdownRoutePath))) {
1137
+ mkdirSync(docsMarkdownRouteDir, { recursive: true });
1138
+ writeFileSync(docsMarkdownRoutePath, DOCS_MARKDOWN_ROUTE_TEMPLATE);
1139
+ }
1076
1140
  const mcp = readMcpConfig(root);
1077
1141
  const sitemap = readSitemapConfig(root);
1078
1142
  const docsMcpRouteDir = join(root, appDir, "api", "docs", "mcp");
@@ -1149,6 +1213,12 @@ function withDocs(nextConfig = {}) {
1149
1213
  const ogEndpoint = readOgEndpoint(root);
1150
1214
  const remarkPlugins = ["remark-gfm", "remark-frontmatter"];
1151
1215
  if (ogEndpoint) remarkPlugins.push(["@farming-labs/next/mdx-plugins/remark-og", { endpoint: ogEndpoint }]);
1216
+ remarkPlugins.push(["@farming-labs/next/mdx-plugins/remark-markdown-alternate", {
1217
+ entry,
1218
+ appDir,
1219
+ contentDir: docsContentDir,
1220
+ enabled: !isStaticExport
1221
+ }]);
1152
1222
  remarkPlugins.push(["remark-mdx-frontmatter", { name: "metadata" }], "@farming-labs/next/mdx-plugins/remark-heading");
1153
1223
  const withMDX = createMDX({
1154
1224
  extension: /\.mdx?$/,
@@ -1206,6 +1276,7 @@ function withDocs(nextConfig = {}) {
1206
1276
  "@farming-labs/next/changelog": join(workspaceRoot, "packages", "next", "dist", "changelog.mjs"),
1207
1277
  "@farming-labs/next/client-callbacks": join(workspaceRoot, "packages", "next", "dist", "client-callbacks.mjs"),
1208
1278
  "@farming-labs/next/layout": join(workspaceRoot, "packages", "next", "dist", "layout.mjs"),
1279
+ "@farming-labs/next/mdx-plugins/remark-markdown-alternate": join(workspaceRoot, "packages", "next", "dist", "mdx-plugins", "remark-markdown-alternate.mjs"),
1209
1280
  "@farming-labs/theme$": join(workspaceRoot, "packages", "fumadocs", "dist", "index.mjs"),
1210
1281
  "@farming-labs/theme/api": join(workspaceRoot, "packages", "fumadocs", "dist", "docs-api.mjs")
1211
1282
  });
@@ -1248,6 +1319,18 @@ function withDocs(nextConfig = {}) {
1248
1319
  skillTraceFile,
1249
1320
  sitemapManifestTraceFile
1250
1321
  ])],
1322
+ "/api/docs/markdown": [...new Set([
1323
+ ...existingTracingIncludes["/api/docs/markdown"] ?? [],
1324
+ docsTraceGlob,
1325
+ skillTraceFile,
1326
+ sitemapManifestTraceFile
1327
+ ])],
1328
+ "/api/docs/markdown/:path*": [...new Set([
1329
+ ...existingTracingIncludes["/api/docs/markdown/:path*"] ?? [],
1330
+ docsTraceGlob,
1331
+ skillTraceFile,
1332
+ sitemapManifestTraceFile
1333
+ ])],
1251
1334
  [DEFAULT_MCP_ROUTE]: [...new Set([...existingTracingIncludes[DEFAULT_MCP_ROUTE] ?? [], docsTraceGlob])]
1252
1335
  };
1253
1336
  return withMDX(nextConfig);
@@ -0,0 +1,20 @@
1
+ //#region src/mdx-plugins/remark-markdown-alternate.d.ts
2
+ interface RemarkMarkdownAlternateOptions {
3
+ entry?: string;
4
+ appDir?: string;
5
+ contentDir?: string;
6
+ enabled?: boolean;
7
+ }
8
+ interface MarkdownNode {
9
+ type: string;
10
+ value?: string;
11
+ }
12
+ interface VFileLike {
13
+ path?: string;
14
+ history?: string[];
15
+ }
16
+ declare function remarkMarkdownAlternate(options?: RemarkMarkdownAlternateOptions): (tree: {
17
+ children: MarkdownNode[];
18
+ }, file?: VFileLike) => void;
19
+ //#endregion
20
+ export { remarkMarkdownAlternate as default };
@@ -0,0 +1,68 @@
1
+ import { toDocsMarkdownUrl } from "@farming-labs/docs";
2
+
3
+ //#region src/mdx-plugins/remark-markdown-alternate.ts
4
+ function normalizePath(value) {
5
+ return value.replace(/\\/g, "/").replace(/\/+/g, "/");
6
+ }
7
+ function normalizeSegment(value, fallback) {
8
+ return (value ?? fallback).replace(/^\/+|\/+$/g, "") || fallback;
9
+ }
10
+ function escapeYamlString(value) {
11
+ return value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
12
+ }
13
+ function routeFromSourcePath(filePath, options) {
14
+ const normalized = `/${normalizePath(filePath).replace(/^\/+/, "")}`;
15
+ if (!/\/page\.mdx?$/.test(normalized)) return null;
16
+ const entry = normalizeSegment(options.entry, "docs");
17
+ const candidates = [
18
+ options.contentDir,
19
+ options.appDir ? `${options.appDir}/${entry}` : void 0,
20
+ `src/app/${entry}`,
21
+ `app/${entry}`
22
+ ].filter((candidate) => typeof candidate === "string" && candidate.length > 0).map((candidate) => normalizePath(candidate).replace(/^\/+|\/+$/g, ""));
23
+ for (const candidate of candidates) {
24
+ const marker = `/${candidate}/`;
25
+ const markerIndex = normalized.lastIndexOf(marker);
26
+ if (markerIndex === -1) continue;
27
+ const relativePath = normalized.slice(markerIndex + marker.length);
28
+ if (!relativePath.endsWith("page.mdx") && !relativePath.endsWith("page.md")) continue;
29
+ const slug = relativePath.replace(/\/?page\.mdx?$/, "").replace(/^\/+|\/+$/g, "");
30
+ return slug ? `/${entry}/${slug}` : `/${entry}`;
31
+ }
32
+ return null;
33
+ }
34
+ function getFilePath(file) {
35
+ return file?.path ?? file?.history?.[0];
36
+ }
37
+ function hasAlternates(yaml) {
38
+ return /^\s*alternates\s*:/m.test(yaml);
39
+ }
40
+ function alternateYaml(url) {
41
+ return [
42
+ "alternates:",
43
+ " types:",
44
+ ` text/markdown: "${escapeYamlString(toDocsMarkdownUrl(url))}"`
45
+ ].join("\n");
46
+ }
47
+ function remarkMarkdownAlternate(options = {}) {
48
+ return (tree, file) => {
49
+ if (options.enabled === false) return;
50
+ const filePath = getFilePath(file);
51
+ if (!filePath) return;
52
+ const route = routeFromSourcePath(filePath, options);
53
+ if (!route) return;
54
+ const yamlNode = tree.children.find((node) => node.type === "yaml");
55
+ if (yamlNode?.value) {
56
+ if (hasAlternates(yamlNode.value)) return;
57
+ yamlNode.value += `\n${alternateYaml(route)}`;
58
+ return;
59
+ }
60
+ tree.children.unshift({
61
+ type: "yaml",
62
+ value: alternateYaml(route)
63
+ });
64
+ };
65
+ }
66
+
67
+ //#endregion
68
+ export { remarkMarkdownAlternate as default };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@farming-labs/next",
3
- "version": "0.1.77",
3
+ "version": "0.1.79",
4
4
  "description": "Next.js adapter for @farming-labs/docs — MDX config wrapper",
5
5
  "keywords": [
6
6
  "docs",
@@ -73,6 +73,11 @@
73
73
  "types": "./dist/mdx-plugins/remark-og.d.mts",
74
74
  "import": "./dist/mdx-plugins/remark-og.mjs",
75
75
  "default": "./dist/mdx-plugins/remark-og.mjs"
76
+ },
77
+ "./mdx-plugins/remark-markdown-alternate": {
78
+ "types": "./dist/mdx-plugins/remark-markdown-alternate.d.mts",
79
+ "import": "./dist/mdx-plugins/remark-markdown-alternate.mjs",
80
+ "default": "./dist/mdx-plugins/remark-markdown-alternate.mjs"
76
81
  }
77
82
  },
78
83
  "dependencies": {
@@ -95,8 +100,8 @@
95
100
  "tsdown": "^0.20.3",
96
101
  "typescript": "^5.9.3",
97
102
  "vitest": "^3.2.4",
98
- "@farming-labs/docs": "0.1.77",
99
- "@farming-labs/theme": "0.1.77"
103
+ "@farming-labs/docs": "0.1.79",
104
+ "@farming-labs/theme": "0.1.79"
100
105
  },
101
106
  "peerDependencies": {
102
107
  "@farming-labs/docs": ">=0.0.1",