@opennextjs/aws 4.1.10 → 4.1.12

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.
@@ -74,7 +74,7 @@ export async function createImageOptimizationBundle(options) {
74
74
  // Sharp provides pre-build binaries for all platforms. https://github.com/lovell/sharp/blob/main/docs/install.md#cross-platform
75
75
  // Target should be same as used by Lambda, see https://github.com/sst/sst/blob/ca6f763fdfddd099ce2260202d0ce48c72e211ea/packages/sst/src/constructs/NextjsSite.ts#L114
76
76
  // For SHARP_IGNORE_GLOBAL_LIBVIPS see: https://github.com/lovell/sharp/blob/main/docs/install.md#aws-lambda
77
- const sharpVersion = process.env.SHARP_VERSION ?? "0.35.4";
77
+ const sharpVersion = process.env.SHARP_VERSION ?? "0.35.5";
78
78
  // In development, we want to use the local machine settings
79
79
  const isDev = config.imageOptimization?.loader === "fs-dev";
80
80
  installDependencies(outputPath, config.imageOptimization?.install ?? {
@@ -66,7 +66,10 @@ export type { SemverOp } from "../utils/semver.js";
66
66
  export { compareSemver } from "../utils/semver.js";
67
67
  /**
68
68
  * Check if a Next.js version is supported by the Next.js team.
69
+ *
69
70
  * Major versions are supported for 2 years following their initial release.
71
+ * A major version is also considered supported as long as it is the Active or Maintenance LTS,
72
+ * that is until the second next major version is released (i.e. 15 is supported until 17 is released).
70
73
  *
71
74
  * @param version - The Next.js version to check (semver format)
72
75
  * @returns true if the version is currently supported, false otherwise
@@ -235,7 +235,10 @@ const NEXT_RELEASE_DATES = {
235
235
  };
236
236
  /**
237
237
  * Check if a Next.js version is supported by the Next.js team.
238
+ *
238
239
  * Major versions are supported for 2 years following their initial release.
240
+ * A major version is also considered supported as long as it is the Active or Maintenance LTS,
241
+ * that is until the second next major version is released (i.e. 15 is supported until 17 is released).
239
242
  *
240
243
  * @param version - The Next.js version to check (semver format)
241
244
  * @returns true if the version is currently supported, false otherwise
@@ -245,10 +248,14 @@ export function isNextVersionSupported(version) {
245
248
  if (!match) {
246
249
  return false;
247
250
  }
248
- const releaseDateStr = NEXT_RELEASE_DATES[Number(match[0])];
251
+ const major = Number(match[0]);
252
+ const releaseDateStr = NEXT_RELEASE_DATES[major];
249
253
  if (!releaseDateStr) {
250
254
  return false;
251
255
  }
256
+ if (!NEXT_RELEASE_DATES[major + 2]) {
257
+ return true;
258
+ }
252
259
  const releaseDate = new Date(releaseDateStr);
253
260
  const supportEndDate = new Date(releaseDate);
254
261
  supportEndDate.setFullYear(supportEndDate.getFullYear() + 2);
@@ -1,3 +1,13 @@
1
1
  import type { CodePatcher } from "../codePatcher.js";
2
- export declare const rule = "\nrule:\n kind: call_expression\n pattern: $PROMISE\n all:\n - has: { pattern: $_.arrayBuffer().then, stopBy: end }\n - has: { pattern: \"Buffer.from\", stopBy: end }\n - any:\n - inside:\n kind: sequence_expression\n inside:\n kind: return_statement\n - inside:\n kind: expression_statement\n precedes:\n kind: return_statement\n - has: { pattern: $_.FETCH, stopBy: end }\n\nfix: |\n globalThis.__openNextAls?.getStore()?.pendingPromiseRunner.add($PROMISE)\n";
2
+ /**
3
+ * Keeps the runtime alive until a fetch cache entry is written.
4
+ *
5
+ * Next writes the fetch cache in the background (`res.arrayBuffer().then(...)`) without a `waitUntil`.
6
+ * The write is registered like the `waitUntil` provided to Next (see `provideNextAfterProvider`):
7
+ * with the runtime `waitUntil` when available, so that writes starting after the request has been
8
+ * drained (background revalidation) are covered, otherwise with the `pendingPromiseRunner`.
9
+ *
10
+ * The fix evaluates to the original promise, Next 15.4+ awaits it.
11
+ */
12
+ export declare const rule = "\nrule:\n kind: call_expression\n pattern: $PROMISE\n all:\n # `x.arrayBuffer().then(...).catch(...).finally(...)`.\n # Arguments are not searched so that enclosing calls (`getTracer().trace(...)` up to Next 15.4)\n # and the patched code do not match.\n - has: { pattern: $_.arrayBuffer().then, stopBy: { kind: arguments } }\n - has: { pattern: \"Buffer.from\", stopBy: end }\n - has: { pattern: $_.FETCH, stopBy: end }\n - any:\n - inside: { kind: sequence_expression, inside: { kind: return_statement } }\n - inside: { kind: expression_statement, precedes: { kind: return_statement } }\n # Next 15.4+: `const cacheSetPromise = cloned1.arrayBuffer().then(...)`\n - inside: { kind: variable_declarator }\n\nfix: |\n ((promise) => {\n const store = globalThis.__openNextAls?.getStore();\n if (store?.waitUntil) {\n store.waitUntil(promise);\n } else {\n store?.pendingPromiseRunner.add(promise);\n }\n return promise;\n })($PROMISE)\n";
3
13
  export declare const patchFetchCacheSetMissingWaitUntil: CodePatcher;
@@ -1,25 +1,42 @@
1
1
  import { getCrossPlatformPathRegex } from "../../../utils/regex.js";
2
2
  import { createPatchCode } from "../astCodePatcher.js";
3
+ /**
4
+ * Keeps the runtime alive until a fetch cache entry is written.
5
+ *
6
+ * Next writes the fetch cache in the background (`res.arrayBuffer().then(...)`) without a `waitUntil`.
7
+ * The write is registered like the `waitUntil` provided to Next (see `provideNextAfterProvider`):
8
+ * with the runtime `waitUntil` when available, so that writes starting after the request has been
9
+ * drained (background revalidation) are covered, otherwise with the `pendingPromiseRunner`.
10
+ *
11
+ * The fix evaluates to the original promise, Next 15.4+ awaits it.
12
+ */
3
13
  export const rule = `
4
14
  rule:
5
15
  kind: call_expression
6
16
  pattern: $PROMISE
7
17
  all:
8
- - has: { pattern: $_.arrayBuffer().then, stopBy: end }
18
+ # \`x.arrayBuffer().then(...).catch(...).finally(...)\`.
19
+ # Arguments are not searched so that enclosing calls (\`getTracer().trace(...)\` up to Next 15.4)
20
+ # and the patched code do not match.
21
+ - has: { pattern: $_.arrayBuffer().then, stopBy: { kind: arguments } }
9
22
  - has: { pattern: "Buffer.from", stopBy: end }
10
- - any:
11
- - inside:
12
- kind: sequence_expression
13
- inside:
14
- kind: return_statement
15
- - inside:
16
- kind: expression_statement
17
- precedes:
18
- kind: return_statement
19
23
  - has: { pattern: $_.FETCH, stopBy: end }
24
+ - any:
25
+ - inside: { kind: sequence_expression, inside: { kind: return_statement } }
26
+ - inside: { kind: expression_statement, precedes: { kind: return_statement } }
27
+ # Next 15.4+: \`const cacheSetPromise = cloned1.arrayBuffer().then(...)\`
28
+ - inside: { kind: variable_declarator }
20
29
 
21
30
  fix: |
22
- globalThis.__openNextAls?.getStore()?.pendingPromiseRunner.add($PROMISE)
31
+ ((promise) => {
32
+ const store = globalThis.__openNextAls?.getStore();
33
+ if (store?.waitUntil) {
34
+ store.waitUntil(promise);
35
+ } else {
36
+ store?.pendingPromiseRunner.add(promise);
37
+ }
38
+ return promise;
39
+ })($PROMISE)
23
40
  `;
24
41
  export const patchFetchCacheSetMissingWaitUntil = {
25
42
  name: "patch-fetch-cache-set-missing-wait-until",
@@ -1,3 +1,28 @@
1
- import type { InternalEvent, InternalResult } from "../types/open-next.js";
1
+ import type { OpenNextNodeResponse } from "../http/index.js";
2
+ import { IncomingMessage } from "../http/index.js";
3
+ import type { InternalEvent, InternalResult, RoutingResult } from "../types/open-next.js";
2
4
  import type { OpenNextHandlerOptions } from "../types/overrides.js";
3
5
  export declare function openNextHandler(internalEvent: InternalEvent, options?: OpenNextHandlerOptions): Promise<InternalResult>;
6
+ /**
7
+ * Renders the next resolved route after a `fallback: false` route threw a `NoFallbackError`.
8
+ *
9
+ * Mirrors the Next.js router server, which retries the next matching route:
10
+ * - Up to six attempts are made, including the initial render, before rendering a 500.
11
+ * - A 404 is rendered when no route candidate is left.
12
+ * - Another `NoFallbackError` retries the following candidate, any other error renders a 500.
13
+ *
14
+ * Next.js 16.4+ only renders the most specific route unless the route to render is passed as a
15
+ * `match` request meta (see `getRouteMatchMetadata`). A 400 is rendered when the params of that
16
+ * match can't be decoded, like Next.js does for a `DecodeError`. Older versions select the route
17
+ * from `invokeOutput` and receive exactly the same metadata as before.
18
+ *
19
+ * Errors are rendered as responses, this function does not throw.
20
+ *
21
+ * @param req The request reused for each rendering attempt
22
+ * @param res The response populated by Next.js
23
+ * @param routingResult The ordered route candidates and internal request
24
+ * @param metadata The request metadata of the initial attempt, mutated by Next.js (i.e. it contains the stale `match`)
25
+ * @param index The next candidate index, counting the initial render as index 0
26
+ * @return A promise that resolves after rendering a route or an error response
27
+ */
28
+ export declare function handleNoFallbackError(req: IncomingMessage, res: OpenNextNodeResponse, routingResult: RoutingResult, metadata: Record<string, unknown>, index?: number): Promise<void>;
@@ -2,9 +2,11 @@ import { AsyncLocalStorage } from "node:async_hooks";
2
2
  import { IncomingMessage } from "../http/index.js";
3
3
  import { ISR_HEADER } from "../utils/cacheHeaders.js";
4
4
  import { runWithOpenNextRequestContext } from "../utils/promise.js";
5
+ import { compareSemver } from "../utils/semver.js";
5
6
  import { NextConfig } from "../adapters/config/index.js";
6
7
  import { debug, error } from "../adapters/logger";
7
8
  import { patchAsyncStorage } from "./patchAsyncStorage";
9
+ import { getRouteMatchMetadata } from "./routeMatchMetadata";
8
10
  import { constructNextUrl, convertRes, convertToQuery, createServerResponse, } from "./routing/util";
9
11
  import routingHandler, { INTERNAL_EVENT_REQUEST_ID, INTERNAL_HEADER_REWRITE_STATUS_CODE, INTERNAL_HEADER_INITIAL_URL, INTERNAL_HEADER_RESOLVED_ROUTES, MIDDLEWARE_HEADER_PREFIX, MIDDLEWARE_HEADER_PREFIX_LEN, } from "./routingHandler";
10
12
  import { requestHandler, setNextjsPrebundledReact } from "./util";
@@ -227,8 +229,33 @@ async function processRequest(req, res, routingResult) {
227
229
  }
228
230
  }
229
231
  }
230
- async function handleNoFallbackError(req, res, routingResult, metadata, index = 1) {
231
- if (index >= 5) {
232
+ // Rendering attempts (including the initial one) when falling through `fallback: false` routes.
233
+ // The Next.js router server allows `handleIndex` 0..5 (`if (handleIndex > 5)`) since 13.4.13.
234
+ const MAX_RENDER_ATTEMPTS = 6;
235
+ /**
236
+ * Renders the next resolved route after a `fallback: false` route threw a `NoFallbackError`.
237
+ *
238
+ * Mirrors the Next.js router server, which retries the next matching route:
239
+ * - Up to six attempts are made, including the initial render, before rendering a 500.
240
+ * - A 404 is rendered when no route candidate is left.
241
+ * - Another `NoFallbackError` retries the following candidate, any other error renders a 500.
242
+ *
243
+ * Next.js 16.4+ only renders the most specific route unless the route to render is passed as a
244
+ * `match` request meta (see `getRouteMatchMetadata`). A 400 is rendered when the params of that
245
+ * match can't be decoded, like Next.js does for a `DecodeError`. Older versions select the route
246
+ * from `invokeOutput` and receive exactly the same metadata as before.
247
+ *
248
+ * Errors are rendered as responses, this function does not throw.
249
+ *
250
+ * @param req The request reused for each rendering attempt
251
+ * @param res The response populated by Next.js
252
+ * @param routingResult The ordered route candidates and internal request
253
+ * @param metadata The request metadata of the initial attempt, mutated by Next.js (i.e. it contains the stale `match`)
254
+ * @param index The next candidate index, counting the initial render as index 0
255
+ * @return A promise that resolves after rendering a route or an error response
256
+ */
257
+ export async function handleNoFallbackError(req, res, routingResult, metadata, index = 1) {
258
+ if (index >= MAX_RENDER_ATTEMPTS) {
232
259
  await tryRenderError("500", res, routingResult.internalEvent);
233
260
  return;
234
261
  }
@@ -236,11 +263,29 @@ async function handleNoFallbackError(req, res, routingResult, metadata, index =
236
263
  await tryRenderError("404", res, routingResult.internalEvent);
237
264
  return;
238
265
  }
266
+ const route = routingResult.resolvedRoutes[index].route;
239
267
  try {
268
+ // Skip the call itself on older versions: decoding here can change their error handling.
269
+ let routeMatchMetadata = {};
270
+ if (compareSemver(globalThis.nextVersion, ">=", "16.4.0")) {
271
+ try {
272
+ routeMatchMetadata = getRouteMatchMetadata(route, routingResult.internalEvent.rawPath);
273
+ }
274
+ catch (e) {
275
+ // Only match decoding is a bad request; rendering errors still use the 500 path.
276
+ if (!(e instanceof URIError)) {
277
+ throw e;
278
+ }
279
+ await tryRenderError("400", res, routingResult.internalEvent);
280
+ return;
281
+ }
282
+ }
240
283
  await requestHandler({
241
284
  ...routingResult,
242
- invokeOutput: routingResult.resolvedRoutes[index].route,
285
+ invokeOutput: route,
243
286
  ...metadata,
287
+ // Must come after `metadata` to override the stale `match` of the previous attempt.
288
+ ...routeMatchMetadata,
244
289
  })(req, res);
245
290
  }
246
291
  catch (e) {
@@ -253,6 +298,16 @@ async function handleNoFallbackError(req, res, routingResult, metadata, index =
253
298
  }
254
299
  }
255
300
  }
301
+ /**
302
+ * Renders a Next.js error response with fresh request metadata.
303
+ *
304
+ * Falls back to a JSON 500 response if rendering the error page fails.
305
+ *
306
+ * @param type The HTTP error status to render
307
+ * @param res The response populated by Next.js
308
+ * @param internalEvent The request supplying headers, body, and remote address
309
+ * @return A promise that resolves after writing the error response
310
+ */
256
311
  async function tryRenderError(type, res, internalEvent) {
257
312
  try {
258
313
  const _req = new IncomingMessage({
@@ -262,11 +317,11 @@ async function tryRenderError(type, res, internalEvent) {
262
317
  body: internalEvent.body,
263
318
  remoteAddress: internalEvent.remoteAddress,
264
319
  });
265
- // By setting this it will allow us to bypass and directly render the 404 or 500 page
320
+ // Bypass route matching and directly render the requested error response.
266
321
  const requestMetadata = {
267
322
  // By setting invokePath and invokeQuery we can bypass some of the routing logic in Next.js
268
- invokePath: type === "404" ? "/404" : "/500",
269
- invokeStatus: type === "404" ? 404 : 500,
323
+ invokePath: `/${type}`,
324
+ invokeStatus: Number(type),
270
325
  middlewareInvoke: false,
271
326
  };
272
327
  await requestHandler(requestMetadata)(_req, res);
@@ -0,0 +1,27 @@
1
+ import type { RouteMatch } from "../types/next-types.js";
2
+ /**
3
+ * Returns the request metadata selecting `route` for `pathname`.
4
+ *
5
+ * Next.js 16.4 removed the route matcher manager from the base server: it now only renders the
6
+ * most specific route matching the pathname, and `invokeOutput` can skip that route but no
7
+ * longer selects another one. The Next.js router server forwards the route to render as a
8
+ * `match` request metadata instead, which is required to retry the next route after a
9
+ * `NoFallbackError` (i.e. a `fallback: false` page which was not prerendered).
10
+ *
11
+ * Next.js mutates the metadata of the previous attempt, which then contains the `match` of the
12
+ * route which threw the `NoFallbackError`. `match` is always returned (possibly `undefined`) so
13
+ * that it overrides this stale match even when the route can't be matched.
14
+ *
15
+ * Only used for Next.js 16.4+. Older versions retain their matcher-based retry path,
16
+ * including parameter-decoding error handling, without calling this helper.
17
+ *
18
+ * @param route The route pattern selected for the retry, i.e. `/blog/[slug]`
19
+ * @param pathname The encoded internal pathname after routing, which may include the basePath, a locale prefix and
20
+ * a trailing slash
21
+ * @return Metadata selecting the route, or `{ match: undefined }` when the route is unknown or doesn't match the
22
+ * pathname (Next.js then renders a 404)
23
+ * @throws {URIError} If a captured parameter contains malformed percent encoding
24
+ */
25
+ export declare function getRouteMatchMetadata(route: string, pathname: string): {
26
+ match: RouteMatch | undefined;
27
+ };
@@ -0,0 +1,146 @@
1
+ import path from "node:path";
2
+ import { AppPathRoutesManifest, AppPathsManifest, NEXT_DIR, NextConfig, PagesManifest, RoutesManifest, } from "../adapters/config/index.js";
3
+ import { normalizeLocalePath, selectAppPageEntry, } from "../utils/routeCacheKey.js";
4
+ // `[slug]`, `[...slug]` or `[[...slug]]`, possibly after an interception marker like `(.)`
5
+ const DYNAMIC_SEGMENT_REGEX = /\[{1,2}(\.\.\.)?([^\]]+)\]{1,2}$/;
6
+ /**
7
+ * Returns the request metadata selecting `route` for `pathname`.
8
+ *
9
+ * Next.js 16.4 removed the route matcher manager from the base server: it now only renders the
10
+ * most specific route matching the pathname, and `invokeOutput` can skip that route but no
11
+ * longer selects another one. The Next.js router server forwards the route to render as a
12
+ * `match` request metadata instead, which is required to retry the next route after a
13
+ * `NoFallbackError` (i.e. a `fallback: false` page which was not prerendered).
14
+ *
15
+ * Next.js mutates the metadata of the previous attempt, which then contains the `match` of the
16
+ * route which threw the `NoFallbackError`. `match` is always returned (possibly `undefined`) so
17
+ * that it overrides this stale match even when the route can't be matched.
18
+ *
19
+ * Only used for Next.js 16.4+. Older versions retain their matcher-based retry path,
20
+ * including parameter-decoding error handling, without calling this helper.
21
+ *
22
+ * @param route The route pattern selected for the retry, i.e. `/blog/[slug]`
23
+ * @param pathname The encoded internal pathname after routing, which may include the basePath, a locale prefix and
24
+ * a trailing slash
25
+ * @return Metadata selecting the route, or `{ match: undefined }` when the route is unknown or doesn't match the
26
+ * pathname (Next.js then renders a 404)
27
+ * @throws {URIError} If a captured parameter contains malformed percent encoding
28
+ */
29
+ export function getRouteMatchMetadata(route, pathname) {
30
+ const definition = getRouteDefinition(route);
31
+ const params = definition
32
+ ? getRouteParams(route, normalizePathname(pathname))
33
+ : null;
34
+ return {
35
+ match: definition && params !== null ? { definition, params } : undefined,
36
+ };
37
+ }
38
+ /**
39
+ * Resolves a route to its Next.js definition from the manifests.
40
+ *
41
+ * App pages take precedence over app route handlers, then pages (`/api` and `/api/*` are pages API routes).
42
+ * For app pages, the canonical entry is selected like Next.js when a route has parallel slots or route groups.
43
+ *
44
+ * @param route The route pattern selected for the retry, i.e. `/blog/[slug]`
45
+ * @return The route definition, or `undefined` when the route is in none of the manifests
46
+ */
47
+ function getRouteDefinition(route) {
48
+ const appPaths = Object.keys(AppPathRoutesManifest).filter((appPath) => AppPathRoutesManifest[appPath] === route);
49
+ const appPages = appPaths.filter((appPath) => appPath.endsWith("/page"));
50
+ // Select the same canonical entry as Next.js and the route cache owner.
51
+ const appPage = selectAppPageEntry(route, appPages);
52
+ if (appPage) {
53
+ return {
54
+ kind: "APP_PAGE",
55
+ pathname: route,
56
+ page: appPage,
57
+ filename: getFilename(AppPathsManifest[appPage]),
58
+ appPaths: appPages,
59
+ };
60
+ }
61
+ const appRoute = appPaths.find((appPath) => appPath.endsWith("/route"));
62
+ if (appRoute) {
63
+ return {
64
+ kind: "APP_ROUTE",
65
+ pathname: route,
66
+ page: appRoute,
67
+ filename: getFilename(AppPathsManifest[appRoute]),
68
+ };
69
+ }
70
+ if (PagesManifest[route]) {
71
+ return {
72
+ kind: route === "/api" || route.startsWith("/api/") ? "PAGES_API" : "PAGES",
73
+ pathname: route,
74
+ page: route,
75
+ filename: getFilename(PagesManifest[route]),
76
+ };
77
+ }
78
+ return undefined;
79
+ }
80
+ /**
81
+ * Resolves a manifest entry to the absolute path of the compiled server module.
82
+ *
83
+ * @param manifestPath The path relative to `.next/server`, as stored in the manifests
84
+ * @return The absolute module path, or an empty string when the entry is missing
85
+ */
86
+ function getFilename(manifestPath) {
87
+ return manifestPath ? path.join(NEXT_DIR, "server", manifestPath) : "";
88
+ }
89
+ /**
90
+ * Normalizes a pathname so it can be matched against route patterns.
91
+ *
92
+ * Strips the basePath, then the locale (case-insensitive), then the trailing slash, which are not part of route
93
+ * patterns. The pathname is not decoded: params are decoded after matching, like Next.js.
94
+ *
95
+ * @param pathname The internal pathname of the request
96
+ * @return The pathname without basePath, locale prefix and trailing slash
97
+ */
98
+ function normalizePathname(pathname) {
99
+ const basePath = NextConfig.basePath;
100
+ let normalized = pathname;
101
+ if (basePath &&
102
+ (normalized === basePath || normalized.startsWith(`${basePath}/`))) {
103
+ normalized = normalized.slice(basePath.length) || "/";
104
+ }
105
+ normalized = normalizeLocalePath(normalized, NextConfig.i18n?.locales);
106
+ return normalized !== "/" && normalized.endsWith("/")
107
+ ? normalized.slice(0, -1)
108
+ : normalized;
109
+ }
110
+ /**
111
+ * Extracts the decoded params of a route for a normalized pathname.
112
+ *
113
+ * Static routes have `undefined` params. Missing optional catch-all params are omitted.
114
+ *
115
+ * @param route The route pattern, i.e. `/blog/[slug]`
116
+ * @param pathname The normalized pathname to match against the route
117
+ * @return The params, `undefined` for a matching static route, or `null` when the route doesn't match
118
+ * @throws {URIError} If a captured parameter contains malformed percent encoding, like Next.js
119
+ */
120
+ function getRouteParams(route, pathname) {
121
+ const dynamicRoute = RoutesManifest.routes.dynamic.find(({ page }) => page === route);
122
+ if (!dynamicRoute) {
123
+ return pathname === route ? undefined : null;
124
+ }
125
+ const result = new RegExp(dynamicRoute.regex).exec(pathname);
126
+ if (!result) {
127
+ return null;
128
+ }
129
+ // The regex has one capture group per dynamic segment, in order
130
+ const params = {};
131
+ let group = 1;
132
+ for (const segment of route.split("/")) {
133
+ const dynamicSegment = DYNAMIC_SEGMENT_REGEX.exec(segment);
134
+ if (!dynamicSegment) {
135
+ continue;
136
+ }
137
+ const [, isCatchAll, name] = dynamicSegment;
138
+ const value = result[group++];
139
+ if (value !== undefined) {
140
+ params[name] = isCatchAll
141
+ ? value.split("/").map(decodeURIComponent)
142
+ : decodeURIComponent(value);
143
+ }
144
+ }
145
+ return params;
146
+ }
package/dist/index.js CHANGED
@@ -1,29 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
  import { build } from "./build.js";
3
+ import { parseArgs } from "./utils/args.js";
3
4
  const command = process.argv[2];
4
5
  if (command !== "build")
5
6
  printHelp();
6
- const args = parseArgs();
7
- if (Object.keys(args).includes("--help"))
7
+ const args = parseArgs(process.argv.slice(2));
8
+ if (args.help)
8
9
  printHelp();
9
- await build(args["--config-path"], args["--node-externals"], args["--dangerously-use-unsupported-next-version"] !== undefined);
10
- function parseArgs() {
11
- return process.argv.slice(2).reduce((acc, key, ind, self) => {
12
- if (key.startsWith("--")) {
13
- if (self[ind + 1] && self[ind + 1].startsWith("-")) {
14
- acc[key] = undefined;
15
- }
16
- else if (self[ind + 1]) {
17
- acc[key] = self[ind + 1];
18
- // eslint-disable-next-line sonarjs/elseif-without-else
19
- }
20
- else if (!self[ind + 1]) {
21
- acc[key] = undefined;
22
- }
23
- }
24
- return acc;
25
- }, {});
26
- }
10
+ await build(args.configPath, args.nodeExternals, args.dangerouslyUseUnsupportedNextVersion);
27
11
  function printHelp() {
28
12
  console.log(`Unknown command
29
13
 
@@ -33,6 +17,8 @@ You can use a custom config path here
33
17
  npx open-next build --config-path ./path/to/open-next.config.ts
34
18
  You can configure externals for the esbuild compilation of the open-next.config.ts file
35
19
  npx open-next build --node-externals aws-sdk,sharp,sqlite3
20
+ You can build with a Next.js version that is not supported by the Next.js team anymore
21
+ npx open-next build --dangerously-use-unsupported-next-version
36
22
  `);
37
23
  process.exit(1);
38
24
  }
@@ -87,6 +87,34 @@ export interface RouteDefinition {
87
87
  page: string;
88
88
  regex: string;
89
89
  }
90
+ /**
91
+ * Kind of a Next.js route.
92
+ * https://github.com/vercel/next.js/blob/v16.4.0/packages/next/src/server/route-kind.ts
93
+ */
94
+ export type RouteKind = "PAGES" | "PAGES_API" | "APP_PAGE" | "APP_ROUTE";
95
+ /**
96
+ * Subset of the Next.js route definition read when rendering a `match` request meta.
97
+ * Not to be confused with the routes manifest `RouteDefinition` above.
98
+ * https://github.com/vercel/next.js/blob/v16.4.0/packages/next/src/server/route-definitions/route-definition.ts
99
+ */
100
+ export interface RouteMatchDefinition {
101
+ kind: RouteKind;
102
+ pathname: string;
103
+ page: string;
104
+ filename: string;
105
+ /** Only for `APP_PAGE` routes */
106
+ appPaths?: string[];
107
+ }
108
+ /**
109
+ * The Next.js `match` request meta, the route selected to render a request.
110
+ * OpenNext only passes it to Next.js 16.4+, which no longer selects the route from `invokeOutput`.
111
+ * https://github.com/vercel/next.js/blob/v16.4.0/packages/next/src/server/route-matches/route-match.ts
112
+ */
113
+ export interface RouteMatch {
114
+ definition: RouteMatchDefinition;
115
+ /** `undefined` for static routes */
116
+ params?: Record<string, string | string[]>;
117
+ }
90
118
  export interface DataRouteDefinition {
91
119
  page: string;
92
120
  dataRouteRegex: string;
@@ -0,0 +1,20 @@
1
+ export interface CliArgs {
2
+ /** Value of `--config-path` */
3
+ configPath?: string;
4
+ /** Value of `--node-externals` */
5
+ nodeExternals?: string;
6
+ /** Whether `--help` is present */
7
+ help: boolean;
8
+ /** Whether `--dangerously-use-unsupported-next-version` is present */
9
+ dangerouslyUseUnsupportedNextVersion: boolean;
10
+ }
11
+ /**
12
+ * Parses the command line arguments of the `open-next` CLI.
13
+ *
14
+ * Boolean flags (i.e. `--help`) are `true` when present and never consume the following argument.
15
+ * Other flags take the following argument as their value unless it starts with `-`.
16
+ *
17
+ * @param argv - The CLI arguments, without the node executable and script path (i.e. `process.argv.slice(2)`)
18
+ * @returns The parsed arguments
19
+ */
20
+ export declare function parseArgs(argv: string[]): CliArgs;
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Flags that do not take a value. They are `true` when present and never consume the next argument.
3
+ */
4
+ const BOOLEAN_FLAGS = new Set([
5
+ "--help",
6
+ "--dangerously-use-unsupported-next-version",
7
+ ]);
8
+ /**
9
+ * Parses the command line arguments of the `open-next` CLI.
10
+ *
11
+ * Boolean flags (i.e. `--help`) are `true` when present and never consume the following argument.
12
+ * Other flags take the following argument as their value unless it starts with `-`.
13
+ *
14
+ * @param argv - The CLI arguments, without the node executable and script path (i.e. `process.argv.slice(2)`)
15
+ * @returns The parsed arguments
16
+ */
17
+ export function parseArgs(argv) {
18
+ const flags = new Set();
19
+ const values = {};
20
+ for (let i = 0; i < argv.length; i++) {
21
+ const key = argv[i];
22
+ if (!key.startsWith("--")) {
23
+ continue;
24
+ }
25
+ if (BOOLEAN_FLAGS.has(key)) {
26
+ flags.add(key);
27
+ continue;
28
+ }
29
+ const next = argv[i + 1];
30
+ if (next && !next.startsWith("-")) {
31
+ values[key] = next;
32
+ i++;
33
+ }
34
+ else {
35
+ values[key] = undefined;
36
+ }
37
+ }
38
+ return {
39
+ configPath: values["--config-path"],
40
+ nodeExternals: values["--node-externals"],
41
+ help: flags.has("--help"),
42
+ dangerouslyUseUnsupportedNextVersion: flags.has("--dangerously-use-unsupported-next-version"),
43
+ };
44
+ }
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "access": "public"
4
4
  },
5
5
  "name": "@opennextjs/aws",
6
- "version": "4.1.10",
6
+ "version": "4.1.12",
7
7
  "bin": {
8
8
  "open-next": "./dist/index.js"
9
9
  },
@@ -56,12 +56,12 @@
56
56
  "@types/express": "5.0.6",
57
57
  "@types/node": "20.17.6",
58
58
  "concurrently": "^9.2.4",
59
- "next": "16.3.8",
59
+ "next": "16.4.1",
60
60
  "tsc-alias": "^1.9.7",
61
61
  "typescript": "5.9.3"
62
62
  },
63
63
  "peerDependencies": {
64
- "next": ">=15.5.27 <16 || >=16.3.8"
64
+ "next": ">=15.5.28 <16 || >=16.4.1"
65
65
  },
66
66
  "bugs": {
67
67
  "url": "https://github.com/opennextjs/opennextjs-aws/issues"