@netlify/plugin-nextjs 5.0.0-beta.0 → 5.0.0-beta.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/build/content/prerendered.js +1 -1
- 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/image-cdn.js +13 -0
- package/dist/build/plugin-context.js +1 -1
- package/dist/esm-chunks/{chunk-XD5TSLZO.js → chunk-3PTPU5GO.js} +9 -8
- package/dist/esm-chunks/{chunk-G2VRYWGL.js → chunk-5SZ5JD6J.js} +26 -15
- package/dist/esm-chunks/{chunk-PEBUKFKH.js → chunk-ALO2SSMH.js} +2 -2
- package/dist/esm-chunks/chunk-H46DW7YI.js +28 -0
- package/dist/esm-chunks/{chunk-G3GM7JNF.js → chunk-S5JXJCXP.js} +2 -0
- package/dist/esm-chunks/{chunk-ZBX3SNQG.js → chunk-SMSOJ2OS.js} +24 -3
- package/dist/esm-chunks/{chunk-HPGTYMVD.js → chunk-TJKO3X6O.js} +12 -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 +13 -9
- package/dist/run/handlers/cache.cjs +18 -12
- package/dist/run/handlers/server.js +15 -4
- package/dist/run/headers.js +3 -1
- package/dist/run/next.cjs +6 -0
- package/dist/run/systemlog.js +15 -0
- package/edge-runtime/lib/response.ts +1 -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-5SZ5JD6J.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-3PTPU5GO.js";
|
|
10
|
+
import "../../esm-chunks/chunk-TJKO3X6O.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-ALO2SSMH.js";
|
|
10
|
+
import "../../esm-chunks/chunk-5SZ5JD6J.js";
|
|
11
11
|
import "../../esm-chunks/chunk-AVWFCGVE.js";
|
|
12
|
-
import "../../esm-chunks/chunk-
|
|
12
|
+
import "../../esm-chunks/chunk-TJKO3X6O.js";
|
|
13
13
|
import "../../esm-chunks/chunk-RSKIKBZH.js";
|
|
14
14
|
export {
|
|
15
15
|
createServerHandler
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
|
|
2
|
+
const require = await (async () => {
|
|
3
|
+
const { createRequire } = await import("node:module");
|
|
4
|
+
return createRequire(import.meta.url);
|
|
5
|
+
})();
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
setImageConfig
|
|
9
|
+
} from "../esm-chunks/chunk-H46DW7YI.js";
|
|
10
|
+
import "../esm-chunks/chunk-RSKIKBZH.js";
|
|
11
|
+
export {
|
|
12
|
+
setImageConfig
|
|
13
|
+
};
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
import {
|
|
8
8
|
EDGE_HANDLER_NAME
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-TJKO3X6O.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';
|
|
@@ -19,19 +19,26 @@ import { dirname, join } from "node:path";
|
|
|
19
19
|
var copyNextServerCode = async (ctx) => {
|
|
20
20
|
const srcDir = join(ctx.publishDir, "standalone/.next");
|
|
21
21
|
const destDir = join(ctx.serverHandlerDir, ".next");
|
|
22
|
-
const paths = await (0, import_fast_glob.default)(
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
const paths = await (0, import_fast_glob.default)(
|
|
23
|
+
[`*`, `server/*`, `server/chunks/*`, `server/edge-chunks/*`, `server/+(app|pages)/**/*.js`],
|
|
24
|
+
{
|
|
25
|
+
cwd: srcDir,
|
|
26
|
+
extglob: true
|
|
27
|
+
}
|
|
28
|
+
);
|
|
26
29
|
await Promise.all(
|
|
27
30
|
paths.map(async (path) => {
|
|
31
|
+
const srcPath = join(srcDir, path);
|
|
28
32
|
const destPath = join(destDir, path);
|
|
29
33
|
if (path === "server/middleware-manifest.json") {
|
|
30
|
-
|
|
31
|
-
|
|
34
|
+
try {
|
|
35
|
+
await replaceMiddlewareManifest(srcPath, destPath);
|
|
36
|
+
} catch (error) {
|
|
37
|
+
throw new Error("Could not patch middleware manifest file", { cause: error });
|
|
38
|
+
}
|
|
32
39
|
return;
|
|
33
40
|
}
|
|
34
|
-
await cp(
|
|
41
|
+
await cp(srcPath, destPath, { recursive: true });
|
|
35
42
|
})
|
|
36
43
|
);
|
|
37
44
|
};
|
|
@@ -73,7 +80,9 @@ var writeTagsManifest = async (ctx) => {
|
|
|
73
80
|
const meta = JSON.parse(file);
|
|
74
81
|
tags = meta.headers["x-next-cache-tags"];
|
|
75
82
|
} catch {
|
|
76
|
-
|
|
83
|
+
if (!definition.dataRoute?.endsWith("/default.rsc")) {
|
|
84
|
+
console.log(`Unable to read cache tags for: ${path}`);
|
|
85
|
+
}
|
|
77
86
|
}
|
|
78
87
|
}
|
|
79
88
|
if (definition.dataRoute?.endsWith(".json")) {
|
|
@@ -90,14 +99,16 @@ var writeTagsManifest = async (ctx) => {
|
|
|
90
99
|
"utf-8"
|
|
91
100
|
);
|
|
92
101
|
};
|
|
93
|
-
var
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
102
|
+
var replaceMiddlewareManifest = async (sourcePath, destPath) => {
|
|
103
|
+
await mkdir(dirname(destPath), { recursive: true });
|
|
104
|
+
const data = await readFile(sourcePath, "utf8");
|
|
105
|
+
const manifest = JSON.parse(data);
|
|
106
|
+
const newManifest = {
|
|
107
|
+
...manifest,
|
|
108
|
+
middleware: {}
|
|
99
109
|
};
|
|
100
|
-
|
|
110
|
+
const newData = JSON.stringify(newManifest);
|
|
111
|
+
await writeFile(destPath, newData);
|
|
101
112
|
};
|
|
102
113
|
|
|
103
114
|
export {
|
|
@@ -8,13 +8,13 @@ import {
|
|
|
8
8
|
copyNextDependencies,
|
|
9
9
|
copyNextServerCode,
|
|
10
10
|
writeTagsManifest
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-5SZ5JD6J.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-TJKO3X6O.js";
|
|
18
18
|
import {
|
|
19
19
|
__toESM
|
|
20
20
|
} from "./chunk-RSKIKBZH.js";
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
|
|
2
|
+
const require = await (async () => {
|
|
3
|
+
const { createRequire } = await import("node:module");
|
|
4
|
+
return createRequire(import.meta.url);
|
|
5
|
+
})();
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
// src/build/image-cdn.ts
|
|
9
|
+
var setImageConfig = async (ctx) => {
|
|
10
|
+
const {
|
|
11
|
+
images: { path: imageEndpointPath, loader: imageLoader }
|
|
12
|
+
} = await ctx.getBuildConfig();
|
|
13
|
+
if (imageLoader === "default") {
|
|
14
|
+
ctx.netlifyConfig.redirects.push({
|
|
15
|
+
from: imageEndpointPath,
|
|
16
|
+
// w and q are too short to be used as params with id-length rule
|
|
17
|
+
// but we are forced to do so because of the next/image loader decides on their names
|
|
18
|
+
// eslint-disable-next-line id-length
|
|
19
|
+
query: { url: ":url", w: ":width", q: ":quality" },
|
|
20
|
+
to: "/.netlify/images?url=:url&w=:width&q=:quality",
|
|
21
|
+
status: 200
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export {
|
|
27
|
+
setImageConfig
|
|
28
|
+
};
|
|
@@ -45,6 +45,8 @@ var copyPrerenderedContent = async (ctx) => {
|
|
|
45
45
|
const key = routeToFilePath(route);
|
|
46
46
|
let value;
|
|
47
47
|
switch (true) {
|
|
48
|
+
case (meta.dataRoute?.endsWith("/default.rsc") && !existsSync(join(ctx.publishDir, "server/app", `${key}.html`))):
|
|
49
|
+
return;
|
|
48
50
|
case meta.dataRoute?.endsWith(".json"):
|
|
49
51
|
value = await buildPagesCacheValue(join(ctx.publishDir, "server/pages", key));
|
|
50
52
|
break;
|
|
@@ -13,6 +13,8 @@ import { Buffer } from "buffer";
|
|
|
13
13
|
import { env } from "process";
|
|
14
14
|
import { Buffer as Buffer2 } from "buffer";
|
|
15
15
|
import { Buffer as Buffer3 } from "buffer";
|
|
16
|
+
import stream from "stream";
|
|
17
|
+
import { promisify } from "util";
|
|
16
18
|
var getEnvironmentContext = () => {
|
|
17
19
|
const context = globalThis.netlifyBlobsContext || env.NETLIFY_BLOBS_CONTEXT;
|
|
18
20
|
if (typeof context !== "string" || !context) {
|
|
@@ -382,6 +384,9 @@ var Store = class _Store {
|
|
|
382
384
|
};
|
|
383
385
|
}
|
|
384
386
|
static validateKey(key) {
|
|
387
|
+
if (key === "") {
|
|
388
|
+
throw new Error("Blob key must not be empty.");
|
|
389
|
+
}
|
|
385
390
|
if (key.startsWith("/") || key.startsWith("%2F")) {
|
|
386
391
|
throw new Error("Blob key must not start with forward slash (/).");
|
|
387
392
|
}
|
|
@@ -466,6 +471,7 @@ var getDeployStore = (options = {}) => {
|
|
|
466
471
|
const client = new Client(clientOptions);
|
|
467
472
|
return new Store({ client, deployID });
|
|
468
473
|
};
|
|
474
|
+
var pipeline = promisify(stream.pipeline);
|
|
469
475
|
|
|
470
476
|
// src/run/headers.ts
|
|
471
477
|
var generateNetlifyVaryValues = ({ headers, languages, cookies }) => {
|
|
@@ -532,9 +538,9 @@ var adjustDateHeader = async (headers, request) => {
|
|
|
532
538
|
headers.set("x-nextjs-date", headers.get("date") ?? lastModifiedDate.toUTCString());
|
|
533
539
|
headers.set("date", lastModifiedDate.toUTCString());
|
|
534
540
|
};
|
|
535
|
-
var setCacheControlHeaders = (headers) => {
|
|
541
|
+
var setCacheControlHeaders = (headers, request) => {
|
|
536
542
|
const cacheControl = headers.get("cache-control");
|
|
537
|
-
if (cacheControl !== null && !headers.has("cdn-cache-control") && !headers.has("netlify-cdn-cache-control")) {
|
|
543
|
+
if (cacheControl !== null && ["GET", "HEAD"].includes(request.method) && !headers.has("cdn-cache-control") && !headers.has("netlify-cdn-cache-control")) {
|
|
538
544
|
const privateCacheControl = omitHeaderValues(cacheControl, [
|
|
539
545
|
"s-maxage",
|
|
540
546
|
"stale-while-revalidate"
|
|
@@ -552,10 +558,25 @@ var setCacheTagsHeaders = (headers, request, manifest) => {
|
|
|
552
558
|
const tags = manifest[path];
|
|
553
559
|
headers.set("cache-tag", tags);
|
|
554
560
|
};
|
|
561
|
+
var NEXT_CACHE_TO_CACHE_STATUS = {
|
|
562
|
+
HIT: `hit`,
|
|
563
|
+
MISS: `miss,`
|
|
564
|
+
};
|
|
565
|
+
var handleNextCacheHeader = (headers) => {
|
|
566
|
+
const nextCache = headers.get("x-nextjs-cache");
|
|
567
|
+
if (typeof nextCache === "string") {
|
|
568
|
+
if (nextCache in NEXT_CACHE_TO_CACHE_STATUS) {
|
|
569
|
+
const cacheStatus = NEXT_CACHE_TO_CACHE_STATUS[nextCache];
|
|
570
|
+
headers.set("cache-status", `"Next.js"; ${cacheStatus}`);
|
|
571
|
+
}
|
|
572
|
+
headers.delete("x-nextjs-cache");
|
|
573
|
+
}
|
|
574
|
+
};
|
|
555
575
|
|
|
556
576
|
export {
|
|
557
577
|
setVaryHeaders,
|
|
558
578
|
adjustDateHeader,
|
|
559
579
|
setCacheControlHeaders,
|
|
560
|
-
setCacheTagsHeaders
|
|
580
|
+
setCacheTagsHeaders,
|
|
581
|
+
handleNextCacheHeader
|
|
561
582
|
};
|
|
@@ -17,6 +17,7 @@ var SERVER_HANDLER_NAME = "___netlify-server-handler";
|
|
|
17
17
|
var EDGE_HANDLER_NAME = "___netlify-edge-handler";
|
|
18
18
|
var PluginContext = class {
|
|
19
19
|
utils;
|
|
20
|
+
netlifyConfig;
|
|
20
21
|
pluginName;
|
|
21
22
|
pluginVersion;
|
|
22
23
|
constants;
|
|
@@ -70,6 +71,7 @@ var PluginContext = class {
|
|
|
70
71
|
this.pluginVersion = this.packageJSON.version;
|
|
71
72
|
this.constants = options.constants;
|
|
72
73
|
this.utils = options.utils;
|
|
74
|
+
this.netlifyConfig = options.netlifyConfig;
|
|
73
75
|
}
|
|
74
76
|
/** Resolves a path correctly with mono repository awareness */
|
|
75
77
|
resolve(...args) {
|
|
@@ -87,6 +89,16 @@ var PluginContext = class {
|
|
|
87
89
|
await readFile(join(this.publishDir, "server/middleware-manifest.json"), "utf-8")
|
|
88
90
|
);
|
|
89
91
|
}
|
|
92
|
+
/** Get Next Config from build output **/
|
|
93
|
+
async getBuildConfig() {
|
|
94
|
+
return JSON.parse(await readFile(join(this.publishDir, "required-server-files.json"), "utf-8")).config;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Get Next.js routes manifest from the build output
|
|
98
|
+
*/
|
|
99
|
+
async getRoutesManifest() {
|
|
100
|
+
return JSON.parse(await readFile(join(this.publishDir, "routes-manifest.json"), "utf-8"));
|
|
101
|
+
}
|
|
90
102
|
/**
|
|
91
103
|
* Write a cache entry to the blob upload directory using
|
|
92
104
|
* 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,34 @@
|
|
|
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
|
-
} from "./esm-chunks/chunk-
|
|
14
|
-
import "./esm-chunks/chunk-G2VRYWGL.js";
|
|
10
|
+
} from "./esm-chunks/chunk-S5JXJCXP.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-3PTPU5GO.js";
|
|
20
|
+
import {
|
|
21
|
+
createServerHandler
|
|
22
|
+
} from "./esm-chunks/chunk-ALO2SSMH.js";
|
|
23
|
+
import "./esm-chunks/chunk-5SZ5JD6J.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
29
|
import {
|
|
27
|
-
|
|
28
|
-
} from "./esm-chunks/chunk-
|
|
30
|
+
setImageConfig
|
|
31
|
+
} from "./esm-chunks/chunk-H46DW7YI.js";
|
|
29
32
|
import {
|
|
30
33
|
PluginContext
|
|
31
|
-
} from "./esm-chunks/chunk-
|
|
34
|
+
} from "./esm-chunks/chunk-TJKO3X6O.js";
|
|
32
35
|
import "./esm-chunks/chunk-RSKIKBZH.js";
|
|
33
36
|
|
|
34
37
|
// src/index.ts
|
|
@@ -42,6 +45,7 @@ var onBuild = async (options) => {
|
|
|
42
45
|
if (!existsSync(ctx.publishDir)) {
|
|
43
46
|
ctx.failBuild("Publish directory not found, please check your netlify.toml");
|
|
44
47
|
}
|
|
48
|
+
await setImageConfig(ctx);
|
|
45
49
|
await saveBuildCache(ctx);
|
|
46
50
|
await Promise.all([
|
|
47
51
|
copyStaticAssets(ctx),
|
|
@@ -174,8 +174,8 @@ var require_stream = __commonJS({
|
|
|
174
174
|
exports2.stream = void 0;
|
|
175
175
|
var node_stream_1 = require("node:stream");
|
|
176
176
|
var node_util_1 = require("node:util");
|
|
177
|
-
var
|
|
178
|
-
var
|
|
177
|
+
var pipeline2 = (0, node_util_1.promisify)(node_stream_1.pipeline);
|
|
178
|
+
var stream2 = (handler) => awslambda.streamifyResponse(async (event, responseStream, context) => {
|
|
179
179
|
const { body, ...httpResponseMetadata } = await handler(event, context);
|
|
180
180
|
const responseBody = awslambda.HttpResponseStream.from(responseStream, httpResponseMetadata);
|
|
181
181
|
if (typeof body === "undefined") {
|
|
@@ -184,10 +184,10 @@ var require_stream = __commonJS({
|
|
|
184
184
|
responseBody.write(body);
|
|
185
185
|
responseBody.end();
|
|
186
186
|
} else {
|
|
187
|
-
await
|
|
187
|
+
await pipeline2(body, responseBody);
|
|
188
188
|
}
|
|
189
189
|
});
|
|
190
|
-
exports2.stream =
|
|
190
|
+
exports2.stream = stream2;
|
|
191
191
|
}
|
|
192
192
|
});
|
|
193
193
|
|
|
@@ -249,7 +249,6 @@ var require_main = __commonJS({
|
|
|
249
249
|
var cache_exports = {};
|
|
250
250
|
__export(cache_exports, {
|
|
251
251
|
NetlifyCacheHandler: () => NetlifyCacheHandler,
|
|
252
|
-
blobStore: () => blobStore,
|
|
253
252
|
default: () => cache_default
|
|
254
253
|
});
|
|
255
254
|
module.exports = __toCommonJS(cache_exports);
|
|
@@ -262,6 +261,8 @@ var import_buffer = require("buffer");
|
|
|
262
261
|
var import_process = require("process");
|
|
263
262
|
var import_buffer2 = require("buffer");
|
|
264
263
|
var import_buffer3 = require("buffer");
|
|
264
|
+
var import_stream = __toESM(require("stream"), 1);
|
|
265
|
+
var import_util = require("util");
|
|
265
266
|
var getEnvironmentContext = () => {
|
|
266
267
|
const context = globalThis.netlifyBlobsContext || import_process.env.NETLIFY_BLOBS_CONTEXT;
|
|
267
268
|
if (typeof context !== "string" || !context) {
|
|
@@ -631,6 +632,9 @@ var Store = class _Store {
|
|
|
631
632
|
};
|
|
632
633
|
}
|
|
633
634
|
static validateKey(key) {
|
|
635
|
+
if (key === "") {
|
|
636
|
+
throw new Error("Blob key must not be empty.");
|
|
637
|
+
}
|
|
634
638
|
if (key.startsWith("/") || key.startsWith("%2F")) {
|
|
635
639
|
throw new Error("Blob key must not start with forward slash (/).");
|
|
636
640
|
}
|
|
@@ -715,11 +719,11 @@ var getDeployStore = (options = {}) => {
|
|
|
715
719
|
const client = new Client(clientOptions);
|
|
716
720
|
return new Store({ client, deployID });
|
|
717
721
|
};
|
|
722
|
+
var pipeline = (0, import_util.promisify)(import_stream.default.pipeline);
|
|
718
723
|
|
|
719
724
|
// src/run/handlers/cache.cts
|
|
720
725
|
var import_functions = __toESM(require_main());
|
|
721
726
|
var import_constants = require("next/dist/lib/constants.js");
|
|
722
|
-
var blobStore = getDeployStore();
|
|
723
727
|
var prerenderManifest = JSON.parse(
|
|
724
728
|
(0, import_node_fs.readFileSync)((0, import_posix.join)(process.cwd(), ".next/prerender-manifest.json"), "utf-8")
|
|
725
729
|
);
|
|
@@ -729,17 +733,20 @@ function toRoute(cacheKey) {
|
|
|
729
733
|
function encodeBlobKey(key) {
|
|
730
734
|
return import_node_buffer.Buffer.from(key.replace(/^\//, "")).toString("base64");
|
|
731
735
|
}
|
|
736
|
+
var fetchBeforeNextPatchedIt = globalThis.fetch;
|
|
732
737
|
var NetlifyCacheHandler = class {
|
|
733
738
|
options;
|
|
734
739
|
revalidatedTags;
|
|
740
|
+
blobStore;
|
|
735
741
|
constructor(options) {
|
|
736
742
|
this.options = options;
|
|
737
743
|
this.revalidatedTags = options.revalidatedTags;
|
|
744
|
+
this.blobStore = getDeployStore({ fetch: fetchBeforeNextPatchedIt });
|
|
738
745
|
}
|
|
739
746
|
async get(...args) {
|
|
740
747
|
const [key, ctx = {}] = args;
|
|
741
748
|
console.debug(`[NetlifyCacheHandler.get]: ${key}`);
|
|
742
|
-
const blob = await blobStore.get(encodeBlobKey(key), {
|
|
749
|
+
const blob = await this.blobStore.get(encodeBlobKey(key), {
|
|
743
750
|
type: "json"
|
|
744
751
|
});
|
|
745
752
|
if (!blob) {
|
|
@@ -783,7 +790,7 @@ var NetlifyCacheHandler = class {
|
|
|
783
790
|
async set(...args) {
|
|
784
791
|
const [key, data] = args;
|
|
785
792
|
console.debug(`[NetlifyCacheHandler.set]: ${key}`);
|
|
786
|
-
await blobStore.setJSON(encodeBlobKey(key), {
|
|
793
|
+
await this.blobStore.setJSON(encodeBlobKey(key), {
|
|
787
794
|
lastModified: Date.now(),
|
|
788
795
|
value: data
|
|
789
796
|
});
|
|
@@ -795,7 +802,7 @@ var NetlifyCacheHandler = class {
|
|
|
795
802
|
revalidatedAt: Date.now()
|
|
796
803
|
};
|
|
797
804
|
try {
|
|
798
|
-
await blobStore.setJSON(encodeBlobKey(tag), data);
|
|
805
|
+
await this.blobStore.setJSON(encodeBlobKey(tag), data);
|
|
799
806
|
} catch (error) {
|
|
800
807
|
console.warn(`Failed to update tag manifest for ${tag}`, error);
|
|
801
808
|
}
|
|
@@ -811,7 +818,7 @@ var NetlifyCacheHandler = class {
|
|
|
811
818
|
const cacheTags = [...tags, ...softTags];
|
|
812
819
|
const allManifests = await Promise.all(
|
|
813
820
|
cacheTags.map(async (tag) => {
|
|
814
|
-
const res = await blobStore.get(encodeBlobKey(tag), { type: "json" }).then((value) => ({ [tag]: value })).catch(console.error);
|
|
821
|
+
const res = await this.blobStore.get(encodeBlobKey(tag), { type: "json" }).then((value) => ({ [tag]: value })).catch(console.error);
|
|
815
822
|
return res || { [tag]: null };
|
|
816
823
|
})
|
|
817
824
|
);
|
|
@@ -844,6 +851,5 @@ var NetlifyCacheHandler = class {
|
|
|
844
851
|
var cache_default = NetlifyCacheHandler;
|
|
845
852
|
// Annotate the CommonJS export names for ESM import in node:
|
|
846
853
|
0 && (module.exports = {
|
|
847
|
-
NetlifyCacheHandler
|
|
848
|
-
blobStore
|
|
854
|
+
NetlifyCacheHandler
|
|
849
855
|
});
|
|
@@ -4,16 +4,20 @@
|
|
|
4
4
|
return createRequire(import.meta.url);
|
|
5
5
|
})();
|
|
6
6
|
|
|
7
|
+
import {
|
|
8
|
+
logger
|
|
9
|
+
} from "../../esm-chunks/chunk-YZXA5QBC.js";
|
|
7
10
|
import {
|
|
8
11
|
getTagsManifest
|
|
9
12
|
} from "../../esm-chunks/chunk-R4NHZWGU.js";
|
|
10
13
|
import "../../esm-chunks/chunk-4AJYXTWN.js";
|
|
11
14
|
import {
|
|
12
15
|
adjustDateHeader,
|
|
16
|
+
handleNextCacheHeader,
|
|
13
17
|
setCacheControlHeaders,
|
|
14
18
|
setCacheTagsHeaders,
|
|
15
19
|
setVaryHeaders
|
|
16
|
-
} from "../../esm-chunks/chunk-
|
|
20
|
+
} from "../../esm-chunks/chunk-SMSOJ2OS.js";
|
|
17
21
|
import {
|
|
18
22
|
nextResponseProxy
|
|
19
23
|
} from "../../esm-chunks/chunk-B6QMRLBH.js";
|
|
@@ -3260,9 +3264,10 @@ var server_default = async (request) => {
|
|
|
3260
3264
|
setRunConfig(nextConfig);
|
|
3261
3265
|
tagsManifest = await getTagsManifest();
|
|
3262
3266
|
const { getMockedRequestHandlers } = await import("../next.cjs");
|
|
3267
|
+
const url = new URL(request.url);
|
|
3263
3268
|
[nextHandler] = await getMockedRequestHandlers({
|
|
3264
|
-
port:
|
|
3265
|
-
hostname:
|
|
3269
|
+
port: Number(url.port) || 443,
|
|
3270
|
+
hostname: url.hostname,
|
|
3266
3271
|
dir: process.cwd(),
|
|
3267
3272
|
isDev: false
|
|
3268
3273
|
});
|
|
@@ -3273,15 +3278,21 @@ var server_default = async (request) => {
|
|
|
3273
3278
|
try {
|
|
3274
3279
|
await nextHandler(req, resProxy);
|
|
3275
3280
|
} catch (error) {
|
|
3281
|
+
logger.withError(error).error("next handler error");
|
|
3276
3282
|
console.error(error);
|
|
3277
3283
|
resProxy.statusCode = 500;
|
|
3278
3284
|
resProxy.end("Internal Server Error");
|
|
3279
3285
|
}
|
|
3280
3286
|
const response = await toComputeResponse(resProxy);
|
|
3281
3287
|
await adjustDateHeader(response.headers, request);
|
|
3282
|
-
setCacheControlHeaders(response.headers);
|
|
3288
|
+
setCacheControlHeaders(response.headers, request);
|
|
3283
3289
|
setCacheTagsHeaders(response.headers, request, tagsManifest);
|
|
3284
3290
|
setVaryHeaders(response.headers, request, nextConfig);
|
|
3291
|
+
handleNextCacheHeader(response.headers);
|
|
3292
|
+
if (response.status > 300 && response.status < 400 || response.status >= 500) {
|
|
3293
|
+
const body = await response.text();
|
|
3294
|
+
return new Response(body || null, response);
|
|
3295
|
+
}
|
|
3285
3296
|
return response;
|
|
3286
3297
|
};
|
|
3287
3298
|
export {
|
package/dist/run/headers.js
CHANGED
|
@@ -6,13 +6,15 @@
|
|
|
6
6
|
|
|
7
7
|
import {
|
|
8
8
|
adjustDateHeader,
|
|
9
|
+
handleNextCacheHeader,
|
|
9
10
|
setCacheControlHeaders,
|
|
10
11
|
setCacheTagsHeaders,
|
|
11
12
|
setVaryHeaders
|
|
12
|
-
} from "../esm-chunks/chunk-
|
|
13
|
+
} from "../esm-chunks/chunk-SMSOJ2OS.js";
|
|
13
14
|
import "../esm-chunks/chunk-RSKIKBZH.js";
|
|
14
15
|
export {
|
|
15
16
|
adjustDateHeader,
|
|
17
|
+
handleNextCacheHeader,
|
|
16
18
|
setCacheControlHeaders,
|
|
17
19
|
setCacheTagsHeaders,
|
|
18
20
|
setVaryHeaders
|
package/dist/run/next.cjs
CHANGED
|
@@ -564,6 +564,8 @@ var import_buffer = require("buffer");
|
|
|
564
564
|
var import_process = require("process");
|
|
565
565
|
var import_buffer2 = require("buffer");
|
|
566
566
|
var import_buffer3 = require("buffer");
|
|
567
|
+
var import_stream = __toESM(require("stream"), 1);
|
|
568
|
+
var import_util = require("util");
|
|
567
569
|
var getEnvironmentContext = () => {
|
|
568
570
|
const context = globalThis.netlifyBlobsContext || import_process.env.NETLIFY_BLOBS_CONTEXT;
|
|
569
571
|
if (typeof context !== "string" || !context) {
|
|
@@ -933,6 +935,9 @@ var Store = class _Store {
|
|
|
933
935
|
};
|
|
934
936
|
}
|
|
935
937
|
static validateKey(key) {
|
|
938
|
+
if (key === "") {
|
|
939
|
+
throw new Error("Blob key must not be empty.");
|
|
940
|
+
}
|
|
936
941
|
if (key.startsWith("/") || key.startsWith("%2F")) {
|
|
937
942
|
throw new Error("Blob key must not start with forward slash (/).");
|
|
938
943
|
}
|
|
@@ -1017,6 +1022,7 @@ var getDeployStore = (options = {}) => {
|
|
|
1017
1022
|
const client = new Client(clientOptions);
|
|
1018
1023
|
return new Store({ client, deployID });
|
|
1019
1024
|
};
|
|
1025
|
+
var pipeline = (0, import_util.promisify)(import_stream.default.pipeline);
|
|
1020
1026
|
|
|
1021
1027
|
// src/run/next.cts
|
|
1022
1028
|
var import_fs_monkey = __toESM(require_lib());
|
|
@@ -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
|
|