@netlify/plugin-nextjs 5.0.0-beta.0 → 5.0.0-beta.1
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/build/content/server.js +1 -1
- package/dist/build/content/static.js +1 -1
- package/dist/build/functions/edge.js +2 -2
- package/dist/build/functions/server.js +3 -3
- package/dist/build/plugin-context.js +1 -1
- package/dist/esm-chunks/{chunk-ZBX3SNQG.js → chunk-5N2CWXSJ.js} +5 -2
- package/dist/esm-chunks/{chunk-G2VRYWGL.js → chunk-A22224GM.js} +16 -10
- package/dist/esm-chunks/{chunk-PEBUKFKH.js → chunk-NOX2JUQZ.js} +2 -2
- package/dist/esm-chunks/{chunk-XD5TSLZO.js → chunk-UXLNY5XK.js} +9 -8
- package/dist/esm-chunks/{chunk-HPGTYMVD.js → chunk-VP3PT3VV.js} +6 -0
- package/dist/esm-chunks/chunk-YZXA5QBC.js +60 -0
- package/dist/esm-chunks/{chunk-62KDS27E.js → chunk-Z7ZMLVTM.js} +3 -2
- package/dist/index.js +9 -9
- package/dist/run/handlers/cache.cjs +3 -0
- package/dist/run/handlers/server.js +12 -4
- package/dist/run/headers.js +1 -1
- package/dist/run/next.cjs +3 -0
- package/dist/run/systemlog.js +15 -0
- package/edge-runtime/lib/routing.ts +451 -0
- package/edge-runtime/matchers.json +1 -0
- package/edge-runtime/middleware.ts +17 -4
- package/edge-runtime/shim/index.js +3 -0
- package/package.json +1 -1
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
copyNextDependencies,
|
|
9
9
|
copyNextServerCode,
|
|
10
10
|
writeTagsManifest
|
|
11
|
-
} from "../../esm-chunks/chunk-
|
|
11
|
+
} from "../../esm-chunks/chunk-A22224GM.js";
|
|
12
12
|
import "../../esm-chunks/chunk-AVWFCGVE.js";
|
|
13
13
|
import "../../esm-chunks/chunk-RSKIKBZH.js";
|
|
14
14
|
export {
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
copyStaticContent,
|
|
10
10
|
publishStaticDir,
|
|
11
11
|
unpublishStaticDir
|
|
12
|
-
} from "../../esm-chunks/chunk-
|
|
12
|
+
} from "../../esm-chunks/chunk-Z7ZMLVTM.js";
|
|
13
13
|
import "../../esm-chunks/chunk-AVWFCGVE.js";
|
|
14
14
|
import "../../esm-chunks/chunk-RSKIKBZH.js";
|
|
15
15
|
export {
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
|
|
7
7
|
import {
|
|
8
8
|
createEdgeHandlers
|
|
9
|
-
} from "../../esm-chunks/chunk-
|
|
10
|
-
import "../../esm-chunks/chunk-
|
|
9
|
+
} from "../../esm-chunks/chunk-UXLNY5XK.js";
|
|
10
|
+
import "../../esm-chunks/chunk-VP3PT3VV.js";
|
|
11
11
|
import "../../esm-chunks/chunk-RSKIKBZH.js";
|
|
12
12
|
export {
|
|
13
13
|
createEdgeHandlers
|
|
@@ -6,10 +6,10 @@
|
|
|
6
6
|
|
|
7
7
|
import {
|
|
8
8
|
createServerHandler
|
|
9
|
-
} from "../../esm-chunks/chunk-
|
|
10
|
-
import "../../esm-chunks/chunk-
|
|
9
|
+
} from "../../esm-chunks/chunk-NOX2JUQZ.js";
|
|
10
|
+
import "../../esm-chunks/chunk-A22224GM.js";
|
|
11
11
|
import "../../esm-chunks/chunk-AVWFCGVE.js";
|
|
12
|
-
import "../../esm-chunks/chunk-
|
|
12
|
+
import "../../esm-chunks/chunk-VP3PT3VV.js";
|
|
13
13
|
import "../../esm-chunks/chunk-RSKIKBZH.js";
|
|
14
14
|
export {
|
|
15
15
|
createServerHandler
|
|
@@ -382,6 +382,9 @@ var Store = class _Store {
|
|
|
382
382
|
};
|
|
383
383
|
}
|
|
384
384
|
static validateKey(key) {
|
|
385
|
+
if (key === "") {
|
|
386
|
+
throw new Error("Blob key must not be empty.");
|
|
387
|
+
}
|
|
385
388
|
if (key.startsWith("/") || key.startsWith("%2F")) {
|
|
386
389
|
throw new Error("Blob key must not start with forward slash (/).");
|
|
387
390
|
}
|
|
@@ -532,9 +535,9 @@ var adjustDateHeader = async (headers, request) => {
|
|
|
532
535
|
headers.set("x-nextjs-date", headers.get("date") ?? lastModifiedDate.toUTCString());
|
|
533
536
|
headers.set("date", lastModifiedDate.toUTCString());
|
|
534
537
|
};
|
|
535
|
-
var setCacheControlHeaders = (headers) => {
|
|
538
|
+
var setCacheControlHeaders = (headers, request) => {
|
|
536
539
|
const cacheControl = headers.get("cache-control");
|
|
537
|
-
if (cacheControl !== null && !headers.has("cdn-cache-control") && !headers.has("netlify-cdn-cache-control")) {
|
|
540
|
+
if (cacheControl !== null && ["GET", "HEAD"].includes(request.method) && !headers.has("cdn-cache-control") && !headers.has("netlify-cdn-cache-control")) {
|
|
538
541
|
const privateCacheControl = omitHeaderValues(cacheControl, [
|
|
539
542
|
"s-maxage",
|
|
540
543
|
"stale-while-revalidate"
|
|
@@ -25,13 +25,17 @@ var copyNextServerCode = async (ctx) => {
|
|
|
25
25
|
});
|
|
26
26
|
await Promise.all(
|
|
27
27
|
paths.map(async (path) => {
|
|
28
|
+
const srcPath = join(srcDir, path);
|
|
28
29
|
const destPath = join(destDir, path);
|
|
29
30
|
if (path === "server/middleware-manifest.json") {
|
|
30
|
-
|
|
31
|
-
|
|
31
|
+
try {
|
|
32
|
+
await replaceMiddlewareManifest(srcPath, destPath);
|
|
33
|
+
} catch (error) {
|
|
34
|
+
throw new Error("Could not patch middleware manifest file", { cause: error });
|
|
35
|
+
}
|
|
32
36
|
return;
|
|
33
37
|
}
|
|
34
|
-
await cp(
|
|
38
|
+
await cp(srcPath, destPath, { recursive: true });
|
|
35
39
|
})
|
|
36
40
|
);
|
|
37
41
|
};
|
|
@@ -90,14 +94,16 @@ var writeTagsManifest = async (ctx) => {
|
|
|
90
94
|
"utf-8"
|
|
91
95
|
);
|
|
92
96
|
};
|
|
93
|
-
var
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
97
|
+
var replaceMiddlewareManifest = async (sourcePath, destPath) => {
|
|
98
|
+
await mkdir(dirname(destPath), { recursive: true });
|
|
99
|
+
const data = await readFile(sourcePath, "utf8");
|
|
100
|
+
const manifest = JSON.parse(data);
|
|
101
|
+
const newManifest = {
|
|
102
|
+
...manifest,
|
|
103
|
+
middleware: {}
|
|
99
104
|
};
|
|
100
|
-
|
|
105
|
+
const newData = JSON.stringify(newManifest);
|
|
106
|
+
await writeFile(destPath, newData);
|
|
101
107
|
};
|
|
102
108
|
|
|
103
109
|
export {
|
|
@@ -8,13 +8,13 @@ import {
|
|
|
8
8
|
copyNextDependencies,
|
|
9
9
|
copyNextServerCode,
|
|
10
10
|
writeTagsManifest
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-A22224GM.js";
|
|
12
12
|
import {
|
|
13
13
|
require_out
|
|
14
14
|
} from "./chunk-AVWFCGVE.js";
|
|
15
15
|
import {
|
|
16
16
|
SERVER_HANDLER_NAME
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-VP3PT3VV.js";
|
|
18
18
|
import {
|
|
19
19
|
__toESM
|
|
20
20
|
} from "./chunk-RSKIKBZH.js";
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
import {
|
|
8
8
|
EDGE_HANDLER_NAME
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-VP3PT3VV.js";
|
|
10
10
|
|
|
11
11
|
// src/build/functions/edge.ts
|
|
12
12
|
import { cp, mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
@@ -15,15 +15,16 @@ var writeEdgeManifest = async (ctx, manifest) => {
|
|
|
15
15
|
await mkdir(ctx.edgeFunctionsDir, { recursive: true });
|
|
16
16
|
await writeFile(join(ctx.edgeFunctionsDir, "manifest.json"), JSON.stringify(manifest, null, 2));
|
|
17
17
|
};
|
|
18
|
-
var writeHandlerFile = async (ctx, { name }) => {
|
|
18
|
+
var writeHandlerFile = async (ctx, { matchers, name }) => {
|
|
19
19
|
const handlerName = getHandlerName({ name });
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
);
|
|
20
|
+
const handlerDirectory = join(ctx.edgeFunctionsDir, handlerName);
|
|
21
|
+
const handlerRuntimeDirectory = join(handlerDirectory, "edge-runtime");
|
|
22
|
+
await cp(join(ctx.pluginDir, "edge-runtime"), handlerRuntimeDirectory, {
|
|
23
|
+
recursive: true
|
|
24
|
+
});
|
|
25
|
+
await writeFile(join(handlerRuntimeDirectory, "matchers.json"), JSON.stringify(matchers));
|
|
25
26
|
await writeFile(
|
|
26
|
-
join(
|
|
27
|
+
join(handlerDirectory, `${handlerName}.js`),
|
|
27
28
|
`
|
|
28
29
|
import {handleMiddleware} from './edge-runtime/middleware.ts';
|
|
29
30
|
import handler from './server/${name}.js';
|
|
@@ -87,6 +87,12 @@ var PluginContext = class {
|
|
|
87
87
|
await readFile(join(this.publishDir, "server/middleware-manifest.json"), "utf-8")
|
|
88
88
|
);
|
|
89
89
|
}
|
|
90
|
+
/**
|
|
91
|
+
* Get Next.js routes manifest from the build output
|
|
92
|
+
*/
|
|
93
|
+
async getRoutesManifest() {
|
|
94
|
+
return JSON.parse(await readFile(join(this.publishDir, "routes-manifest.json"), "utf-8"));
|
|
95
|
+
}
|
|
90
96
|
/**
|
|
91
97
|
* Write a cache entry to the blob upload directory using
|
|
92
98
|
* base64 keys to avoid collisions with directories
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
|
|
2
|
+
const require = await (async () => {
|
|
3
|
+
const { createRequire } = await import("node:module");
|
|
4
|
+
return createRequire(import.meta.url);
|
|
5
|
+
})();
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
// src/run/systemlog.ts
|
|
9
|
+
var systemLogTag = "__nfSystemLog";
|
|
10
|
+
var serializeError = (error) => {
|
|
11
|
+
const cause = error?.cause instanceof Error ? serializeError(error.cause) : error.cause;
|
|
12
|
+
return {
|
|
13
|
+
error: error.message,
|
|
14
|
+
error_cause: cause,
|
|
15
|
+
error_stack: error.stack
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
var StructuredLogger = class _StructuredLogger {
|
|
19
|
+
fields;
|
|
20
|
+
message;
|
|
21
|
+
constructor(message, fields) {
|
|
22
|
+
this.fields = fields ?? {};
|
|
23
|
+
this.message = message ?? "";
|
|
24
|
+
}
|
|
25
|
+
// TODO: add sampling
|
|
26
|
+
doLog(logger2, message) {
|
|
27
|
+
logger2(systemLogTag, JSON.stringify({ msg: message, fields: this.fields }));
|
|
28
|
+
}
|
|
29
|
+
log(message) {
|
|
30
|
+
this.doLog(console.log, message);
|
|
31
|
+
}
|
|
32
|
+
info(message) {
|
|
33
|
+
this.doLog(console.info, message);
|
|
34
|
+
}
|
|
35
|
+
debug(message) {
|
|
36
|
+
this.doLog(console.debug, message);
|
|
37
|
+
}
|
|
38
|
+
warn(message) {
|
|
39
|
+
this.doLog(console.warn, message);
|
|
40
|
+
}
|
|
41
|
+
error(message) {
|
|
42
|
+
this.doLog(console.error, message);
|
|
43
|
+
}
|
|
44
|
+
withError(error) {
|
|
45
|
+
const fields = error instanceof Error ? serializeError(error) : { error };
|
|
46
|
+
return this.withFields(fields);
|
|
47
|
+
}
|
|
48
|
+
withFields(fields) {
|
|
49
|
+
return new _StructuredLogger(this.message, {
|
|
50
|
+
...this.fields,
|
|
51
|
+
...fields
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
var logger = new StructuredLogger();
|
|
56
|
+
|
|
57
|
+
export {
|
|
58
|
+
StructuredLogger,
|
|
59
|
+
logger
|
|
60
|
+
};
|
|
@@ -38,11 +38,12 @@ var copyStaticContent = async (ctx) => {
|
|
|
38
38
|
var copyStaticAssets = async (ctx) => {
|
|
39
39
|
try {
|
|
40
40
|
await rm(ctx.staticDir, { recursive: true, force: true });
|
|
41
|
+
const { basePath } = await ctx.getRoutesManifest();
|
|
41
42
|
if (existsSync(ctx.resolve("public"))) {
|
|
42
|
-
await cp(ctx.resolve("public"), ctx.staticDir, { recursive: true });
|
|
43
|
+
await cp(ctx.resolve("public"), join(ctx.staticDir, basePath), { recursive: true });
|
|
43
44
|
}
|
|
44
45
|
if (existsSync(join(ctx.publishDir, "static"))) {
|
|
45
|
-
await cp(join(ctx.publishDir, "static"), join(ctx.staticDir, "_next/static"), {
|
|
46
|
+
await cp(join(ctx.publishDir, "static"), join(ctx.staticDir, basePath, "_next/static"), {
|
|
46
47
|
recursive: true
|
|
47
48
|
});
|
|
48
49
|
}
|
package/dist/index.js
CHANGED
|
@@ -4,31 +4,31 @@
|
|
|
4
4
|
return createRequire(import.meta.url);
|
|
5
5
|
})();
|
|
6
6
|
|
|
7
|
-
import {
|
|
8
|
-
createServerHandler
|
|
9
|
-
} from "./esm-chunks/chunk-PEBUKFKH.js";
|
|
10
7
|
import {
|
|
11
8
|
copyFetchContent,
|
|
12
9
|
copyPrerenderedContent
|
|
13
10
|
} from "./esm-chunks/chunk-G3GM7JNF.js";
|
|
14
|
-
import "./esm-chunks/chunk-G2VRYWGL.js";
|
|
15
11
|
import {
|
|
16
12
|
copyStaticAssets,
|
|
17
13
|
copyStaticContent,
|
|
18
14
|
publishStaticDir,
|
|
19
15
|
unpublishStaticDir
|
|
20
|
-
} from "./esm-chunks/chunk-
|
|
16
|
+
} from "./esm-chunks/chunk-Z7ZMLVTM.js";
|
|
17
|
+
import {
|
|
18
|
+
createEdgeHandlers
|
|
19
|
+
} from "./esm-chunks/chunk-UXLNY5XK.js";
|
|
20
|
+
import {
|
|
21
|
+
createServerHandler
|
|
22
|
+
} from "./esm-chunks/chunk-NOX2JUQZ.js";
|
|
23
|
+
import "./esm-chunks/chunk-A22224GM.js";
|
|
21
24
|
import "./esm-chunks/chunk-AVWFCGVE.js";
|
|
22
25
|
import {
|
|
23
26
|
restoreBuildCache,
|
|
24
27
|
saveBuildCache
|
|
25
28
|
} from "./esm-chunks/chunk-GGHAQM5D.js";
|
|
26
|
-
import {
|
|
27
|
-
createEdgeHandlers
|
|
28
|
-
} from "./esm-chunks/chunk-XD5TSLZO.js";
|
|
29
29
|
import {
|
|
30
30
|
PluginContext
|
|
31
|
-
} from "./esm-chunks/chunk-
|
|
31
|
+
} from "./esm-chunks/chunk-VP3PT3VV.js";
|
|
32
32
|
import "./esm-chunks/chunk-RSKIKBZH.js";
|
|
33
33
|
|
|
34
34
|
// src/index.ts
|
|
@@ -631,6 +631,9 @@ var Store = class _Store {
|
|
|
631
631
|
};
|
|
632
632
|
}
|
|
633
633
|
static validateKey(key) {
|
|
634
|
+
if (key === "") {
|
|
635
|
+
throw new Error("Blob key must not be empty.");
|
|
636
|
+
}
|
|
634
637
|
if (key.startsWith("/") || key.startsWith("%2F")) {
|
|
635
638
|
throw new Error("Blob key must not start with forward slash (/).");
|
|
636
639
|
}
|
|
@@ -13,10 +13,13 @@ import {
|
|
|
13
13
|
setCacheControlHeaders,
|
|
14
14
|
setCacheTagsHeaders,
|
|
15
15
|
setVaryHeaders
|
|
16
|
-
} from "../../esm-chunks/chunk-
|
|
16
|
+
} from "../../esm-chunks/chunk-5N2CWXSJ.js";
|
|
17
17
|
import {
|
|
18
18
|
nextResponseProxy
|
|
19
19
|
} from "../../esm-chunks/chunk-B6QMRLBH.js";
|
|
20
|
+
import {
|
|
21
|
+
logger
|
|
22
|
+
} from "../../esm-chunks/chunk-YZXA5QBC.js";
|
|
20
23
|
import {
|
|
21
24
|
__commonJS,
|
|
22
25
|
__toESM
|
|
@@ -3260,9 +3263,10 @@ var server_default = async (request) => {
|
|
|
3260
3263
|
setRunConfig(nextConfig);
|
|
3261
3264
|
tagsManifest = await getTagsManifest();
|
|
3262
3265
|
const { getMockedRequestHandlers } = await import("../next.cjs");
|
|
3266
|
+
const url = new URL(request.url);
|
|
3263
3267
|
[nextHandler] = await getMockedRequestHandlers({
|
|
3264
|
-
port:
|
|
3265
|
-
hostname:
|
|
3268
|
+
port: Number(url.port) || 443,
|
|
3269
|
+
hostname: url.hostname,
|
|
3266
3270
|
dir: process.cwd(),
|
|
3267
3271
|
isDev: false
|
|
3268
3272
|
});
|
|
@@ -3273,15 +3277,19 @@ var server_default = async (request) => {
|
|
|
3273
3277
|
try {
|
|
3274
3278
|
await nextHandler(req, resProxy);
|
|
3275
3279
|
} catch (error) {
|
|
3280
|
+
logger.withError(error).error("next handler error");
|
|
3276
3281
|
console.error(error);
|
|
3277
3282
|
resProxy.statusCode = 500;
|
|
3278
3283
|
resProxy.end("Internal Server Error");
|
|
3279
3284
|
}
|
|
3280
3285
|
const response = await toComputeResponse(resProxy);
|
|
3281
3286
|
await adjustDateHeader(response.headers, request);
|
|
3282
|
-
setCacheControlHeaders(response.headers);
|
|
3287
|
+
setCacheControlHeaders(response.headers, request);
|
|
3283
3288
|
setCacheTagsHeaders(response.headers, request, tagsManifest);
|
|
3284
3289
|
setVaryHeaders(response.headers, request, nextConfig);
|
|
3290
|
+
if (response.status > 300 && response.status < 400) {
|
|
3291
|
+
return new Response(null, response);
|
|
3292
|
+
}
|
|
3285
3293
|
return response;
|
|
3286
3294
|
};
|
|
3287
3295
|
export {
|
package/dist/run/headers.js
CHANGED
package/dist/run/next.cjs
CHANGED
|
@@ -933,6 +933,9 @@ var Store = class _Store {
|
|
|
933
933
|
};
|
|
934
934
|
}
|
|
935
935
|
static validateKey(key) {
|
|
936
|
+
if (key === "") {
|
|
937
|
+
throw new Error("Blob key must not be empty.");
|
|
938
|
+
}
|
|
936
939
|
if (key.startsWith("/") || key.startsWith("%2F")) {
|
|
937
940
|
throw new Error("Blob key must not start with forward slash (/).");
|
|
938
941
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
|
|
2
|
+
const require = await (async () => {
|
|
3
|
+
const { createRequire } = await import("node:module");
|
|
4
|
+
return createRequire(import.meta.url);
|
|
5
|
+
})();
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
StructuredLogger,
|
|
9
|
+
logger
|
|
10
|
+
} from "../esm-chunks/chunk-YZXA5QBC.js";
|
|
11
|
+
import "../esm-chunks/chunk-RSKIKBZH.js";
|
|
12
|
+
export {
|
|
13
|
+
StructuredLogger,
|
|
14
|
+
logger
|
|
15
|
+
};
|
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Various router utils ported to Deno from Next.js source
|
|
3
|
+
* Licence: https://github.com/vercel/next.js/blob/7280c3ced186bb9a7ae3d7012613ef93f20b0fa9/license.md
|
|
4
|
+
*
|
|
5
|
+
* Some types have been re-implemented to be more compatible with Deno or avoid chains of dependent files
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { Key } from '../vendor/deno.land/x/path_to_regexp@v6.2.1/index.ts'
|
|
9
|
+
|
|
10
|
+
import { compile, pathToRegexp } from '../vendor/deno.land/x/path_to_regexp@v6.2.1/index.ts'
|
|
11
|
+
import { getCookies } from '../vendor/deno.land/std@0.175.0/http/cookie.ts'
|
|
12
|
+
|
|
13
|
+
/*
|
|
14
|
+
┌─────────────────────────────────────────────────────────────────────────┐
|
|
15
|
+
│ Inlined/re-implemented types │
|
|
16
|
+
└─────────────────────────────────────────────────────────────────────────┘
|
|
17
|
+
*/
|
|
18
|
+
export interface ParsedUrlQuery {
|
|
19
|
+
[key: string]: string | string[]
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface Params {
|
|
23
|
+
[param: string]: any
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export type RouteHas =
|
|
27
|
+
| {
|
|
28
|
+
type: 'header' | 'query' | 'cookie'
|
|
29
|
+
key: string
|
|
30
|
+
value?: string
|
|
31
|
+
}
|
|
32
|
+
| {
|
|
33
|
+
type: 'host'
|
|
34
|
+
key?: undefined
|
|
35
|
+
value: string
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export type Rewrite = {
|
|
39
|
+
source: string
|
|
40
|
+
destination: string
|
|
41
|
+
basePath?: false
|
|
42
|
+
locale?: false
|
|
43
|
+
has?: RouteHas[]
|
|
44
|
+
missing?: RouteHas[]
|
|
45
|
+
regex: string
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export type Header = {
|
|
49
|
+
source: string
|
|
50
|
+
basePath?: false
|
|
51
|
+
locale?: false
|
|
52
|
+
headers: Array<{ key: string; value: string }>
|
|
53
|
+
has?: RouteHas[]
|
|
54
|
+
missing?: RouteHas[]
|
|
55
|
+
regex: string
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export type Redirect = {
|
|
59
|
+
source: string
|
|
60
|
+
destination: string
|
|
61
|
+
basePath?: false
|
|
62
|
+
locale?: false
|
|
63
|
+
has?: RouteHas[]
|
|
64
|
+
missing?: RouteHas[]
|
|
65
|
+
statusCode?: number
|
|
66
|
+
permanent?: boolean
|
|
67
|
+
regex: string
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export type DynamicRoute = {
|
|
71
|
+
page: string
|
|
72
|
+
regex: string
|
|
73
|
+
namedRegex?: string
|
|
74
|
+
routeKeys?: { [key: string]: string }
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export type RoutesManifest = {
|
|
78
|
+
basePath: string
|
|
79
|
+
redirects: Redirect[]
|
|
80
|
+
headers: Header[]
|
|
81
|
+
rewrites: {
|
|
82
|
+
beforeFiles: Rewrite[]
|
|
83
|
+
afterFiles: Rewrite[]
|
|
84
|
+
fallback: Rewrite[]
|
|
85
|
+
}
|
|
86
|
+
dynamicRoutes: DynamicRoute[]
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/*
|
|
90
|
+
┌─────────────────────────────────────────────────────────────────────────┐
|
|
91
|
+
│ packages/next/src/shared/lib/escape-regexp.ts │
|
|
92
|
+
└─────────────────────────────────────────────────────────────────────────┘
|
|
93
|
+
*/
|
|
94
|
+
// regexp is based on https://github.com/sindresorhus/escape-string-regexp
|
|
95
|
+
const reHasRegExp = /[|\\{}()[\]^$+*?.-]/
|
|
96
|
+
const reReplaceRegExp = /[|\\{}()[\]^$+*?.-]/g
|
|
97
|
+
|
|
98
|
+
export function escapeStringRegexp(str: string) {
|
|
99
|
+
// see also: https://github.com/lodash/lodash/blob/2da024c3b4f9947a48517639de7560457cd4ec6c/escapeRegExp.js#L23
|
|
100
|
+
if (reHasRegExp.test(str)) {
|
|
101
|
+
return str.replace(reReplaceRegExp, '\\$&')
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return str
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/*
|
|
108
|
+
┌─────────────────────────────────────────────────────────────────────────┐
|
|
109
|
+
│ packages/next/src/shared/lib/router/utils/querystring.ts │
|
|
110
|
+
└─────────────────────────────────────────────────────────────────────────┘
|
|
111
|
+
*/
|
|
112
|
+
export function searchParamsToUrlQuery(searchParams: URLSearchParams): ParsedUrlQuery {
|
|
113
|
+
const query: ParsedUrlQuery = {}
|
|
114
|
+
|
|
115
|
+
searchParams.forEach((value, key) => {
|
|
116
|
+
if (typeof query[key] === 'undefined') {
|
|
117
|
+
query[key] = value
|
|
118
|
+
} else if (Array.isArray(query[key])) {
|
|
119
|
+
;(query[key] as string[]).push(value)
|
|
120
|
+
} else {
|
|
121
|
+
query[key] = [query[key] as string, value]
|
|
122
|
+
}
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
return query
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/*
|
|
129
|
+
┌─────────────────────────────────────────────────────────────────────────┐
|
|
130
|
+
│ packages/next/src/shared/lib/router/utils/parse-url.ts │
|
|
131
|
+
└─────────────────────────────────────────────────────────────────────────┘
|
|
132
|
+
*/
|
|
133
|
+
interface ParsedUrl {
|
|
134
|
+
hash: string
|
|
135
|
+
hostname?: string | null
|
|
136
|
+
href: string
|
|
137
|
+
pathname: string
|
|
138
|
+
port?: string | null
|
|
139
|
+
protocol?: string | null
|
|
140
|
+
query: ParsedUrlQuery
|
|
141
|
+
search: string
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function parseUrl(url: string): ParsedUrl {
|
|
145
|
+
const parsedURL = url.startsWith('/') ? new URL(url, 'http://n') : new URL(url)
|
|
146
|
+
|
|
147
|
+
return {
|
|
148
|
+
hash: parsedURL.hash,
|
|
149
|
+
hostname: parsedURL.hostname,
|
|
150
|
+
href: parsedURL.href,
|
|
151
|
+
pathname: parsedURL.pathname,
|
|
152
|
+
port: parsedURL.port,
|
|
153
|
+
protocol: parsedURL.protocol,
|
|
154
|
+
query: searchParamsToUrlQuery(parsedURL.searchParams),
|
|
155
|
+
search: parsedURL.search,
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/*
|
|
160
|
+
┌─────────────────────────────────────────────────────────────────────────┐
|
|
161
|
+
│ packages/next/src/shared/lib/router/utils/prepare-destination.ts │
|
|
162
|
+
│ — Changed to use WHATWG Fetch `Request` instead of │
|
|
163
|
+
│ `http.IncomingMessage`. │
|
|
164
|
+
└─────────────────────────────────────────────────────────────────────────┘
|
|
165
|
+
*/
|
|
166
|
+
export function matchHas(
|
|
167
|
+
req: Pick<Request, 'headers' | 'url'>,
|
|
168
|
+
query: Params,
|
|
169
|
+
has: RouteHas[] = [],
|
|
170
|
+
missing: RouteHas[] = [],
|
|
171
|
+
): false | Params {
|
|
172
|
+
const params: Params = {}
|
|
173
|
+
const cookies = getCookies(req.headers)
|
|
174
|
+
const url = new URL(req.url)
|
|
175
|
+
const hasMatch = (hasItem: RouteHas) => {
|
|
176
|
+
let value: undefined | string | null
|
|
177
|
+
let key = hasItem.key
|
|
178
|
+
|
|
179
|
+
switch (hasItem.type) {
|
|
180
|
+
case 'header': {
|
|
181
|
+
key = hasItem.key.toLowerCase()
|
|
182
|
+
value = req.headers.get(key)
|
|
183
|
+
break
|
|
184
|
+
}
|
|
185
|
+
case 'cookie': {
|
|
186
|
+
value = cookies[hasItem.key]
|
|
187
|
+
break
|
|
188
|
+
}
|
|
189
|
+
case 'query': {
|
|
190
|
+
value = query[hasItem.key]
|
|
191
|
+
break
|
|
192
|
+
}
|
|
193
|
+
case 'host': {
|
|
194
|
+
value = url.hostname
|
|
195
|
+
break
|
|
196
|
+
}
|
|
197
|
+
default: {
|
|
198
|
+
break
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
if (!hasItem.value && value && key) {
|
|
202
|
+
params[getSafeParamName(key)] = value
|
|
203
|
+
|
|
204
|
+
return true
|
|
205
|
+
} else if (value) {
|
|
206
|
+
const matcher = new RegExp(`^${hasItem.value}$`)
|
|
207
|
+
const matches = Array.isArray(value)
|
|
208
|
+
? value.slice(-1)[0].match(matcher)
|
|
209
|
+
: value.match(matcher)
|
|
210
|
+
|
|
211
|
+
if (matches) {
|
|
212
|
+
if (Array.isArray(matches)) {
|
|
213
|
+
if (matches.groups) {
|
|
214
|
+
Object.keys(matches.groups).forEach((groupKey) => {
|
|
215
|
+
params[groupKey] = matches.groups![groupKey]
|
|
216
|
+
})
|
|
217
|
+
} else if (hasItem.type === 'host' && matches[0]) {
|
|
218
|
+
params.host = matches[0]
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return true
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return false
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const allMatch = has.every((item) => hasMatch(item)) && !missing.some((item) => hasMatch(item))
|
|
228
|
+
|
|
229
|
+
if (allMatch) {
|
|
230
|
+
return params
|
|
231
|
+
}
|
|
232
|
+
return false
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export function compileNonPath(value: string, params: Params): string {
|
|
236
|
+
if (!value.includes(':')) {
|
|
237
|
+
return value
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
for (const key of Object.keys(params)) {
|
|
241
|
+
if (value.includes(`:${key}`)) {
|
|
242
|
+
value = value
|
|
243
|
+
.replace(new RegExp(`:${key}\\*`, 'g'), `:${key}--ESCAPED_PARAM_ASTERISKS`)
|
|
244
|
+
.replace(new RegExp(`:${key}\\?`, 'g'), `:${key}--ESCAPED_PARAM_QUESTION`)
|
|
245
|
+
.replace(new RegExp(`:${key}\\+`, 'g'), `:${key}--ESCAPED_PARAM_PLUS`)
|
|
246
|
+
.replace(new RegExp(`:${key}(?!\\w)`, 'g'), `--ESCAPED_PARAM_COLON${key}`)
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
value = value
|
|
250
|
+
.replace(/(:|\*|\?|\+|\(|\)|\{|\})/g, '\\$1')
|
|
251
|
+
.replace(/--ESCAPED_PARAM_PLUS/g, '+')
|
|
252
|
+
.replace(/--ESCAPED_PARAM_COLON/g, ':')
|
|
253
|
+
.replace(/--ESCAPED_PARAM_QUESTION/g, '?')
|
|
254
|
+
.replace(/--ESCAPED_PARAM_ASTERISKS/g, '*')
|
|
255
|
+
// the value needs to start with a forward-slash to be compiled
|
|
256
|
+
// correctly
|
|
257
|
+
return compile(`/${value}`, { validate: false })(params).slice(1)
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
export function prepareDestination(args: {
|
|
261
|
+
appendParamsToQuery: boolean
|
|
262
|
+
destination: string
|
|
263
|
+
params: Params
|
|
264
|
+
query: ParsedUrlQuery
|
|
265
|
+
}) {
|
|
266
|
+
const query = Object.assign({}, args.query)
|
|
267
|
+
delete query.__nextLocale
|
|
268
|
+
delete query.__nextDefaultLocale
|
|
269
|
+
delete query.__nextDataReq
|
|
270
|
+
|
|
271
|
+
let escapedDestination = args.destination
|
|
272
|
+
|
|
273
|
+
for (const param of Object.keys({ ...args.params, ...query })) {
|
|
274
|
+
escapedDestination = escapeSegment(escapedDestination, param)
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const parsedDestination: ParsedUrl = parseUrl(escapedDestination)
|
|
278
|
+
const destQuery = parsedDestination.query
|
|
279
|
+
const destPath = unescapeSegments(`${parsedDestination.pathname!}${parsedDestination.hash || ''}`)
|
|
280
|
+
const destHostname = unescapeSegments(parsedDestination.hostname || '')
|
|
281
|
+
const destPathParamKeys: Key[] = []
|
|
282
|
+
const destHostnameParamKeys: Key[] = []
|
|
283
|
+
pathToRegexp(destPath, destPathParamKeys)
|
|
284
|
+
pathToRegexp(destHostname, destHostnameParamKeys)
|
|
285
|
+
|
|
286
|
+
const destParams: (string | number)[] = []
|
|
287
|
+
|
|
288
|
+
destPathParamKeys.forEach((key) => destParams.push(key.name))
|
|
289
|
+
destHostnameParamKeys.forEach((key) => destParams.push(key.name))
|
|
290
|
+
|
|
291
|
+
const destPathCompiler = compile(
|
|
292
|
+
destPath,
|
|
293
|
+
// we don't validate while compiling the destination since we should
|
|
294
|
+
// have already validated before we got to this point and validating
|
|
295
|
+
// breaks compiling destinations with named pattern params from the source
|
|
296
|
+
// e.g. /something:hello(.*) -> /another/:hello is broken with validation
|
|
297
|
+
// since compile validation is meant for reversing and not for inserting
|
|
298
|
+
// params from a separate path-regex into another
|
|
299
|
+
{ validate: false },
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
const destHostnameCompiler = compile(destHostname, { validate: false })
|
|
303
|
+
|
|
304
|
+
// update any params in query values
|
|
305
|
+
for (const [key, strOrArray] of Object.entries(destQuery)) {
|
|
306
|
+
// the value needs to start with a forward-slash to be compiled
|
|
307
|
+
// correctly
|
|
308
|
+
if (Array.isArray(strOrArray)) {
|
|
309
|
+
destQuery[key] = strOrArray.map((value) =>
|
|
310
|
+
compileNonPath(unescapeSegments(value), args.params),
|
|
311
|
+
)
|
|
312
|
+
} else {
|
|
313
|
+
destQuery[key] = compileNonPath(unescapeSegments(strOrArray), args.params)
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// add path params to query if it's not a redirect and not
|
|
318
|
+
// already defined in destination query or path
|
|
319
|
+
const paramKeys = Object.keys(args.params).filter((name) => name !== 'nextInternalLocale')
|
|
320
|
+
|
|
321
|
+
if (args.appendParamsToQuery && !paramKeys.some((key) => destParams.includes(key))) {
|
|
322
|
+
for (const key of paramKeys) {
|
|
323
|
+
if (!(key in destQuery)) {
|
|
324
|
+
destQuery[key] = args.params[key]
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
let newUrl
|
|
330
|
+
|
|
331
|
+
try {
|
|
332
|
+
newUrl = destPathCompiler(args.params)
|
|
333
|
+
|
|
334
|
+
const [pathname, hash] = newUrl.split('#')
|
|
335
|
+
parsedDestination.hostname = destHostnameCompiler(args.params)
|
|
336
|
+
parsedDestination.pathname = pathname
|
|
337
|
+
parsedDestination.hash = `${hash ? '#' : ''}${hash || ''}`
|
|
338
|
+
delete (parsedDestination as any).search
|
|
339
|
+
} catch (err: any) {
|
|
340
|
+
if (err.message.match(/Expected .*? to not repeat, but got an array/)) {
|
|
341
|
+
throw new Error(
|
|
342
|
+
`To use a multi-match in the destination you must add \`*\` at the end of the param name to signify it should repeat. https://nextjs.org/docs/messages/invalid-multi-match`,
|
|
343
|
+
)
|
|
344
|
+
}
|
|
345
|
+
throw err
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// Query merge order lowest priority to highest
|
|
349
|
+
// 1. initial URL query values
|
|
350
|
+
// 2. path segment values
|
|
351
|
+
// 3. destination specified query values
|
|
352
|
+
parsedDestination.query = {
|
|
353
|
+
...query,
|
|
354
|
+
...parsedDestination.query,
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
return {
|
|
358
|
+
newUrl,
|
|
359
|
+
destQuery,
|
|
360
|
+
parsedDestination,
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Ensure only a-zA-Z are used for param names for proper interpolating
|
|
366
|
+
* with path-to-regexp
|
|
367
|
+
*/
|
|
368
|
+
function getSafeParamName(paramName: string) {
|
|
369
|
+
let newParamName = ''
|
|
370
|
+
|
|
371
|
+
for (let i = 0; i < paramName.length; i++) {
|
|
372
|
+
const charCode = paramName.charCodeAt(i)
|
|
373
|
+
|
|
374
|
+
if (
|
|
375
|
+
(charCode > 64 && charCode < 91) || // A-Z
|
|
376
|
+
(charCode > 96 && charCode < 123) // a-z
|
|
377
|
+
) {
|
|
378
|
+
newParamName += paramName[i]
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
return newParamName
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function escapeSegment(str: string, segmentName: string) {
|
|
385
|
+
return str.replace(
|
|
386
|
+
new RegExp(`:${escapeStringRegexp(segmentName)}`, 'g'),
|
|
387
|
+
`__ESC_COLON_${segmentName}`,
|
|
388
|
+
)
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function unescapeSegments(str: string) {
|
|
392
|
+
return str.replace(/__ESC_COLON_/gi, ':')
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/*
|
|
396
|
+
┌─────────────────────────────────────────────────────────────────────────┐
|
|
397
|
+
│ packages/next/src/shared/lib/router/utils/is-dynamic.ts │
|
|
398
|
+
└─────────────────────────────────────────────────────────────────────────┘
|
|
399
|
+
*/
|
|
400
|
+
// Identify /[param]/ in route string
|
|
401
|
+
const TEST_ROUTE = /\/\[[^/]+?\](?=\/|$)/
|
|
402
|
+
|
|
403
|
+
export function isDynamicRoute(route: string): boolean {
|
|
404
|
+
return TEST_ROUTE.test(route)
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/*
|
|
408
|
+
┌─────────────────────────────────────────────────────────────────────────┐
|
|
409
|
+
│ packages/next/shared/lib/router/utils/middleware-route-matcher.ts │
|
|
410
|
+
└─────────────────────────────────────────────────────────────────────────┘
|
|
411
|
+
*/
|
|
412
|
+
export interface MiddlewareRouteMatch {
|
|
413
|
+
(
|
|
414
|
+
pathname: string | null | undefined,
|
|
415
|
+
request: Pick<Request, 'headers' | 'url'>,
|
|
416
|
+
query: Params,
|
|
417
|
+
): boolean
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
export interface MiddlewareMatcher {
|
|
421
|
+
regexp: string
|
|
422
|
+
locale?: false
|
|
423
|
+
has?: RouteHas[]
|
|
424
|
+
missing?: RouteHas[]
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
export function getMiddlewareRouteMatcher(matchers: MiddlewareMatcher[]): MiddlewareRouteMatch {
|
|
428
|
+
return (
|
|
429
|
+
pathname: string | null | undefined,
|
|
430
|
+
req: Pick<Request, 'headers' | 'url'>,
|
|
431
|
+
query: Params,
|
|
432
|
+
) => {
|
|
433
|
+
for (const matcher of matchers) {
|
|
434
|
+
const routeMatch = new RegExp(matcher.regexp).exec(pathname!)
|
|
435
|
+
if (!routeMatch) {
|
|
436
|
+
continue
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
if (matcher.has || matcher.missing) {
|
|
440
|
+
const hasParams = matchHas(req, query, matcher.has, matcher.missing)
|
|
441
|
+
if (!hasParams) {
|
|
442
|
+
continue
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
return true
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
return false
|
|
450
|
+
}
|
|
451
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
[]
|
|
@@ -1,11 +1,20 @@
|
|
|
1
1
|
import type { Context } from '@netlify/edge-functions'
|
|
2
2
|
|
|
3
|
+
import matchers from './matchers.json' assert { type: 'json' }
|
|
4
|
+
|
|
3
5
|
import { buildNextRequest, RequestData } from './lib/next-request.ts'
|
|
4
6
|
import { buildResponse } from './lib/response.ts'
|
|
5
7
|
import { FetchEventResult } from './lib/response.ts'
|
|
8
|
+
import {
|
|
9
|
+
type MiddlewareRouteMatch,
|
|
10
|
+
getMiddlewareRouteMatcher,
|
|
11
|
+
searchParamsToUrlQuery,
|
|
12
|
+
} from './lib/routing.ts'
|
|
6
13
|
|
|
7
14
|
type NextHandler = (params: { request: RequestData }) => Promise<FetchEventResult>
|
|
8
15
|
|
|
16
|
+
const matchesMiddleware: MiddlewareRouteMatch = getMiddlewareRouteMatcher(matchers || [])
|
|
17
|
+
|
|
9
18
|
/**
|
|
10
19
|
* Runs a Next.js middleware as a Netlify Edge Function. It translates a web
|
|
11
20
|
* platform Request into a NextRequest instance on the way in, and translates
|
|
@@ -20,13 +29,17 @@ export async function handleMiddleware(
|
|
|
20
29
|
context: Context,
|
|
21
30
|
nextHandler: NextHandler,
|
|
22
31
|
) {
|
|
23
|
-
|
|
24
|
-
|
|
32
|
+
const nextRequest = buildNextRequest(request, context)
|
|
33
|
+
const url = new URL(request.url)
|
|
34
|
+
|
|
35
|
+
// While we have already checked the path when mapping to the edge function,
|
|
36
|
+
// Next.js supports extra rules that we need to check here too, because we
|
|
37
|
+
// might be running an edge function for a path we should not. If we find
|
|
38
|
+
// that's the case, short-circuit the execution.
|
|
39
|
+
if (!matchesMiddleware(url.pathname, request, searchParamsToUrlQuery(url.searchParams))) {
|
|
25
40
|
return
|
|
26
41
|
}
|
|
27
42
|
|
|
28
|
-
const nextRequest = buildNextRequest(request, context)
|
|
29
|
-
|
|
30
43
|
try {
|
|
31
44
|
const result = await nextHandler({ request: nextRequest })
|
|
32
45
|
const response = await buildResponse({ result, request: request, context })
|
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
// a Webpack bundle. You should not import this file from anywhere in the
|
|
3
3
|
// application.
|
|
4
4
|
import { AsyncLocalStorage } from 'node:async_hooks'
|
|
5
|
+
import process from 'node:process'
|
|
6
|
+
|
|
7
|
+
globalThis.process = process
|
|
5
8
|
|
|
6
9
|
globalThis.AsyncLocalStorage = AsyncLocalStorage
|
|
7
10
|
|