next-md-negotiate 0.0.1 → 0.0.2
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/cli.js +152 -0
- package/dist/index.cjs +207 -0
- package/dist/index.d.cts +146 -0
- package/dist/index.d.ts +146 -0
- package/dist/index.js +175 -0
- package/package.json +38 -2
- package/index.js +0 -1
package/dist/cli.js
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// bin/cli.ts
|
|
4
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
5
|
+
import { join } from "path";
|
|
6
|
+
var APP_ROUTE_HANDLER = `import { createMdHandler } from 'next-md-negotiate';
|
|
7
|
+
import registry from '@/md.config';
|
|
8
|
+
|
|
9
|
+
export const GET = createMdHandler(registry);
|
|
10
|
+
`;
|
|
11
|
+
var PAGES_API_HANDLER = `import { createMdApiHandler } from 'next-md-negotiate';
|
|
12
|
+
import registry from '@/md.config';
|
|
13
|
+
|
|
14
|
+
export default createMdApiHandler(registry);
|
|
15
|
+
`;
|
|
16
|
+
var MD_CONFIG = `import { createMdVersion } from 'next-md-negotiate';
|
|
17
|
+
|
|
18
|
+
export default [
|
|
19
|
+
// createMdVersion('/products/[productId]', async ({ productId }) => {
|
|
20
|
+
// return \`# Product \${productId}\`;
|
|
21
|
+
// }),
|
|
22
|
+
];
|
|
23
|
+
`;
|
|
24
|
+
function main() {
|
|
25
|
+
const command = process.argv[2];
|
|
26
|
+
if (command !== "init") {
|
|
27
|
+
console.log("Usage: next-md-negotiate init");
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
const cwd = process.cwd();
|
|
31
|
+
const pkgPath = join(cwd, "package.json");
|
|
32
|
+
if (existsSync(pkgPath)) {
|
|
33
|
+
try {
|
|
34
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
35
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
36
|
+
if (!deps["next"]) {
|
|
37
|
+
console.error('Error: "next" is not listed in package.json dependencies. Is this a Next.js project?');
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
} catch {
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const useSrc = existsSync(join(cwd, "src", "app")) || existsSync(join(cwd, "src", "pages"));
|
|
44
|
+
const hasAppDir = existsSync(join(cwd, useSrc ? "src" : "", "app"));
|
|
45
|
+
const hasPagesDir = existsSync(join(cwd, useSrc ? "src" : "", "pages"));
|
|
46
|
+
if (!hasAppDir && !hasPagesDir) {
|
|
47
|
+
console.error(
|
|
48
|
+
"Error: Could not find app/ or pages/ directory. Make sure you are in a Next.js project root."
|
|
49
|
+
);
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
const configDir = useSrc ? join(cwd, "src") : cwd;
|
|
53
|
+
try {
|
|
54
|
+
if (hasAppDir) {
|
|
55
|
+
const appDir = join(cwd, useSrc ? "src" : "", "app");
|
|
56
|
+
const routeDir = join(appDir, "md-api", "[...path]");
|
|
57
|
+
const routePath = join(routeDir, "route.ts");
|
|
58
|
+
if (existsSync(routePath)) {
|
|
59
|
+
console.log(" Skipped app/md-api/[...path]/route.ts (already exists)");
|
|
60
|
+
} else {
|
|
61
|
+
mkdirSync(routeDir, { recursive: true });
|
|
62
|
+
writeFileSync(routePath, APP_ROUTE_HANDLER);
|
|
63
|
+
console.log(" Created app/md-api/[...path]/route.ts");
|
|
64
|
+
}
|
|
65
|
+
} else {
|
|
66
|
+
const pagesDir = join(cwd, useSrc ? "src" : "", "pages");
|
|
67
|
+
const apiDir = join(pagesDir, "api", "md-api");
|
|
68
|
+
const routePath = join(apiDir, "[...path].ts");
|
|
69
|
+
if (existsSync(routePath)) {
|
|
70
|
+
console.log(" Skipped pages/api/md-api/[...path].ts (already exists)");
|
|
71
|
+
} else {
|
|
72
|
+
mkdirSync(apiDir, { recursive: true });
|
|
73
|
+
writeFileSync(routePath, PAGES_API_HANDLER);
|
|
74
|
+
console.log(" Created pages/api/md-api/[...path].ts");
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
const configPath = join(configDir, "md.config.ts");
|
|
78
|
+
if (existsSync(configPath)) {
|
|
79
|
+
console.log(" Skipped md.config.ts (already exists)");
|
|
80
|
+
} else {
|
|
81
|
+
writeFileSync(configPath, MD_CONFIG);
|
|
82
|
+
console.log(" Created md.config.ts");
|
|
83
|
+
}
|
|
84
|
+
} catch (err) {
|
|
85
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
86
|
+
console.error(`Error: Failed to write files \u2014 ${message}`);
|
|
87
|
+
process.exit(1);
|
|
88
|
+
}
|
|
89
|
+
let nextMajor = null;
|
|
90
|
+
if (existsSync(pkgPath)) {
|
|
91
|
+
try {
|
|
92
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
93
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
94
|
+
const nextVersion = deps["next"];
|
|
95
|
+
if (nextVersion) {
|
|
96
|
+
const match = nextVersion.match(/(\d+)/);
|
|
97
|
+
if (match) nextMajor = parseInt(match[1], 10);
|
|
98
|
+
}
|
|
99
|
+
} catch {
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
console.log("\nNext step \u2014 add content negotiation:\n");
|
|
103
|
+
if (nextMajor !== null && nextMajor >= 16) {
|
|
104
|
+
console.log(` Your project uses Next.js ${nextMajor}, so add a proxy.ts in your project root:
|
|
105
|
+
`);
|
|
106
|
+
console.log(" // proxy.ts");
|
|
107
|
+
console.log(" import { createMarkdownProxy } from 'next-md-negotiate';");
|
|
108
|
+
console.log("");
|
|
109
|
+
console.log(" const md = createMarkdownProxy({");
|
|
110
|
+
console.log(" routes: ['/products/[productId]', '/blog/[slug]'],");
|
|
111
|
+
console.log(" });");
|
|
112
|
+
console.log("");
|
|
113
|
+
console.log(" export function proxy(request: Request) {");
|
|
114
|
+
console.log(" return md(request);");
|
|
115
|
+
console.log(" }");
|
|
116
|
+
} else if (nextMajor !== null && nextMajor >= 14 && hasAppDir) {
|
|
117
|
+
console.log(` Your project uses Next.js ${nextMajor} with App Router, so add a middleware.ts in your project root:
|
|
118
|
+
`);
|
|
119
|
+
console.log(" // middleware.ts");
|
|
120
|
+
console.log(" import { createMarkdownMiddleware } from 'next-md-negotiate';");
|
|
121
|
+
console.log("");
|
|
122
|
+
console.log(" const md = createMarkdownMiddleware({");
|
|
123
|
+
console.log(" routes: ['/products/[productId]', '/blog/[slug]'],");
|
|
124
|
+
console.log(" });");
|
|
125
|
+
console.log("");
|
|
126
|
+
console.log(" export function middleware(request: Request) {");
|
|
127
|
+
console.log(" return md(request);");
|
|
128
|
+
console.log(" }");
|
|
129
|
+
} else if (nextMajor !== null && hasPagesDir && !hasAppDir) {
|
|
130
|
+
console.log(` Your project uses Next.js ${nextMajor} with Pages Router, so add rewrites to next.config.js:
|
|
131
|
+
`);
|
|
132
|
+
console.log(" // next.config.js");
|
|
133
|
+
console.log(" import { createMarkdownRewrites } from 'next-md-negotiate';");
|
|
134
|
+
console.log("");
|
|
135
|
+
console.log(" export default {");
|
|
136
|
+
console.log(" async rewrites() {");
|
|
137
|
+
console.log(" return {");
|
|
138
|
+
console.log(" beforeFiles: createMarkdownRewrites({");
|
|
139
|
+
console.log(" routes: ['/products/[productId]', '/blog/[slug]'],");
|
|
140
|
+
console.log(" }),");
|
|
141
|
+
console.log(" };");
|
|
142
|
+
console.log(" },");
|
|
143
|
+
console.log(" };");
|
|
144
|
+
} else {
|
|
145
|
+
console.log(" Choose the integration method that fits your setup:\n");
|
|
146
|
+
console.log(" Next.js 16+ \u2192 proxy.ts with createMarkdownProxy");
|
|
147
|
+
console.log(" Next.js 14-15 (App Router) \u2192 middleware.ts with createMarkdownMiddleware");
|
|
148
|
+
console.log(" Pages Router \u2192 next.config.js with createMarkdownRewrites");
|
|
149
|
+
}
|
|
150
|
+
console.log("\n Then define your routes in md.config.ts");
|
|
151
|
+
}
|
|
152
|
+
main();
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
createMarkdownMiddleware: () => createMarkdownMiddleware,
|
|
24
|
+
createMarkdownProxy: () => createMarkdownProxy,
|
|
25
|
+
createMarkdownRewrites: () => createMarkdownRewrites,
|
|
26
|
+
createMdApiHandler: () => createMdApiHandler,
|
|
27
|
+
createMdHandler: () => createMdHandler,
|
|
28
|
+
createMdVersion: () => createMdVersion
|
|
29
|
+
});
|
|
30
|
+
module.exports = __toCommonJS(index_exports);
|
|
31
|
+
|
|
32
|
+
// src/validatePattern.ts
|
|
33
|
+
var VALID_PATTERN = /^(\/([a-zA-Z0-9_-]+|\[(\.\.\.)?[a-zA-Z_]+\]))+$|^\/$/;
|
|
34
|
+
function validatePattern(pattern) {
|
|
35
|
+
if (!VALID_PATTERN.test(pattern)) {
|
|
36
|
+
throw new Error(
|
|
37
|
+
`Invalid route pattern: "${pattern}". Patterns must start with / and use [param] or [...param] for dynamic segments.`
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// src/createMdVersion.ts
|
|
43
|
+
function createMdVersion(pattern, handler) {
|
|
44
|
+
validatePattern(pattern);
|
|
45
|
+
return { pattern, handler };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// src/matchPath.ts
|
|
49
|
+
var regexCache = /* @__PURE__ */ new Map();
|
|
50
|
+
function compilePattern(pattern) {
|
|
51
|
+
const cached = regexCache.get(pattern);
|
|
52
|
+
if (cached) return cached;
|
|
53
|
+
const CATCHALL_TOKEN = "\0CATCHALL_";
|
|
54
|
+
const PARAM_TOKEN = "\0PARAM_";
|
|
55
|
+
let tokenized = pattern;
|
|
56
|
+
const catchAllNames = [];
|
|
57
|
+
const paramNames = [];
|
|
58
|
+
tokenized = tokenized.replace(/\[\.\.\.([a-zA-Z_]+)\]/g, (_, name) => {
|
|
59
|
+
catchAllNames.push(name);
|
|
60
|
+
return `${CATCHALL_TOKEN}${catchAllNames.length - 1}\0`;
|
|
61
|
+
});
|
|
62
|
+
tokenized = tokenized.replace(/\[([a-zA-Z_]+)\]/g, (_, name) => {
|
|
63
|
+
paramNames.push(name);
|
|
64
|
+
return `${PARAM_TOKEN}${paramNames.length - 1}\0`;
|
|
65
|
+
});
|
|
66
|
+
let regexStr = tokenized.replace(/[.*+?^${}()|\\]/g, "\\$&");
|
|
67
|
+
catchAllNames.forEach((name, i) => {
|
|
68
|
+
regexStr = regexStr.replace(`${CATCHALL_TOKEN}${i}\0`, `(?<${name}>.+)`);
|
|
69
|
+
});
|
|
70
|
+
paramNames.forEach((name, i) => {
|
|
71
|
+
regexStr = regexStr.replace(`${PARAM_TOKEN}${i}\0`, `(?<${name}>[^/]+)`);
|
|
72
|
+
});
|
|
73
|
+
const regex = new RegExp(`^${regexStr}$`);
|
|
74
|
+
regexCache.set(pattern, regex);
|
|
75
|
+
return regex;
|
|
76
|
+
}
|
|
77
|
+
function matchPath(pattern, path) {
|
|
78
|
+
const regex = compilePattern(pattern);
|
|
79
|
+
const match = path.match(regex);
|
|
80
|
+
if (!match) return null;
|
|
81
|
+
return match.groups ? { ...match.groups } : {};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// src/createMdHandler.ts
|
|
85
|
+
function createMdHandler(registry) {
|
|
86
|
+
return async function GET(_req, { params }) {
|
|
87
|
+
const { path } = await params;
|
|
88
|
+
const incomingPath = "/" + path.join("/");
|
|
89
|
+
for (const route of registry) {
|
|
90
|
+
const match = matchPath(route.pattern, incomingPath);
|
|
91
|
+
if (match) {
|
|
92
|
+
try {
|
|
93
|
+
const markdown = await route.handler(match);
|
|
94
|
+
const headers = new Headers();
|
|
95
|
+
headers.set("Content-Type", "text/markdown; charset=utf-8");
|
|
96
|
+
return new Response(markdown, { status: 200, headers });
|
|
97
|
+
} catch (error) {
|
|
98
|
+
console.error("[next-md-negotiate] Handler error:", error);
|
|
99
|
+
return new Response("Internal Server Error", { status: 500 });
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return new Response("Not Found", { status: 404 });
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// src/createMdApiHandler.ts
|
|
108
|
+
function createMdApiHandler(registry) {
|
|
109
|
+
return async function handler(req, res) {
|
|
110
|
+
const pathParam = req.query.path;
|
|
111
|
+
const pathSegments = Array.isArray(pathParam) ? pathParam : pathParam ? [pathParam] : [];
|
|
112
|
+
const incomingPath = "/" + pathSegments.join("/");
|
|
113
|
+
for (const route of registry) {
|
|
114
|
+
const match = matchPath(route.pattern, incomingPath);
|
|
115
|
+
if (match) {
|
|
116
|
+
try {
|
|
117
|
+
const markdown = await route.handler(match);
|
|
118
|
+
res.setHeader("Content-Type", "text/markdown; charset=utf-8");
|
|
119
|
+
res.status(200).end(markdown);
|
|
120
|
+
return;
|
|
121
|
+
} catch (error) {
|
|
122
|
+
console.error("[next-md-negotiate] Handler error:", error);
|
|
123
|
+
res.status(500).end("Internal Server Error");
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
res.status(404).end("Not Found");
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// src/createMarkdownRewrites.ts
|
|
133
|
+
var MARKDOWN_ACCEPT_REGEX = ".*(text/markdown|application/markdown|text/x-markdown).*";
|
|
134
|
+
function createMarkdownRewrites(options) {
|
|
135
|
+
const prefix = options.internalPrefix ?? "/md-api";
|
|
136
|
+
return options.routes.map((source) => {
|
|
137
|
+
validatePattern(source);
|
|
138
|
+
const rewritePath = source.replace(/\[\.\.\.([a-zA-Z_]+)\]/g, ":$1*").replace(/\[([a-zA-Z_]+)\]/g, ":$1");
|
|
139
|
+
return {
|
|
140
|
+
source: rewritePath,
|
|
141
|
+
has: [
|
|
142
|
+
{
|
|
143
|
+
type: "header",
|
|
144
|
+
key: "accept",
|
|
145
|
+
value: MARKDOWN_ACCEPT_REGEX
|
|
146
|
+
}
|
|
147
|
+
],
|
|
148
|
+
destination: `${prefix}${rewritePath}`
|
|
149
|
+
};
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// src/negotiateRequest.ts
|
|
154
|
+
var MARKDOWN_TYPES = [
|
|
155
|
+
"text/markdown",
|
|
156
|
+
"application/markdown",
|
|
157
|
+
"text/x-markdown"
|
|
158
|
+
];
|
|
159
|
+
function negotiateRequest(request, options) {
|
|
160
|
+
const accept = request.headers.get("accept") ?? "";
|
|
161
|
+
const wantsMarkdown = MARKDOWN_TYPES.some((type) => accept.includes(type));
|
|
162
|
+
if (!wantsMarkdown) return null;
|
|
163
|
+
const url = new URL(request.url);
|
|
164
|
+
const pathname = url.pathname;
|
|
165
|
+
const prefix = options.internalPrefix ?? "/md-api";
|
|
166
|
+
for (const route of options.routes) {
|
|
167
|
+
if (matchPath(route, pathname)) {
|
|
168
|
+
return new URL(`${prefix}${pathname}`, url.origin);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// src/createRewriteHandler.ts
|
|
175
|
+
function createRewriteHandler(options) {
|
|
176
|
+
for (const route of options.routes) {
|
|
177
|
+
validatePattern(route);
|
|
178
|
+
}
|
|
179
|
+
return (request) => {
|
|
180
|
+
const rewriteUrl = negotiateRequest(request, options);
|
|
181
|
+
if (!rewriteUrl) return void 0;
|
|
182
|
+
return new Response(null, {
|
|
183
|
+
headers: {
|
|
184
|
+
"x-middleware-rewrite": rewriteUrl.toString()
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// src/createMarkdownProxy.ts
|
|
191
|
+
function createMarkdownProxy(options) {
|
|
192
|
+
return createRewriteHandler(options);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// src/createMarkdownMiddleware.ts
|
|
196
|
+
function createMarkdownMiddleware(options) {
|
|
197
|
+
return createRewriteHandler(options);
|
|
198
|
+
}
|
|
199
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
200
|
+
0 && (module.exports = {
|
|
201
|
+
createMarkdownMiddleware,
|
|
202
|
+
createMarkdownProxy,
|
|
203
|
+
createMarkdownRewrites,
|
|
204
|
+
createMdApiHandler,
|
|
205
|
+
createMdHandler,
|
|
206
|
+
createMdVersion
|
|
207
|
+
});
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extracts parameter names from a Next.js-style route pattern.
|
|
3
|
+
*
|
|
4
|
+
* '/products/[productId]' → { productId: string }
|
|
5
|
+
* '/blog/[slug]' → { slug: string }
|
|
6
|
+
* '/docs/[section]/[page]' → { section: string; page: string }
|
|
7
|
+
* '/docs/[...slug]' → { slug: string }
|
|
8
|
+
*/
|
|
9
|
+
type ExtractParams<T extends string> = T extends `${string}[...${infer Param}]${infer Rest}` ? {
|
|
10
|
+
[K in Param]: string;
|
|
11
|
+
} & ExtractParams<Rest> : T extends `${string}[${infer Param}]${infer Rest}` ? {
|
|
12
|
+
[K in Param]: string;
|
|
13
|
+
} & ExtractParams<Rest> : {};
|
|
14
|
+
interface MdVersionHandler {
|
|
15
|
+
pattern: string;
|
|
16
|
+
handler: (params: Record<string, string>) => Promise<string>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Defines a markdown version for a route.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* createMdVersion('/products/[productId]', async ({ productId }) => {
|
|
24
|
+
* const product = await getProduct(productId);
|
|
25
|
+
* return `# ${product.name}\n\n${product.description}`;
|
|
26
|
+
* });
|
|
27
|
+
*/
|
|
28
|
+
declare function createMdVersion<T extends string>(pattern: T, handler: (params: ExtractParams<T>) => Promise<string>): MdVersionHandler;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Creates a Next.js route handler for the catch-all `/md-api/[...path]` route.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* // app/md-api/[...path]/route.ts
|
|
35
|
+
* import { createMdHandler } from 'next-md-negotiate';
|
|
36
|
+
* import registry from '@/md.config';
|
|
37
|
+
*
|
|
38
|
+
* export const GET = createMdHandler(registry);
|
|
39
|
+
*/
|
|
40
|
+
declare function createMdHandler(registry: MdVersionHandler[]): (req: Request, ctx: {
|
|
41
|
+
params: Promise<{
|
|
42
|
+
path: string[];
|
|
43
|
+
}>;
|
|
44
|
+
}) => Promise<Response>;
|
|
45
|
+
|
|
46
|
+
interface NextApiRequest {
|
|
47
|
+
query: Record<string, string | string[] | undefined>;
|
|
48
|
+
}
|
|
49
|
+
interface NextApiResponse {
|
|
50
|
+
status(code: number): NextApiResponse;
|
|
51
|
+
setHeader(name: string, value: string): NextApiResponse;
|
|
52
|
+
end(body?: string): void;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Creates a Pages Router API handler for `pages/api/md-api/[...path].ts`.
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* // pages/api/md-api/[...path].ts
|
|
59
|
+
* import { createMdApiHandler } from 'next-md-negotiate';
|
|
60
|
+
* import registry from '@/md.config';
|
|
61
|
+
*
|
|
62
|
+
* export default createMdApiHandler(registry);
|
|
63
|
+
*/
|
|
64
|
+
declare function createMdApiHandler(registry: MdVersionHandler[]): (req: NextApiRequest, res: NextApiResponse) => Promise<void>;
|
|
65
|
+
|
|
66
|
+
interface MarkdownRewriteOptions {
|
|
67
|
+
routes: string[];
|
|
68
|
+
/** Internal route prefix. Defaults to '/md-api'. */
|
|
69
|
+
internalPrefix?: string;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Generates Next.js rewrite rules that redirect markdown requests
|
|
73
|
+
* to the catch-all handler.
|
|
74
|
+
*
|
|
75
|
+
* Translates Next.js [param] syntax to :param for rewrite rules.
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* // next.config.ts
|
|
79
|
+
* import { createMarkdownRewrites } from 'next-md-negotiate';
|
|
80
|
+
*
|
|
81
|
+
* export default {
|
|
82
|
+
* async rewrites() {
|
|
83
|
+
* return {
|
|
84
|
+
* beforeFiles: createMarkdownRewrites({
|
|
85
|
+
* routes: ['/products/[productId]', '/blog/[slug]'],
|
|
86
|
+
* }),
|
|
87
|
+
* };
|
|
88
|
+
* },
|
|
89
|
+
* };
|
|
90
|
+
*/
|
|
91
|
+
declare function createMarkdownRewrites(options: MarkdownRewriteOptions): {
|
|
92
|
+
source: string;
|
|
93
|
+
has: {
|
|
94
|
+
type: "header";
|
|
95
|
+
key: string;
|
|
96
|
+
value: string;
|
|
97
|
+
}[];
|
|
98
|
+
destination: string;
|
|
99
|
+
}[];
|
|
100
|
+
|
|
101
|
+
interface NegotiateOptions {
|
|
102
|
+
routes: string[];
|
|
103
|
+
internalPrefix?: string;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Creates a proxy handler for Next.js 16+ `proxy.ts`.
|
|
108
|
+
*
|
|
109
|
+
* Returns a rewrite response for markdown requests matching
|
|
110
|
+
* configured routes, or `undefined` to pass through.
|
|
111
|
+
*
|
|
112
|
+
* @example
|
|
113
|
+
* // proxy.ts
|
|
114
|
+
* import { createMarkdownProxy } from 'next-md-negotiate';
|
|
115
|
+
*
|
|
116
|
+
* const markdownProxy = createMarkdownProxy({
|
|
117
|
+
* routes: ['/products/[productId]', '/blog/[slug]'],
|
|
118
|
+
* });
|
|
119
|
+
*
|
|
120
|
+
* export function proxy(request: Request) {
|
|
121
|
+
* return markdownProxy(request);
|
|
122
|
+
* }
|
|
123
|
+
*/
|
|
124
|
+
declare function createMarkdownProxy(options: NegotiateOptions): (request: Request) => Response | undefined;
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Creates a middleware handler for Next.js 14-15 `middleware.ts`.
|
|
128
|
+
*
|
|
129
|
+
* Returns a rewrite response for markdown requests matching configured
|
|
130
|
+
* routes, or `undefined` to pass through.
|
|
131
|
+
*
|
|
132
|
+
* @example
|
|
133
|
+
* // middleware.ts
|
|
134
|
+
* import { createMarkdownMiddleware } from 'next-md-negotiate';
|
|
135
|
+
*
|
|
136
|
+
* const markdownMiddleware = createMarkdownMiddleware({
|
|
137
|
+
* routes: ['/products/[productId]', '/blog/[slug]'],
|
|
138
|
+
* });
|
|
139
|
+
*
|
|
140
|
+
* export function middleware(request: Request) {
|
|
141
|
+
* return markdownMiddleware(request);
|
|
142
|
+
* }
|
|
143
|
+
*/
|
|
144
|
+
declare function createMarkdownMiddleware(options: NegotiateOptions): (request: Request) => Response | undefined;
|
|
145
|
+
|
|
146
|
+
export { type ExtractParams, type MarkdownRewriteOptions, type MdVersionHandler, type NegotiateOptions, createMarkdownMiddleware, createMarkdownProxy, createMarkdownRewrites, createMdApiHandler, createMdHandler, createMdVersion };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extracts parameter names from a Next.js-style route pattern.
|
|
3
|
+
*
|
|
4
|
+
* '/products/[productId]' → { productId: string }
|
|
5
|
+
* '/blog/[slug]' → { slug: string }
|
|
6
|
+
* '/docs/[section]/[page]' → { section: string; page: string }
|
|
7
|
+
* '/docs/[...slug]' → { slug: string }
|
|
8
|
+
*/
|
|
9
|
+
type ExtractParams<T extends string> = T extends `${string}[...${infer Param}]${infer Rest}` ? {
|
|
10
|
+
[K in Param]: string;
|
|
11
|
+
} & ExtractParams<Rest> : T extends `${string}[${infer Param}]${infer Rest}` ? {
|
|
12
|
+
[K in Param]: string;
|
|
13
|
+
} & ExtractParams<Rest> : {};
|
|
14
|
+
interface MdVersionHandler {
|
|
15
|
+
pattern: string;
|
|
16
|
+
handler: (params: Record<string, string>) => Promise<string>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Defines a markdown version for a route.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* createMdVersion('/products/[productId]', async ({ productId }) => {
|
|
24
|
+
* const product = await getProduct(productId);
|
|
25
|
+
* return `# ${product.name}\n\n${product.description}`;
|
|
26
|
+
* });
|
|
27
|
+
*/
|
|
28
|
+
declare function createMdVersion<T extends string>(pattern: T, handler: (params: ExtractParams<T>) => Promise<string>): MdVersionHandler;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Creates a Next.js route handler for the catch-all `/md-api/[...path]` route.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* // app/md-api/[...path]/route.ts
|
|
35
|
+
* import { createMdHandler } from 'next-md-negotiate';
|
|
36
|
+
* import registry from '@/md.config';
|
|
37
|
+
*
|
|
38
|
+
* export const GET = createMdHandler(registry);
|
|
39
|
+
*/
|
|
40
|
+
declare function createMdHandler(registry: MdVersionHandler[]): (req: Request, ctx: {
|
|
41
|
+
params: Promise<{
|
|
42
|
+
path: string[];
|
|
43
|
+
}>;
|
|
44
|
+
}) => Promise<Response>;
|
|
45
|
+
|
|
46
|
+
interface NextApiRequest {
|
|
47
|
+
query: Record<string, string | string[] | undefined>;
|
|
48
|
+
}
|
|
49
|
+
interface NextApiResponse {
|
|
50
|
+
status(code: number): NextApiResponse;
|
|
51
|
+
setHeader(name: string, value: string): NextApiResponse;
|
|
52
|
+
end(body?: string): void;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Creates a Pages Router API handler for `pages/api/md-api/[...path].ts`.
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* // pages/api/md-api/[...path].ts
|
|
59
|
+
* import { createMdApiHandler } from 'next-md-negotiate';
|
|
60
|
+
* import registry from '@/md.config';
|
|
61
|
+
*
|
|
62
|
+
* export default createMdApiHandler(registry);
|
|
63
|
+
*/
|
|
64
|
+
declare function createMdApiHandler(registry: MdVersionHandler[]): (req: NextApiRequest, res: NextApiResponse) => Promise<void>;
|
|
65
|
+
|
|
66
|
+
interface MarkdownRewriteOptions {
|
|
67
|
+
routes: string[];
|
|
68
|
+
/** Internal route prefix. Defaults to '/md-api'. */
|
|
69
|
+
internalPrefix?: string;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Generates Next.js rewrite rules that redirect markdown requests
|
|
73
|
+
* to the catch-all handler.
|
|
74
|
+
*
|
|
75
|
+
* Translates Next.js [param] syntax to :param for rewrite rules.
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* // next.config.ts
|
|
79
|
+
* import { createMarkdownRewrites } from 'next-md-negotiate';
|
|
80
|
+
*
|
|
81
|
+
* export default {
|
|
82
|
+
* async rewrites() {
|
|
83
|
+
* return {
|
|
84
|
+
* beforeFiles: createMarkdownRewrites({
|
|
85
|
+
* routes: ['/products/[productId]', '/blog/[slug]'],
|
|
86
|
+
* }),
|
|
87
|
+
* };
|
|
88
|
+
* },
|
|
89
|
+
* };
|
|
90
|
+
*/
|
|
91
|
+
declare function createMarkdownRewrites(options: MarkdownRewriteOptions): {
|
|
92
|
+
source: string;
|
|
93
|
+
has: {
|
|
94
|
+
type: "header";
|
|
95
|
+
key: string;
|
|
96
|
+
value: string;
|
|
97
|
+
}[];
|
|
98
|
+
destination: string;
|
|
99
|
+
}[];
|
|
100
|
+
|
|
101
|
+
interface NegotiateOptions {
|
|
102
|
+
routes: string[];
|
|
103
|
+
internalPrefix?: string;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Creates a proxy handler for Next.js 16+ `proxy.ts`.
|
|
108
|
+
*
|
|
109
|
+
* Returns a rewrite response for markdown requests matching
|
|
110
|
+
* configured routes, or `undefined` to pass through.
|
|
111
|
+
*
|
|
112
|
+
* @example
|
|
113
|
+
* // proxy.ts
|
|
114
|
+
* import { createMarkdownProxy } from 'next-md-negotiate';
|
|
115
|
+
*
|
|
116
|
+
* const markdownProxy = createMarkdownProxy({
|
|
117
|
+
* routes: ['/products/[productId]', '/blog/[slug]'],
|
|
118
|
+
* });
|
|
119
|
+
*
|
|
120
|
+
* export function proxy(request: Request) {
|
|
121
|
+
* return markdownProxy(request);
|
|
122
|
+
* }
|
|
123
|
+
*/
|
|
124
|
+
declare function createMarkdownProxy(options: NegotiateOptions): (request: Request) => Response | undefined;
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Creates a middleware handler for Next.js 14-15 `middleware.ts`.
|
|
128
|
+
*
|
|
129
|
+
* Returns a rewrite response for markdown requests matching configured
|
|
130
|
+
* routes, or `undefined` to pass through.
|
|
131
|
+
*
|
|
132
|
+
* @example
|
|
133
|
+
* // middleware.ts
|
|
134
|
+
* import { createMarkdownMiddleware } from 'next-md-negotiate';
|
|
135
|
+
*
|
|
136
|
+
* const markdownMiddleware = createMarkdownMiddleware({
|
|
137
|
+
* routes: ['/products/[productId]', '/blog/[slug]'],
|
|
138
|
+
* });
|
|
139
|
+
*
|
|
140
|
+
* export function middleware(request: Request) {
|
|
141
|
+
* return markdownMiddleware(request);
|
|
142
|
+
* }
|
|
143
|
+
*/
|
|
144
|
+
declare function createMarkdownMiddleware(options: NegotiateOptions): (request: Request) => Response | undefined;
|
|
145
|
+
|
|
146
|
+
export { type ExtractParams, type MarkdownRewriteOptions, type MdVersionHandler, type NegotiateOptions, createMarkdownMiddleware, createMarkdownProxy, createMarkdownRewrites, createMdApiHandler, createMdHandler, createMdVersion };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
// src/validatePattern.ts
|
|
2
|
+
var VALID_PATTERN = /^(\/([a-zA-Z0-9_-]+|\[(\.\.\.)?[a-zA-Z_]+\]))+$|^\/$/;
|
|
3
|
+
function validatePattern(pattern) {
|
|
4
|
+
if (!VALID_PATTERN.test(pattern)) {
|
|
5
|
+
throw new Error(
|
|
6
|
+
`Invalid route pattern: "${pattern}". Patterns must start with / and use [param] or [...param] for dynamic segments.`
|
|
7
|
+
);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// src/createMdVersion.ts
|
|
12
|
+
function createMdVersion(pattern, handler) {
|
|
13
|
+
validatePattern(pattern);
|
|
14
|
+
return { pattern, handler };
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// src/matchPath.ts
|
|
18
|
+
var regexCache = /* @__PURE__ */ new Map();
|
|
19
|
+
function compilePattern(pattern) {
|
|
20
|
+
const cached = regexCache.get(pattern);
|
|
21
|
+
if (cached) return cached;
|
|
22
|
+
const CATCHALL_TOKEN = "\0CATCHALL_";
|
|
23
|
+
const PARAM_TOKEN = "\0PARAM_";
|
|
24
|
+
let tokenized = pattern;
|
|
25
|
+
const catchAllNames = [];
|
|
26
|
+
const paramNames = [];
|
|
27
|
+
tokenized = tokenized.replace(/\[\.\.\.([a-zA-Z_]+)\]/g, (_, name) => {
|
|
28
|
+
catchAllNames.push(name);
|
|
29
|
+
return `${CATCHALL_TOKEN}${catchAllNames.length - 1}\0`;
|
|
30
|
+
});
|
|
31
|
+
tokenized = tokenized.replace(/\[([a-zA-Z_]+)\]/g, (_, name) => {
|
|
32
|
+
paramNames.push(name);
|
|
33
|
+
return `${PARAM_TOKEN}${paramNames.length - 1}\0`;
|
|
34
|
+
});
|
|
35
|
+
let regexStr = tokenized.replace(/[.*+?^${}()|\\]/g, "\\$&");
|
|
36
|
+
catchAllNames.forEach((name, i) => {
|
|
37
|
+
regexStr = regexStr.replace(`${CATCHALL_TOKEN}${i}\0`, `(?<${name}>.+)`);
|
|
38
|
+
});
|
|
39
|
+
paramNames.forEach((name, i) => {
|
|
40
|
+
regexStr = regexStr.replace(`${PARAM_TOKEN}${i}\0`, `(?<${name}>[^/]+)`);
|
|
41
|
+
});
|
|
42
|
+
const regex = new RegExp(`^${regexStr}$`);
|
|
43
|
+
regexCache.set(pattern, regex);
|
|
44
|
+
return regex;
|
|
45
|
+
}
|
|
46
|
+
function matchPath(pattern, path) {
|
|
47
|
+
const regex = compilePattern(pattern);
|
|
48
|
+
const match = path.match(regex);
|
|
49
|
+
if (!match) return null;
|
|
50
|
+
return match.groups ? { ...match.groups } : {};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// src/createMdHandler.ts
|
|
54
|
+
function createMdHandler(registry) {
|
|
55
|
+
return async function GET(_req, { params }) {
|
|
56
|
+
const { path } = await params;
|
|
57
|
+
const incomingPath = "/" + path.join("/");
|
|
58
|
+
for (const route of registry) {
|
|
59
|
+
const match = matchPath(route.pattern, incomingPath);
|
|
60
|
+
if (match) {
|
|
61
|
+
try {
|
|
62
|
+
const markdown = await route.handler(match);
|
|
63
|
+
const headers = new Headers();
|
|
64
|
+
headers.set("Content-Type", "text/markdown; charset=utf-8");
|
|
65
|
+
return new Response(markdown, { status: 200, headers });
|
|
66
|
+
} catch (error) {
|
|
67
|
+
console.error("[next-md-negotiate] Handler error:", error);
|
|
68
|
+
return new Response("Internal Server Error", { status: 500 });
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return new Response("Not Found", { status: 404 });
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// src/createMdApiHandler.ts
|
|
77
|
+
function createMdApiHandler(registry) {
|
|
78
|
+
return async function handler(req, res) {
|
|
79
|
+
const pathParam = req.query.path;
|
|
80
|
+
const pathSegments = Array.isArray(pathParam) ? pathParam : pathParam ? [pathParam] : [];
|
|
81
|
+
const incomingPath = "/" + pathSegments.join("/");
|
|
82
|
+
for (const route of registry) {
|
|
83
|
+
const match = matchPath(route.pattern, incomingPath);
|
|
84
|
+
if (match) {
|
|
85
|
+
try {
|
|
86
|
+
const markdown = await route.handler(match);
|
|
87
|
+
res.setHeader("Content-Type", "text/markdown; charset=utf-8");
|
|
88
|
+
res.status(200).end(markdown);
|
|
89
|
+
return;
|
|
90
|
+
} catch (error) {
|
|
91
|
+
console.error("[next-md-negotiate] Handler error:", error);
|
|
92
|
+
res.status(500).end("Internal Server Error");
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
res.status(404).end("Not Found");
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// src/createMarkdownRewrites.ts
|
|
102
|
+
var MARKDOWN_ACCEPT_REGEX = ".*(text/markdown|application/markdown|text/x-markdown).*";
|
|
103
|
+
function createMarkdownRewrites(options) {
|
|
104
|
+
const prefix = options.internalPrefix ?? "/md-api";
|
|
105
|
+
return options.routes.map((source) => {
|
|
106
|
+
validatePattern(source);
|
|
107
|
+
const rewritePath = source.replace(/\[\.\.\.([a-zA-Z_]+)\]/g, ":$1*").replace(/\[([a-zA-Z_]+)\]/g, ":$1");
|
|
108
|
+
return {
|
|
109
|
+
source: rewritePath,
|
|
110
|
+
has: [
|
|
111
|
+
{
|
|
112
|
+
type: "header",
|
|
113
|
+
key: "accept",
|
|
114
|
+
value: MARKDOWN_ACCEPT_REGEX
|
|
115
|
+
}
|
|
116
|
+
],
|
|
117
|
+
destination: `${prefix}${rewritePath}`
|
|
118
|
+
};
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// src/negotiateRequest.ts
|
|
123
|
+
var MARKDOWN_TYPES = [
|
|
124
|
+
"text/markdown",
|
|
125
|
+
"application/markdown",
|
|
126
|
+
"text/x-markdown"
|
|
127
|
+
];
|
|
128
|
+
function negotiateRequest(request, options) {
|
|
129
|
+
const accept = request.headers.get("accept") ?? "";
|
|
130
|
+
const wantsMarkdown = MARKDOWN_TYPES.some((type) => accept.includes(type));
|
|
131
|
+
if (!wantsMarkdown) return null;
|
|
132
|
+
const url = new URL(request.url);
|
|
133
|
+
const pathname = url.pathname;
|
|
134
|
+
const prefix = options.internalPrefix ?? "/md-api";
|
|
135
|
+
for (const route of options.routes) {
|
|
136
|
+
if (matchPath(route, pathname)) {
|
|
137
|
+
return new URL(`${prefix}${pathname}`, url.origin);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// src/createRewriteHandler.ts
|
|
144
|
+
function createRewriteHandler(options) {
|
|
145
|
+
for (const route of options.routes) {
|
|
146
|
+
validatePattern(route);
|
|
147
|
+
}
|
|
148
|
+
return (request) => {
|
|
149
|
+
const rewriteUrl = negotiateRequest(request, options);
|
|
150
|
+
if (!rewriteUrl) return void 0;
|
|
151
|
+
return new Response(null, {
|
|
152
|
+
headers: {
|
|
153
|
+
"x-middleware-rewrite": rewriteUrl.toString()
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// src/createMarkdownProxy.ts
|
|
160
|
+
function createMarkdownProxy(options) {
|
|
161
|
+
return createRewriteHandler(options);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// src/createMarkdownMiddleware.ts
|
|
165
|
+
function createMarkdownMiddleware(options) {
|
|
166
|
+
return createRewriteHandler(options);
|
|
167
|
+
}
|
|
168
|
+
export {
|
|
169
|
+
createMarkdownMiddleware,
|
|
170
|
+
createMarkdownProxy,
|
|
171
|
+
createMarkdownRewrites,
|
|
172
|
+
createMdApiHandler,
|
|
173
|
+
createMdHandler,
|
|
174
|
+
createMdVersion
|
|
175
|
+
};
|
package/package.json
CHANGED
|
@@ -1,8 +1,36 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "next-md-negotiate",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.2",
|
|
4
4
|
"description": "Content negotiation for Next.js - serve Markdown to LLMs, HTML to browsers, from a single URL",
|
|
5
|
-
"
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"import": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"default": "./dist/index.js"
|
|
11
|
+
},
|
|
12
|
+
"require": {
|
|
13
|
+
"types": "./dist/index.d.cts",
|
|
14
|
+
"default": "./dist/index.cjs"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"main": "./dist/index.cjs",
|
|
19
|
+
"module": "./dist/index.js",
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"bin": {
|
|
22
|
+
"next-md-negotiate": "./dist/cli.js"
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"dist"
|
|
26
|
+
],
|
|
27
|
+
"scripts": {
|
|
28
|
+
"build": "tsup",
|
|
29
|
+
"dev": "tsup --watch",
|
|
30
|
+
"test": "vitest run",
|
|
31
|
+
"test:watch": "vitest",
|
|
32
|
+
"typecheck": "tsc --noEmit"
|
|
33
|
+
},
|
|
6
34
|
"keywords": [
|
|
7
35
|
"nextjs",
|
|
8
36
|
"markdown",
|
|
@@ -15,5 +43,13 @@
|
|
|
15
43
|
"repository": {
|
|
16
44
|
"type": "git",
|
|
17
45
|
"url": "https://github.com/kasin-it/next-md-negotiate"
|
|
46
|
+
},
|
|
47
|
+
"peerDependencies": {
|
|
48
|
+
"next": ">=14.0.0"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"tsup": "^8.0.0",
|
|
52
|
+
"typescript": "^5.0.0",
|
|
53
|
+
"vitest": "^4.0.18"
|
|
18
54
|
}
|
|
19
55
|
}
|
package/index.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
module.exports = {};
|