@opennextjs/aws 3.2.0 → 3.2.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.
@@ -88,6 +88,7 @@ export default class S3Cache {
88
88
  kindHint?: "app" | "pages" | "fetch";
89
89
  tags?: string[];
90
90
  softTags?: string[];
91
+ kind?: "FETCH";
91
92
  }): Promise<CacheHandlerValue | null>;
92
93
  getFetchCache(key: string, softTags?: string[], tags?: string[]): Promise<CacheHandlerValue | null>;
93
94
  getIncrementalCache(key: string): Promise<CacheHandlerValue | null>;
@@ -5,6 +5,17 @@ const CACHE_EXTENSION_REGEX = /\.(cache|fetch)$/;
5
5
  export function hasCacheExtension(key) {
6
6
  return CACHE_EXTENSION_REGEX.test(key);
7
7
  }
8
+ function isFetchCache(options) {
9
+ if (typeof options === "boolean") {
10
+ return options;
11
+ }
12
+ if (typeof options === "object") {
13
+ return (options.kindHint === "fetch" ||
14
+ options.fetchCache ||
15
+ options.kind === "FETCH");
16
+ }
17
+ return false;
18
+ }
8
19
  // We need to use globalThis client here as this class can be defined at load time in next 12 but client is not available at load time
9
20
  export default class S3Cache {
10
21
  constructor(_ctx) { }
@@ -14,14 +25,9 @@ export default class S3Cache {
14
25
  if (globalThis.disableIncrementalCache) {
15
26
  return null;
16
27
  }
17
- const isFetchCache = typeof options === "object"
18
- ? options.kindHint
19
- ? options.kindHint === "fetch"
20
- : options.fetchCache
21
- : options;
22
28
  const softTags = typeof options === "object" ? options.softTags : [];
23
29
  const tags = typeof options === "object" ? options.tags : [];
24
- return isFetchCache
30
+ return isFetchCache(options)
25
31
  ? this.getFetchCache(key, softTags, tags)
26
32
  : this.getIncrementalCache(key);
27
33
  }
@@ -42,6 +42,7 @@ export async function compileOpenNextConfig(baseDir, openNextConfigPath, nodeExt
42
42
  }
43
43
  export function compileOpenNextConfigNode(sourcePath, outputDir, externals) {
44
44
  const outputPath = path.join(outputDir, "open-next.config.mjs");
45
+ logger.debug("Compiling open-next.config.ts for Node.", outputPath);
45
46
  //Check if open-next.config.ts exists
46
47
  if (!fs.existsSync(sourcePath)) {
47
48
  //Create a simple open-next.config.mjs file
@@ -71,7 +72,7 @@ export function compileOpenNextConfigNode(sourcePath, outputDir, externals) {
71
72
  }
72
73
  export function compileOpenNextConfigEdge(sourcePath, outputDir, externals) {
73
74
  const outputPath = path.join(outputDir, "open-next.config.edge.mjs");
74
- logger.info("Compiling open-next.config.ts for edge runtime.", outputPath);
75
+ logger.debug("Compiling open-next.config.ts for edge runtime.", outputPath);
75
76
  buildSync({
76
77
  entryPoints: [sourcePath],
77
78
  outfile: outputPath,
@@ -82,5 +83,4 @@ export function compileOpenNextConfigEdge(sourcePath, outputDir, externals) {
82
83
  platform: "browser",
83
84
  external: externals,
84
85
  });
85
- logger.info("Compiled open-next.config.ts for edge runtime.");
86
86
  }
@@ -54,7 +54,7 @@ export async function copyTracedFiles(buildOutputPath, packagePath, outputDir, r
54
54
  throw new Error(`
55
55
  --------------------------------------------------------------------------------
56
56
  ${pagePath} cannot use the edge runtime.
57
- OpenNext requires edge runtime function to be defined in a separate function.
57
+ OpenNext requires edge runtime function to be defined in a separate function.
58
58
  See the docs for more information on how to bundle edge runtime functions.
59
59
  --------------------------------------------------------------------------------
60
60
  `);
@@ -71,7 +71,7 @@ File ${fullFilePath} does not exist
71
71
  extractFiles(requiredFiles.files, path.join(standaloneNextDir, dir)).forEach((f) => {
72
72
  filesToCopy.set(f, f.replace(standaloneDir, outputDir));
73
73
  });
74
- if (!existsSync(path.join(standaloneNextDir, `${fullFilePath}`))) {
74
+ if (!existsSync(path.join(standaloneNextDir, fullFilePath))) {
75
75
  throw new Error(`This error should only happen for static 404 and 500 page from page router. Report this if that's not the case.,
76
76
  File ${fullFilePath} does not exist`);
77
77
  }
@@ -75,5 +75,8 @@ export async function createImageOptimizationBundle(options) {
75
75
  const sharpVersion = process.env.SHARP_VERSION ?? "0.32.6";
76
76
  installDependencies(outputPath, config.imageOptimization?.install ?? {
77
77
  packages: [`sharp@${sharpVersion}`],
78
+ arch: "arm64",
79
+ nodeVersion: "18",
80
+ libc: "glibc",
78
81
  });
79
82
  }
@@ -3,8 +3,8 @@ import * as buildHelper from "./helper.js";
3
3
  * Compiles the middleware bundle.
4
4
  *
5
5
  * @param options Build Options.
6
- * @returns Whether the app uses a Middleware.
6
+ * @param forceOnlyBuildOnce force to build only once.
7
7
  */
8
- export declare function createMiddleware(options: buildHelper.BuildOptions): Promise<{
9
- useMiddleware: boolean;
10
- }>;
8
+ export declare function createMiddleware(options: buildHelper.BuildOptions, { forceOnlyBuildOnce }?: {
9
+ forceOnlyBuildOnce?: boolean | undefined;
10
+ }): Promise<void>;
@@ -8,26 +8,16 @@ import { installDependencies } from "./installDeps.js";
8
8
  * Compiles the middleware bundle.
9
9
  *
10
10
  * @param options Build Options.
11
- * @returns Whether the app uses a Middleware.
11
+ * @param forceOnlyBuildOnce force to build only once.
12
12
  */
13
- export async function createMiddleware(options) {
13
+ export async function createMiddleware(options, { forceOnlyBuildOnce = false } = {}) {
14
14
  logger.info(`Bundling middleware function...`);
15
15
  const { appBuildOutputPath, config, outputDir } = options;
16
16
  // Get middleware manifest
17
17
  const middlewareManifest = JSON.parse(fs.readFileSync(path.join(appBuildOutputPath, ".next/server/middleware-manifest.json"), "utf8"));
18
- const entry = middlewareManifest.middleware["/"];
19
- if (!entry) {
20
- return { useMiddleware: false };
21
- }
22
- // Create output folder
23
- let outputPath = path.join(outputDir, "server-function");
24
- const commonMiddlewareOptions = {
25
- middlewareInfo: entry,
26
- options,
27
- appBuildOutputPath,
28
- };
18
+ const middlewareInfo = middlewareManifest.middleware["/"];
29
19
  if (config.middleware?.external) {
30
- outputPath = path.join(outputDir, "middleware");
20
+ const outputPath = path.join(outputDir, "middleware");
31
21
  fs.mkdirSync(outputPath, { recursive: true });
32
22
  // Copy open-next.config.mjs
33
23
  buildHelper.copyOpenNextConfig(options.buildDir, outputPath, config.middleware.override?.wrapper === "cloudflare");
@@ -35,11 +25,13 @@ export async function createMiddleware(options) {
35
25
  await buildEdgeBundle({
36
26
  entrypoint: path.join(options.openNextDistDir, "adapters", "middleware.js"),
37
27
  outfile: path.join(outputPath, "handler.mjs"),
38
- ...commonMiddlewareOptions,
28
+ middlewareInfo,
29
+ options,
39
30
  overrides: config.middleware?.override,
40
31
  defaultConverter: "aws-cloudfront",
41
32
  includeCache: config.dangerous?.enableCacheInterception,
42
33
  additionalExternals: config.edgeExternals,
34
+ onlyBuildOnce: forceOnlyBuildOnce === true,
43
35
  });
44
36
  installDependencies(outputPath, config.middleware?.install);
45
37
  }
@@ -47,9 +39,9 @@ export async function createMiddleware(options) {
47
39
  await buildEdgeBundle({
48
40
  entrypoint: path.join(options.openNextDistDir, "core", "edgeFunctionHandler.js"),
49
41
  outfile: path.join(options.buildDir, "middleware.mjs"),
50
- ...commonMiddlewareOptions,
42
+ middlewareInfo,
43
+ options,
51
44
  onlyBuildOnce: true,
52
45
  });
53
46
  }
54
- return { useMiddleware: true };
55
47
  }
@@ -1,5 +1,4 @@
1
1
  import fs from "node:fs";
2
- import { createRequire } from "node:module";
3
2
  import path from "node:path";
4
3
  import logger from "../logger.js";
5
4
  import { minifyAll } from "../minimize-js.js";
@@ -11,7 +10,6 @@ import { copyTracedFiles } from "./copyTracedFiles.js";
11
10
  import { generateEdgeBundle } from "./edge/createEdgeBundle.js";
12
11
  import * as buildHelper from "./helper.js";
13
12
  import { installDependencies } from "./installDeps.js";
14
- const require = createRequire(import.meta.url);
15
13
  export async function createServerBundle(options) {
16
14
  const { config } = options;
17
15
  const foundRoutes = new Set();
@@ -27,17 +25,17 @@ export async function createServerBundle(options) {
27
25
  const routes = fnOptions.routes;
28
26
  routes.forEach((route) => foundRoutes.add(route));
29
27
  if (fnOptions.runtime === "edge") {
30
- await generateEdgeBundle(name, config, options, fnOptions);
28
+ await generateEdgeBundle(name, options, fnOptions);
31
29
  }
32
30
  else {
33
- await generateBundle(name, config, options, fnOptions);
31
+ await generateBundle(name, options, fnOptions);
34
32
  }
35
33
  });
36
34
  //TODO: throw an error if not all edge runtime routes has been bundled in a separate function
37
35
  // We build every other function than default before so we know which route there is left
38
36
  await Promise.all(promises);
39
37
  const remainingRoutes = new Set();
40
- const { monorepoRoot, appBuildOutputPath } = options;
38
+ const { appBuildOutputPath, monorepoRoot } = options;
41
39
  const packagePath = path.relative(monorepoRoot, appBuildOutputPath);
42
40
  // Find remaining routes
43
41
  const serverPath = path.join(appBuildOutputPath, ".next", "standalone", packagePath, ".next", "server");
@@ -68,15 +66,15 @@ export async function createServerBundle(options) {
68
66
  }, () => { });
69
67
  }
70
68
  // Generate default function
71
- await generateBundle("default", config, options, {
69
+ await generateBundle("default", options, {
72
70
  ...defaultFn,
73
71
  // @ts-expect-error - Those string are RouteTemplate
74
72
  routes: Array.from(remainingRoutes),
75
73
  patterns: ["*"],
76
74
  });
77
75
  }
78
- async function generateBundle(name, config, options, fnOptions) {
79
- const { appPath, appBuildOutputPath, outputDir, monorepoRoot } = options;
76
+ async function generateBundle(name, options, fnOptions) {
77
+ const { appPath, appBuildOutputPath, config, outputDir, monorepoRoot } = options;
80
78
  logger.info(`Building server function: ${name}...`);
81
79
  // Create output folder
82
80
  const outputPath = path.join(outputDir, "server-functions", name);
@@ -99,8 +97,7 @@ async function generateBundle(name, config, options, fnOptions) {
99
97
  await bundleNextServer(path.join(outputPath, packagePath), appPath);
100
98
  }
101
99
  // Copy middleware
102
- if (!config.middleware?.external &&
103
- fs.existsSync(path.join(options.buildDir, "middleware.mjs"))) {
100
+ if (!config.middleware?.external) {
104
101
  fs.copyFileSync(path.join(options.buildDir, "middleware.mjs"), path.join(outputPath, packagePath, "middleware.mjs"));
105
102
  }
106
103
  // Copy open-next.config.mjs
@@ -123,12 +120,10 @@ async function generateBundle(name, config, options, fnOptions) {
123
120
  openNextReplacementPlugin({
124
121
  name: `requestHandlerOverride ${name}`,
125
122
  target: /core(\/|\\)requestHandler\.js/g,
126
- deletes: disableNextPrebundledReact ? ["applyNextjsPrebundledReact"] : [],
127
- replacements: disableRouting
128
- ? [
129
- require.resolve("../adapters/plugins/without-routing/requestHandler.js"),
130
- ]
131
- : [],
123
+ deletes: [
124
+ ...(disableNextPrebundledReact ? ["applyNextjsPrebundledReact"] : []),
125
+ ...(disableRouting ? ["withRouting"] : []),
126
+ ],
132
127
  }),
133
128
  openNextReplacementPlugin({
134
129
  name: `utilOverride ${name}`,
@@ -1,9 +1,8 @@
1
1
  import type { MiddlewareInfo } from "../../types/next-types";
2
- import type { IncludedConverter, OpenNextConfig, OverrideOptions, SplittedFunctionOptions } from "../../types/open-next";
2
+ import type { IncludedConverter, OverrideOptions, SplittedFunctionOptions } from "../../types/open-next";
3
3
  import type { BuildOptions } from "../helper.js";
4
4
  interface BuildEdgeBundleOptions {
5
- appBuildOutputPath: string;
6
- middlewareInfo: MiddlewareInfo;
5
+ middlewareInfo?: MiddlewareInfo;
7
6
  entrypoint: string;
8
7
  outfile: string;
9
8
  options: BuildOptions;
@@ -14,7 +13,6 @@ interface BuildEdgeBundleOptions {
14
13
  additionalExternals?: string[];
15
14
  onlyBuildOnce?: boolean;
16
15
  }
17
- export declare function buildEdgeBundle({ appBuildOutputPath, middlewareInfo, entrypoint, outfile, options, defaultConverter, overrides, additionalInject, includeCache, additionalExternals, onlyBuildOnce, }: BuildEdgeBundleOptions): Promise<void>;
18
- export declare function copyMiddlewareAssetsAndWasm({}: {}): void;
19
- export declare function generateEdgeBundle(name: string, config: OpenNextConfig, options: BuildOptions, fnOptions: SplittedFunctionOptions): Promise<void>;
16
+ export declare function buildEdgeBundle({ middlewareInfo, entrypoint, outfile, options, defaultConverter, overrides, additionalInject, includeCache, additionalExternals, onlyBuildOnce, }: BuildEdgeBundleOptions): Promise<void>;
17
+ export declare function generateEdgeBundle(name: string, options: BuildOptions, fnOptions: SplittedFunctionOptions): Promise<void>;
20
18
  export {};
@@ -7,7 +7,7 @@ import { openNextEdgePlugins } from "../../plugins/edge.js";
7
7
  import { openNextReplacementPlugin } from "../../plugins/replacement.js";
8
8
  import { openNextResolvePlugin } from "../../plugins/resolve.js";
9
9
  import { copyOpenNextConfig, esbuildAsync } from "../helper.js";
10
- export async function buildEdgeBundle({ appBuildOutputPath, middlewareInfo, entrypoint, outfile, options, defaultConverter, overrides, additionalInject, includeCache, additionalExternals, onlyBuildOnce, }) {
10
+ export async function buildEdgeBundle({ middlewareInfo, entrypoint, outfile, options, defaultConverter, overrides, additionalInject, includeCache, additionalExternals, onlyBuildOnce, }) {
11
11
  const isInCloudfare = typeof overrides?.wrapper === "string"
12
12
  ? overrides.wrapper === "cloudflare"
13
13
  : (await overrides?.wrapper?.())?.edgeRuntime;
@@ -50,7 +50,7 @@ export async function buildEdgeBundle({ appBuildOutputPath, middlewareInfo, entr
50
50
  }),
51
51
  openNextEdgePlugins({
52
52
  middlewareInfo,
53
- nextDir: path.join(appBuildOutputPath, ".next"),
53
+ nextDir: path.join(options.appBuildOutputPath, ".next"),
54
54
  edgeFunctionHandlerPath: path.join(options.openNextDistDir, "core", "edgeFunctionHandler.js"),
55
55
  isInCloudfare,
56
56
  }),
@@ -106,8 +106,7 @@ globalThis.AsyncLocalStorage = AsyncLocalStorage;
106
106
  });
107
107
  }
108
108
  }
109
- export function copyMiddlewareAssetsAndWasm({}) { }
110
- export async function generateEdgeBundle(name, config, options, fnOptions) {
109
+ export async function generateEdgeBundle(name, options, fnOptions) {
111
110
  const { appBuildOutputPath, outputDir } = options;
112
111
  logger.info(`Generating edge bundle for: ${name}`);
113
112
  // Create output folder
@@ -122,26 +121,25 @@ export async function generateEdgeBundle(name, config, options, fnOptions) {
122
121
  if (functions.length > 1) {
123
122
  throw new Error("Only one function is supported for now");
124
123
  }
125
- const fn = functions[0];
124
+ const middlewareInfo = functions[0];
126
125
  //Copy wasm files
127
- const wasmFiles = fn.wasm;
126
+ const wasmFiles = middlewareInfo.wasm;
128
127
  mkdirSync(path.join(outputPath, "wasm"), { recursive: true });
129
128
  for (const wasmFile of wasmFiles) {
130
129
  fs.copyFileSync(path.join(appBuildOutputPath, ".next", wasmFile.filePath), path.join(outputPath, `wasm/${wasmFile.name}.wasm`));
131
130
  }
132
131
  // Copy assets
133
- const assets = fn.assets;
132
+ const assets = middlewareInfo.assets;
134
133
  mkdirSync(path.join(outputPath, "assets"), { recursive: true });
135
134
  for (const asset of assets) {
136
135
  fs.copyFileSync(path.join(appBuildOutputPath, ".next", asset.filePath), path.join(outputPath, `assets/${asset.name}`));
137
136
  }
138
137
  await buildEdgeBundle({
139
- appBuildOutputPath,
140
- middlewareInfo: fn,
138
+ middlewareInfo,
141
139
  entrypoint: path.join(options.openNextDistDir, "adapters", "edge-adapter.js"),
142
140
  outfile: path.join(outputPath, "index.mjs"),
143
141
  options,
144
142
  overrides: fnOptions.override,
145
- additionalExternals: config.edgeExternals,
143
+ additionalExternals: options.config.edgeExternals,
146
144
  });
147
145
  }
@@ -14,7 +14,17 @@ export function installDependencies(outputDir, installOptions) {
14
14
  logger.info(`Installing dependencies for ${name}...`);
15
15
  // We then need to run install in the tempDir
16
16
  // We don't install in the output dir directly because it could contain a package.json, and npm would then try to reinstall not complete deps from tracing the files
17
- const installCommand = `npm install --arch=${installOptions.arch ?? "arm64"} --platform=linux --target=${installOptions.nodeVersion ?? "18"} --libc=${installOptions.libc ?? "glibc"} ${installOptions.packages.join(" ")}`;
17
+ const archOption = installOptions.arch
18
+ ? `--arch=${installOptions.arch}`
19
+ : "";
20
+ const targetOption = installOptions.nodeVersion
21
+ ? `--target=${installOptions.nodeVersion}`
22
+ : "";
23
+ const libcOption = installOptions.libc
24
+ ? `--libc=${installOptions.libc}`
25
+ : "";
26
+ const additionalArgs = installOptions.additionalArgs ?? "";
27
+ const installCommand = `npm install --platform=linux ${archOption} ${targetOption} ${libcOption} ${additionalArgs} ${installOptions.packages.join(" ")}`;
18
28
  execSync(installCommand, {
19
29
  stdio: "pipe",
20
30
  cwd: tempInstallDir,
@@ -7,10 +7,10 @@ export async function createGenericHandler(handler) {
7
7
  globalThis.openNextConfig = config;
8
8
  const override = config[handler.type]
9
9
  ?.override;
10
- // From the config, we create the adapter
11
- const adapter = await resolveConverter(override?.converter);
10
+ // From the config, we create the converter
11
+ const converter = await resolveConverter(override?.converter);
12
12
  // Then we create the handler
13
- const wrapper = await resolveWrapper(override?.wrapper);
14
- debug("Using wrapper", wrapper.name);
15
- return wrapper.wrapper(handler.handler, adapter);
13
+ const { name, wrapper } = await resolveWrapper(override?.wrapper);
14
+ debug("Using wrapper", name);
15
+ return wrapper(handler.handler, converter);
16
16
  }
@@ -15,10 +15,10 @@ export async function createMainHandler() {
15
15
  globalThis.incrementalCache = await resolveIncrementalCache(thisFunction.override?.incrementalCache);
16
16
  globalThis.tagCache = await resolveTagCache(thisFunction.override?.tagCache);
17
17
  globalThis.lastModified = {};
18
- // From the config, we create the adapter
19
- const adapter = await resolveConverter(thisFunction.override?.converter);
18
+ // From the config, we create the converter
19
+ const converter = await resolveConverter(thisFunction.override?.converter);
20
20
  // Then we create the handler
21
- const wrapper = await resolveWrapper(thisFunction.override?.wrapper);
22
- debug("Using wrapper", wrapper.name);
23
- return wrapper.wrapper(openNextHandler, adapter);
21
+ const { wrapper, name } = await resolveWrapper(thisFunction.override?.wrapper);
22
+ debug("Using wrapper", name);
23
+ return wrapper(openNextHandler, converter);
24
24
  }
@@ -14,13 +14,13 @@ export async function openNextHandler(internalEvent, responseStreaming) {
14
14
  internalEvent.headers.host = internalEvent.headers["x-forwarded-host"];
15
15
  }
16
16
  debug("internalEvent", internalEvent);
17
- //#override withRouting
18
17
  let preprocessResult = {
19
18
  internalEvent: internalEvent,
20
19
  isExternalRewrite: false,
21
20
  origin: false,
22
21
  isISR: false,
23
22
  };
23
+ //#override withRouting
24
24
  try {
25
25
  preprocessResult = await routingHandler(internalEvent);
26
26
  }
@@ -122,10 +122,16 @@ export function handleRewrites(event, rewrites) {
122
122
  const { pathname, protocol, hostname, queryString } = getUrlParts(rewrite.destination, isExternalRewrite);
123
123
  // We need to use a localized path if the rewrite is not locale specific
124
124
  const pathToUse = rewrite.locale === false ? rawPath : localizedRawPath;
125
+ // We need to encode the "+" character with its UTF-8 equivalent "%20" to avoid 2 issues:
126
+ // 1. The compile function from path-to-regexp will throw an error if it finds a "+" character.
127
+ // https://github.com/pillarjs/path-to-regexp?tab=readme-ov-file#unexpected--or-
128
+ // 2. The convertToQueryString function will replace the "+" character with %2B instead of %20.
129
+ // %2B does not get interpreted as a space in the URL thus breaking the query string.
130
+ const encodePlusQueryString = queryString.replaceAll("+", "%20");
125
131
  debug("urlParts", { pathname, protocol, hostname, queryString });
126
132
  const toDestinationPath = compile(escapeRegex(pathname ?? "") ?? "");
127
133
  const toDestinationHost = compile(escapeRegex(hostname ?? "") ?? "");
128
- const toDestinationQuery = compile(escapeRegex(queryString ?? "") ?? "");
134
+ const toDestinationQuery = compile(escapeRegex(encodePlusQueryString ?? "") ?? "");
129
135
  let params = {
130
136
  // params for the source
131
137
  ...getParamsFromSource(match(escapeRegex(rewrite?.source) ?? ""))(pathToUse),
@@ -139,7 +145,7 @@ export function handleRewrites(event, rewrites) {
139
145
  }, {}),
140
146
  };
141
147
  const isUsingParams = Object.keys(params).length > 0;
142
- let rewrittenQuery = queryString;
148
+ let rewrittenQuery = encodePlusQueryString;
143
149
  let rewrittenHost = hostname;
144
150
  let rewrittenPath = pathname;
145
151
  if (isUsingParams) {
@@ -149,7 +155,7 @@ export function handleRewrites(event, rewrites) {
149
155
  }
150
156
  rewrittenUrl = isExternalRewrite
151
157
  ? `${protocol}//${rewrittenHost}${rewrittenPath}`
152
- : `/${rewrittenPath}`;
158
+ : `${rewrittenPath}`;
153
159
  // Should we merge the query params or use only the ones from the rewrite?
154
160
  finalQuery = {
155
161
  ...query,
@@ -244,8 +250,9 @@ export function fixDataPage(internalEvent, buildId) {
244
250
  };
245
251
  }
246
252
  if (rawPath.startsWith(dataPattern) && rawPath.endsWith(".json")) {
247
- let newPath = rawPath.replace(dataPattern, "").replace(/\.json$/, "");
248
- newPath = newPath === "/index" ? "/" : newPath;
253
+ const newPath = rawPath
254
+ .slice(dataPattern.length, -".json".length)
255
+ .replace(/^\/index$/, "/");
249
256
  query.__nextDataReq = "1";
250
257
  return {
251
258
  ...internalEvent,
@@ -30,13 +30,16 @@ export async function handleMiddleware(internalEvent, middlewareLoader = default
30
30
  // We bypass the middleware if the request is internal
31
31
  if (internalEvent.headers["x-isr"])
32
32
  return internalEvent;
33
+ // Retrieve the protocol:
34
+ // - In lambda, the url only contains the rawPath and the query - default to https
35
+ // - In cloudflare, the protocol is usually http in dev and https in production
36
+ const protocol = internalEvent.url.startsWith("http://") ? "http:" : "https:";
33
37
  const host = internalEvent.headers.host
34
- ? `https://${internalEvent.headers.host}`
38
+ ? `${protocol}//${internalEvent.headers.host}`
35
39
  : "http://localhost:3000";
36
40
  const initialUrl = new URL(normalizedPath, host);
37
41
  initialUrl.search = convertToQueryString(query);
38
42
  const url = initialUrl.toString();
39
- // console.log("url", url, normalizedPath);
40
43
  const middleware = await middlewareLoader();
41
44
  const result = await middleware.default({
42
45
  geo: {
@@ -96,7 +99,7 @@ export async function handleMiddleware(internalEvent, middlewareLoader = default
96
99
  resHeaders.location =
97
100
  responseHeaders
98
101
  .get("location")
99
- ?.replace("http://localhost:3000", `https://${internalEvent.headers.host}`) ?? resHeaders.location;
102
+ ?.replace("http://localhost:3000", `${protocol}//${internalEvent.headers.host}`) ?? resHeaders.location;
100
103
  // res.setHeader("Location", location);
101
104
  return {
102
105
  body: emptyReadableStream(),
@@ -37,7 +37,7 @@ export function getUrlParts(url, isExternal) {
37
37
  const match = url.match(regex);
38
38
  return {
39
39
  hostname: "",
40
- pathname: match?.[1] ?? url,
40
+ pathname: match?.[1] ? `/${match[1]}` : url,
41
41
  protocol: "",
42
42
  queryString: match?.[2] ?? "",
43
43
  };
@@ -50,7 +50,7 @@ export function getUrlParts(url, isExternal) {
50
50
  return {
51
51
  protocol: match[1] ?? "https:",
52
52
  hostname: match[2],
53
- pathname: match[3],
53
+ pathname: match[3] ?? "",
54
54
  queryString: match[4]?.slice(1) ?? "",
55
55
  };
56
56
  }
@@ -84,13 +84,16 @@ export function convertRes(res) {
84
84
  * @__PURE__
85
85
  */
86
86
  export function convertToQueryString(query) {
87
+ // URLSearchParams is a representation of the PARSED query.
88
+ // So we must decode the value before appending it to the URLSearchParams.
89
+ // https://stackoverflow.com/a/45516812
87
90
  const urlQuery = new URLSearchParams();
88
91
  Object.entries(query).forEach(([key, value]) => {
89
92
  if (Array.isArray(value)) {
90
- value.forEach((entry) => urlQuery.append(key, entry));
93
+ value.forEach((entry) => urlQuery.append(key, decodeURIComponent(entry)));
91
94
  }
92
95
  else {
93
- urlQuery.append(key, value);
96
+ urlQuery.append(key, decodeURIComponent(value));
94
97
  }
95
98
  });
96
99
  const queryString = urlQuery.toString();
@@ -30,7 +30,7 @@ function applyMiddlewareHeaders(eventHeaders, middlewareHeaders, setPrefix = tru
30
30
  });
31
31
  }
32
32
  export default async function routingHandler(event) {
33
- const nextHeaders = addNextConfigHeaders(event, ConfigHeaders) ?? {};
33
+ const nextHeaders = addNextConfigHeaders(event, ConfigHeaders);
34
34
  let internalEvent = fixDataPage(event, BuildId);
35
35
  if ("statusCode" in internalEvent) {
36
36
  return internalEvent;
@@ -1,5 +1,8 @@
1
1
  import type { Converter, InternalEvent, InternalResult } from "../../types/open-next";
2
2
  import type { MiddlewareOutputEvent } from "../../core/routingHandler";
3
+ declare global {
4
+ var __dangerous_ON_edge_converter_returns_request: boolean | undefined;
5
+ }
3
6
  declare const converter: Converter<InternalEvent, InternalResult | ({
4
7
  type: "middleware";
5
8
  } & MiddlewareOutputEvent)>;
@@ -52,7 +52,7 @@ const converter = {
52
52
  url = `https://${result.internalEvent.headers.host}${url}`;
53
53
  }
54
54
  }
55
- const req = new Request(url, {
55
+ const request = new Request(url, {
56
56
  body: result.internalEvent.body,
57
57
  method: result.internalEvent.method,
58
58
  headers: {
@@ -60,12 +60,15 @@ const converter = {
60
60
  "x-forwarded-host": result.internalEvent.headers.host,
61
61
  },
62
62
  });
63
+ if (globalThis.__dangerous_ON_edge_converter_returns_request === true) {
64
+ return request;
65
+ }
63
66
  const cfCache = (result.isISR ||
64
67
  result.internalEvent.rawPath.startsWith("/_next/image")) &&
65
68
  process.env.DISABLE_CACHE !== "true"
66
69
  ? { cacheEverything: true }
67
70
  : {};
68
- return fetch(req, {
71
+ return fetch(request, {
69
72
  // This is a hack to make sure that the response is cached by Cloudflare
70
73
  // See https://developers.cloudflare.com/workers/examples/cache-using-fetch/#caching-html-resources
71
74
  // @ts-expect-error - This is a Cloudflare specific option
@@ -1,7 +1,7 @@
1
1
  const handler = async (handler, converter) => async (event, env) => {
2
2
  globalThis.process = process;
3
3
  // Set the environment variables
4
- // Cloudlare suggests to not override the process.env object but instead apply the values to it
4
+ // Cloudflare suggests to not override the process.env object but instead apply the values to it
5
5
  for (const [key, value] of Object.entries(env)) {
6
6
  if (typeof value === "string") {
7
7
  process.env[key] = value;
@@ -3,13 +3,14 @@ import type { MiddlewareInfo } from "../types/next-types.js";
3
3
  export interface IPluginSettings {
4
4
  nextDir: string;
5
5
  edgeFunctionHandlerPath?: string;
6
- middlewareInfo: MiddlewareInfo;
6
+ middlewareInfo?: MiddlewareInfo;
7
7
  isInCloudfare?: boolean;
8
8
  }
9
9
  /**
10
10
  * @param opts.nextDir - The path to the .next directory
11
11
  * @param opts.edgeFunctionHandlerPath - The path to the edgeFunctionHandler.js file that we'll use to bundle the routing
12
- * @param opts.entryFiles - The entry files that we'll inject into the edgeFunctionHandler.js file
12
+ * @param opts.middlewareInfo - Information about the middleware
13
+ * @param opts.isInCloudfare - Whether the code runs on the cloudflare runtime
13
14
  * @returns
14
15
  */
15
16
  export declare function openNextEdgePlugins({ nextDir, edgeFunctionHandlerPath, middlewareInfo, isInCloudfare, }: IPluginSettings): Plugin;
@@ -4,19 +4,22 @@ import { loadAppPathsManifestKeys, loadBuildId, loadConfig, loadConfigHeaders, l
4
4
  /**
5
5
  * @param opts.nextDir - The path to the .next directory
6
6
  * @param opts.edgeFunctionHandlerPath - The path to the edgeFunctionHandler.js file that we'll use to bundle the routing
7
- * @param opts.entryFiles - The entry files that we'll inject into the edgeFunctionHandler.js file
7
+ * @param opts.middlewareInfo - Information about the middleware
8
+ * @param opts.isInCloudfare - Whether the code runs on the cloudflare runtime
8
9
  * @returns
9
10
  */
10
11
  export function openNextEdgePlugins({ nextDir, edgeFunctionHandlerPath, middlewareInfo, isInCloudfare, }) {
11
- const entryFiles = middlewareInfo.files.map((file) => path.join(nextDir, file));
12
- const routes = [
13
- {
14
- name: middlewareInfo.name || "/",
15
- page: middlewareInfo.page,
16
- regex: middlewareInfo.matchers.map((m) => m.regexp),
17
- },
18
- ];
19
- const wasmFiles = middlewareInfo.wasm ?? [];
12
+ const entryFiles = middlewareInfo?.files.map((file) => path.join(nextDir, file)) ?? [];
13
+ const routes = middlewareInfo
14
+ ? [
15
+ {
16
+ name: middlewareInfo.name || "/",
17
+ page: middlewareInfo.page,
18
+ regex: middlewareInfo.matchers.map((m) => m.regexp),
19
+ },
20
+ ]
21
+ : [];
22
+ const wasmFiles = middlewareInfo?.wasm ?? [];
20
23
  return {
21
24
  name: "opennext-edge",
22
25
  setup(build) {
@@ -44,12 +47,10 @@ export function openNextEdgePlugins({ nextDir, edgeFunctionHandlerPath, middlewa
44
47
  });
45
48
  // we convert the imports we tagged with the node-built-in-modules namespace so that instead of `require("node:*")`
46
49
  // they import from `export * from "node:*";`
47
- build.onLoad({ filter: /.*/, namespace: "node-built-in-modules" }, ({ path }) => {
48
- return {
49
- contents: `export * from '${path}'`,
50
- loader: "js",
51
- };
52
- });
50
+ build.onLoad({ filter: /.*/, namespace: "node-built-in-modules" }, ({ path }) => ({
51
+ contents: `export * from '${path}'`,
52
+ loader: "js",
53
+ }));
53
54
  // We inject the entry files into the edgeFunctionHandler
54
55
  build.onLoad({ filter: /\/edgeFunctionHandler.js/g }, async (args) => {
55
56
  let contents = readFileSync(args.path, "utf-8");
@@ -97,7 +98,7 @@ if(!globalThis.Crypto) {
97
98
  globalThis.Crypto = webcrypto.Crypto
98
99
  }
99
100
  // We also need to polyfill URLPattern
100
- if (!globalThis.URLPattern) {
101
+ if (!globalThis.URLPattern) {
101
102
  await import("urlpattern-polyfill");
102
103
  }
103
104
  `}
@@ -106,8 +107,8 @@ ${wasmFiles
106
107
  ? `import ${file.name} from './wasm/${file.name}.wasm';`
107
108
  : `const ${file.name} = readFileSync(path.join(__dirname,'/wasm/${file.name}.wasm'));`)
108
109
  .join("\n")}
109
- ${entryFiles?.map((file) => `require("${file}");`).join("\n")}
110
- ${contents}
110
+ ${entryFiles.map((file) => `require("${file}");`).join("\n")}
111
+ ${contents}
111
112
  `;
112
113
  return {
113
114
  contents,
@@ -124,18 +125,18 @@ ${contents}
124
125
  const MiddlewareManifest = loadMiddlewareManifest(nextDir);
125
126
  const contents = `
126
127
  import path from "path";
127
-
128
+
128
129
  import { debug } from "../logger";
129
-
130
+
130
131
  if(!globalThis.__dirname) {
131
132
  globalThis.__dirname = ""
132
133
  }
133
-
134
+
134
135
  export const NEXT_DIR = path.join(__dirname, ".next");
135
136
  export const OPEN_NEXT_DIR = path.join(__dirname, ".open-next");
136
-
137
+
137
138
  debug({ NEXT_DIR, OPEN_NEXT_DIR });
138
-
139
+
139
140
  export const NextConfig = ${JSON.stringify(NextConfig)};
140
141
  export const BuildId = ${JSON.stringify(BuildId)};
141
142
  export const HtmlPages = ${JSON.stringify(HtmlPages)};
@@ -146,11 +147,8 @@ ${contents}
146
147
  export const MiddlewareManifest = ${JSON.stringify(MiddlewareManifest)};
147
148
 
148
149
  process.env.NEXT_BUILD_ID = BuildId;
149
-
150
- `;
151
- return {
152
- contents,
153
- };
150
+ `;
151
+ return { contents };
154
152
  });
155
153
  },
156
154
  };
@@ -13,7 +13,7 @@ export interface IPluginSettings {
13
13
  * deletes: ["id1"],
14
14
  * })
15
15
  *
16
- * To inject arbritary code by using (import at top of file):
16
+ * To inject arbitrary code by using (import at top of file):
17
17
  *
18
18
  * //#import
19
19
  *
@@ -11,7 +11,7 @@ const importPattern = /\/\/#import([\s\S]*?)\n\/\/#endImport/gm;
11
11
  * deletes: ["id1"],
12
12
  * })
13
13
  *
14
- * To inject arbritary code by using (import at top of file):
14
+ * To inject arbitrary code by using (import at top of file):
15
15
  *
16
16
  * //#import
17
17
  *
@@ -49,9 +49,8 @@ export function openNextReplacementPlugin({ target, replacements, deletes, name,
49
49
  logger.debug(chalk.blue(`Open-next replacement plugin ${name}`), ` -- Deleting override for ${id}`);
50
50
  contents = contents.replace(pattern, "");
51
51
  }),
52
- ...(replacements ?? []).map(async (fp) => {
53
- const p = fp;
54
- const replacementFile = await readFile(p, "utf-8");
52
+ ...(replacements ?? []).map(async (filename) => {
53
+ const replacementFile = await readFile(filename, "utf-8");
55
54
  const matches = replacementFile.matchAll(overridePattern);
56
55
  const importMatch = replacementFile.match(importPattern);
57
56
  const addedImport = importMatch ? importMatch[0] : "";
@@ -60,7 +59,7 @@ export function openNextReplacementPlugin({ target, replacements, deletes, name,
60
59
  const replacement = match[2];
61
60
  const id = match[1];
62
61
  const pattern = new RegExp(`\/\/#override (${id})\n([\\s\\S]*?)\/\/#endOverride`, "g");
63
- logger.debug(chalk.blue(`Open-next replacement plugin ${name}`), `-- Applying override for ${id} from ${fp}`);
62
+ logger.debug(chalk.blue(`Open-next replacement plugin ${name}`), `-- Applying override for ${id} from ${filename}`);
64
63
  contents = contents.replace(pattern, replacement);
65
64
  }
66
65
  }),
@@ -143,17 +143,22 @@ export interface InstallOptions {
143
143
  */
144
144
  packages: string[];
145
145
  /**
146
- * @default "arm64"
146
+ * @default undefined
147
147
  */
148
148
  arch?: "x64" | "arm64";
149
149
  /**
150
- * @default "18"
150
+ * @default undefined
151
151
  */
152
- nodeVersion?: "18" | "20" | "22";
152
+ nodeVersion?: string;
153
153
  /**
154
- * @default "glibc"
154
+ * @default undefined
155
155
  */
156
156
  libc?: "glibc" | "musl";
157
+ /**
158
+ * @default undefined
159
+ * Additional arguments to pass to the install command (i.e. npm install)
160
+ */
161
+ additionalArgs?: string;
157
162
  }
158
163
  export interface DefaultFunctionOptions<E extends BaseEventOrResult = InternalEvent, R extends BaseEventOrResult = InternalResult> {
159
164
  /**
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "access": "public"
4
4
  },
5
5
  "name": "@opennextjs/aws",
6
- "version": "3.2.0",
6
+ "version": "3.2.1",
7
7
  "bin": {
8
8
  "open-next": "./dist/index.js"
9
9
  },
@@ -1,2 +0,0 @@
1
- declare const _default: {};
2
- export default _default;
@@ -1,10 +0,0 @@
1
- //#override withRouting
2
- const preprocessResult = {
3
- internalEvent: internalEvent,
4
- isExternalRewrite: false,
5
- origin: false,
6
- isISR: false,
7
- };
8
- //#endOverride
9
- // We need to export something otherwise when compiled in js it creates an empty export {} inside the override
10
- export default {};