diff --git a/packages/open-next/package.json b/packages/open-next/package.json index beac7a20f..7813c913e 100644 --- a/packages/open-next/package.json +++ b/packages/open-next/package.json @@ -47,7 +47,7 @@ "@types/aws-lambda": "^8.10.109", "@types/node": "^18.16.1", "tsc-alias": "^1.8.8", - "typescript": "^4.9.3" + "typescript": "^5.6.3" }, "bugs": { "url": "https://github.com/opennextjs/opennextjs-aws/issues" diff --git a/packages/open-next/src/adapters/dynamo-provider.ts b/packages/open-next/src/adapters/dynamo-provider.ts index d45572544..7cf27fa82 100644 --- a/packages/open-next/src/adapters/dynamo-provider.ts +++ b/packages/open-next/src/adapters/dynamo-provider.ts @@ -56,7 +56,7 @@ async function insert( const parsedData = data.map((item) => ({ tag: item.tag.S, path: item.path.S, - revalidatedAt: parseInt(item.revalidatedAt.N), + revalidatedAt: Number.parseInt(item.revalidatedAt.N), })); await tagCache.writeTags(parsedData); diff --git a/packages/open-next/src/adapters/server-adapter.ts b/packages/open-next/src/adapters/server-adapter.ts index 34e9b616a..486d8a4a3 100644 --- a/packages/open-next/src/adapters/server-adapter.ts +++ b/packages/open-next/src/adapters/server-adapter.ts @@ -28,7 +28,8 @@ export const handler = await createMainHandler(); ////////////////////// function setNextjsServerWorkingDirectory() { - // WORKAROUND: Set `NextServer` working directory (AWS specific) — https://github.com/serverless-stack/open-next#workaround-set-nextserver-working-directory-aws-specific + // WORKAROUND: Set `NextServer` working directory (AWS specific) + // See https://opennext.js.org/aws/v2/advanced/workaround#workaround-set-nextserver-working-directory-aws-specific process.chdir(__dirname); } diff --git a/packages/open-next/src/adapters/util.ts b/packages/open-next/src/adapters/util.ts index d35545e89..99a65564c 100644 --- a/packages/open-next/src/adapters/util.ts +++ b/packages/open-next/src/adapters/util.ts @@ -31,7 +31,7 @@ export function parseNumberFromEnv( return envValue; } - const parsedValue = parseInt(envValue); + const parsedValue = Number.parseInt(envValue); return isNaN(parsedValue) ? undefined : parsedValue; } diff --git a/packages/open-next/src/build/createAssets.ts b/packages/open-next/src/build/createAssets.ts index baf8774e3..c02058c65 100644 --- a/packages/open-next/src/build/createAssets.ts +++ b/packages/open-next/src/build/createAssets.ts @@ -14,12 +14,17 @@ export function createStaticAssets(options: buildHelper.BuildOptions) { const outputPath = path.join(outputDir, "assets"); fs.mkdirSync(outputPath, { recursive: true }); - // Next.js outputs assets into multiple files. Copy into the same directory. - // Copy over: - // - .next/BUILD_ID => _next/BUILD_ID - // - .next/static => _next/static - // - public/* => * - // - app/favicon.ico or src/app/favicon.ico => favicon.ico + /** + * Next.js outputs assets into multiple files. Copy into the same directory. + * + * Copy over: + * - .next/BUILD_ID => BUILD_ID + * - .next/static => _next/static + * - public/* => * + * - app/favicon.ico or src/app/favicon.ico => favicon.ico + * + * Note: BUILD_ID is used by the SST infra. + */ fs.copyFileSync( path.join(appBuildOutputPath, ".next/BUILD_ID"), path.join(outputPath, "BUILD_ID"), diff --git a/packages/open-next/src/build/createMiddleware.ts b/packages/open-next/src/build/createMiddleware.ts index 0d1256ec9..109acfe6c 100644 --- a/packages/open-next/src/build/createMiddleware.ts +++ b/packages/open-next/src/build/createMiddleware.ts @@ -2,9 +2,9 @@ import fs from "node:fs"; import path from "node:path"; import logger from "../logger.js"; -import { - type MiddlewareInfo, - type MiddlewareManifest, +import type { + MiddlewareInfo, + MiddlewareManifest, } from "../types/next-types.js"; import { buildEdgeBundle } from "./edge/createEdgeBundle.js"; import * as buildHelper from "./helper.js"; diff --git a/packages/open-next/src/build/createServerBundle.ts b/packages/open-next/src/build/createServerBundle.ts index 54c6bb22d..a6208adf5 100644 --- a/packages/open-next/src/build/createServerBundle.ts +++ b/packages/open-next/src/build/createServerBundle.ts @@ -197,25 +197,18 @@ async function generateBundle( ...(disableNextPrebundledReact ? ["requireHooks"] : []), ...(disableRouting ? ["trustHostHeader"] : []), ...(!isBefore13413 ? ["requestHandlerHost"] : []), - ...(!isAfter141 ? ["stableIncrementalCache"] : []), - ...(isAfter141 ? ["experimentalIncrementalCacheHandler"] : []), + ...(isAfter141 + ? ["experimentalIncrementalCacheHandler"] + : ["stableIncrementalCache"]), ], }), openNextResolvePlugin({ fnName: name, - overrides: overrides, + overrides, }), ]; - if (plugins && plugins.length > 0) { - logger.debug( - `Applying plugins:: [${plugins - .map(({ name }) => name) - .join(",")}] for Next version: ${options.nextVersion}`, - ); - } - const outfileExt = fnOptions.runtime === "deno" ? "ts" : "mjs"; await buildHelper.esbuildAsync( { @@ -238,9 +231,12 @@ async function generateBundle( }, plugins, alias: { - "next/dist/server/next-server.js": isBundled - ? "./next-server.runtime.prod.js" - : "next/dist/server/next-server.js", + ...(isBundled + ? { + "next/dist/server/next-server.js": + "./next-server.runtime.prod.js", + } + : {}), }, }, options, diff --git a/packages/open-next/src/build/edge/createEdgeBundle.ts b/packages/open-next/src/build/edge/createEdgeBundle.ts index 7b5c91cc3..f84f2ddd7 100644 --- a/packages/open-next/src/build/edge/createEdgeBundle.ts +++ b/packages/open-next/src/build/edge/createEdgeBundle.ts @@ -52,7 +52,6 @@ export async function buildEdgeBundle({ await esbuildAsync( { entryPoints: [entrypoint], - // inject: , bundle: true, outfile, external: ["node:*", "next", "@aws-sdk/*"], diff --git a/packages/open-next/src/build/helper.ts b/packages/open-next/src/build/helper.ts index 4d1365e55..1039e9aac 100644 --- a/packages/open-next/src/build/helper.ts +++ b/packages/open-next/src/build/helper.ts @@ -189,7 +189,7 @@ export function traverseFiles( root: string, conditionFn: (paths: TraversePath) => boolean, callbackFn: (paths: TraversePath) => void, - searchingDir: string = "", + searchingDir = "", ) { fs.readdirSync(path.join(root, searchingDir)).forEach((file) => { const relativePath = path.join(searchingDir, file); diff --git a/packages/open-next/src/core/patchAsyncStorage.ts b/packages/open-next/src/core/patchAsyncStorage.ts index e53c7c69a..0035ac825 100644 --- a/packages/open-next/src/core/patchAsyncStorage.ts +++ b/packages/open-next/src/core/patchAsyncStorage.ts @@ -3,13 +3,13 @@ const mod = require("module"); const resolveFilename = mod._resolveFilename; export function patchAsyncStorage() { - mod._resolveFilename = function ( + mod._resolveFilename = (( originalResolveFilename: typeof resolveFilename, request: string, parent: any, isMain: boolean, options: any, - ) { + ) => { if ( request.endsWith("static-generation-async-storage.external") || request.endsWith("static-generation-async-storage.external.js") @@ -35,5 +35,5 @@ export function patchAsyncStorage() { ); // We use `bind` here to avoid referencing outside variables to create potential memory leaks. - }.bind(null, resolveFilename); + }).bind(null, resolveFilename); } diff --git a/packages/open-next/src/core/requestHandler.ts b/packages/open-next/src/core/requestHandler.ts index 8d1758ae9..8dc393443 100644 --- a/packages/open-next/src/core/requestHandler.ts +++ b/packages/open-next/src/core/requestHandler.ts @@ -9,7 +9,10 @@ import { debug, error, warn } from "../adapters/logger"; import { patchAsyncStorage } from "./patchAsyncStorage"; import { convertRes, createServerResponse, proxyRequest } from "./routing/util"; import type { MiddlewareOutputEvent } from "./routingHandler"; -import routingHandler from "./routingHandler"; +import routingHandler, { + MIDDLEWARE_HEADER_PREFIX, + MIDDLEWARE_HEADER_PREFIX_LEN, +} from "./routingHandler"; import { requestHandler, setNextjsPrebundledReact } from "./util"; // This is used to identify requests in the cache @@ -51,107 +54,103 @@ export async function openNextHandler( ? preprocessResult.headers : preprocessResult.internalEvent.headers; - const overwrittenResponseHeaders = Object.entries( - "type" in preprocessResult - ? preprocessResult.headers - : preprocessResult.internalEvent.headers, - ).reduce((acc, [key, value]) => { - if (!key.startsWith("x-middleware-response-")) { - return acc; - } else { - const newKey = key.replace("x-middleware-response-", ""); - delete headers[key]; - headers[newKey] = value; - return { ...acc, [newKey]: value }; + const overwrittenResponseHeaders: Record = {}; + + for (const [rawKey, value] of Object.entries(headers)) { + if (!rawKey.startsWith(MIDDLEWARE_HEADER_PREFIX)) { + continue; } - }, {}); + const key = rawKey.slice(MIDDLEWARE_HEADER_PREFIX_LEN); + overwrittenResponseHeaders[key] = value; + headers[key] = value; + delete headers[rawKey]; + } if ("type" in preprocessResult) { - // // res is used only in the streaming case + // response is used only in the streaming case if (responseStreaming) { - const res = createServerResponse( + const response = createServerResponse( internalEvent, headers, responseStreaming, ); - res.statusCode = preprocessResult.statusCode; - res.flushHeaders(); + response.statusCode = preprocessResult.statusCode; + response.flushHeaders(); const [bodyToConsume, bodyToReturn] = preprocessResult.body.tee(); for await (const chunk of bodyToConsume) { - res.write(chunk); + response.write(chunk); } - res.end(); + response.end(); preprocessResult.body = bodyToReturn; } return preprocessResult; - } else { - const preprocessedEvent = preprocessResult.internalEvent; - debug("preprocessedEvent", preprocessedEvent); - const reqProps = { - method: preprocessedEvent.method, - url: preprocessedEvent.url, - //WORKAROUND: We pass this header to the serverless function to mimic a prefetch request which will not trigger revalidation since we handle revalidation differently - // There is 3 way we can handle revalidation: - // 1. We could just let the revalidation go as normal, but due to race condtions the revalidation will be unreliable - // 2. We could alter the lastModified time of our cache to make next believe that the cache is fresh, but this could cause issues with stale data since the cdn will cache the stale data as if it was fresh - // 3. OUR CHOICE: We could pass a purpose prefetch header to the serverless function to make next believe that the request is a prefetch request and not trigger revalidation (This could potentially break in the future if next changes the behavior of prefetch requests) - headers: { ...headers, purpose: "prefetch" }, - body: preprocessedEvent.body, - remoteAddress: preprocessedEvent.remoteAddress, - }; - const requestId = Math.random().toString(36); - const pendingPromiseRunner: DetachedPromiseRunner = - new DetachedPromiseRunner(); - const isISRRevalidation = headers["x-isr"] === "1"; - const mergeHeadersPriority = globalThis.openNextConfig.dangerous - ?.headersAndCookiesPriority - ? globalThis.openNextConfig.dangerous.headersAndCookiesPriority( - preprocessedEvent, - ) - : "middleware"; - const internalResult = await globalThis.__als.run( - { - requestId, - pendingPromiseRunner, - isISRRevalidation, - mergeHeadersPriority, - }, - async () => { - const preprocessedResult = preprocessResult as MiddlewareOutputEvent; - const req = new IncomingMessage(reqProps); - const res = createServerResponse( - preprocessedEvent, - overwrittenResponseHeaders, - responseStreaming, - ); - - await processRequest( - req, - res, - preprocessedEvent, - preprocessedResult.isExternalRewrite, - ); - - const { statusCode, headers, isBase64Encoded, body } = convertRes(res); - - const internalResult = { - type: internalEvent.type, - statusCode, - headers, - body, - isBase64Encoded, - }; - - // reset lastModified. We need to do this to avoid memory leaks - delete globalThis.lastModified[requestId]; - - await pendingPromiseRunner.await(); - - return internalResult; - }, - ); - return internalResult; } + + const preprocessedEvent = preprocessResult.internalEvent; + debug("preprocessedEvent", preprocessedEvent); + const reqProps = { + method: preprocessedEvent.method, + url: preprocessedEvent.url, + //WORKAROUND: We pass this header to the serverless function to mimic a prefetch request which will not trigger revalidation since we handle revalidation differently + // There is 3 way we can handle revalidation: + // 1. We could just let the revalidation go as normal, but due to race conditions the revalidation will be unreliable + // 2. We could alter the lastModified time of our cache to make next believe that the cache is fresh, but this could cause issues with stale data since the cdn will cache the stale data as if it was fresh + // 3. OUR CHOICE: We could pass a purpose prefetch header to the serverless function to make next believe that the request is a prefetch request and not trigger revalidation (This could potentially break in the future if next changes the behavior of prefetch requests) + headers: { ...headers, purpose: "prefetch" }, + body: preprocessedEvent.body, + remoteAddress: preprocessedEvent.remoteAddress, + }; + const requestId = Math.random().toString(36); + const pendingPromiseRunner = new DetachedPromiseRunner(); + const isISRRevalidation = headers["x-isr"] === "1"; + const mergeHeadersPriority = globalThis.openNextConfig.dangerous + ?.headersAndCookiesPriority + ? globalThis.openNextConfig.dangerous.headersAndCookiesPriority( + preprocessedEvent, + ) + : "middleware"; + const internalResult = await globalThis.__als.run( + { + requestId, + pendingPromiseRunner, + isISRRevalidation, + mergeHeadersPriority, + }, + async () => { + const preprocessedResult = preprocessResult as MiddlewareOutputEvent; + const req = new IncomingMessage(reqProps); + const res = createServerResponse( + preprocessedEvent, + overwrittenResponseHeaders, + responseStreaming, + ); + + await processRequest( + req, + res, + preprocessedEvent, + preprocessedResult.isExternalRewrite, + ); + + const { statusCode, headers, isBase64Encoded, body } = convertRes(res); + + const internalResult = { + type: internalEvent.type, + statusCode, + headers, + body, + isBase64Encoded, + }; + + // reset lastModified. We need to do this to avoid memory leaks + delete globalThis.lastModified[requestId]; + + await pendingPromiseRunner.await(); + + return internalResult; + }, + ); + return internalResult; } async function processRequest( @@ -169,18 +168,16 @@ async function processRequest( // `serverHandler` is replaced at build time depending on user's // nextjs version to patch Nextjs 13.4.x and future breaking changes. - const { rawPath } = internalEvent; - if (isExternalRewrite) { return proxyRequest(internalEvent, res); - } else { - //#override applyNextjsPrebundledReact - setNextjsPrebundledReact(rawPath); - //#endOverride - - // Next Server - await requestHandler(req, res); } + + //#override applyNextjsPrebundledReact + setNextjsPrebundledReact(internalEvent.rawPath); + //#endOverride + + // Next Server + await requestHandler(req, res); } catch (e: any) { // This might fail when using bundled next, importing won't do the trick either if (e.constructor.name === "NoFallbackError") { diff --git a/packages/open-next/src/core/require-hooks.ts b/packages/open-next/src/core/require-hooks.ts index 963dbfa96..cf42d03c4 100644 --- a/packages/open-next/src/core/require-hooks.ts +++ b/packages/open-next/src/core/require-hooks.ts @@ -132,7 +132,7 @@ function isApp() { } export function applyOverride() { - mod._resolveFilename = function ( + mod._resolveFilename = (( originalResolveFilename: typeof resolveFilename, requestMapApp: Map, requestMapPage: Map, @@ -140,7 +140,7 @@ export function applyOverride() { parent: any, isMain: boolean, options: any, - ) { + ) => { const hookResolved = isApp() ? requestMapApp.get(request) : requestMapPage.get(request); @@ -148,5 +148,5 @@ export function applyOverride() { return originalResolveFilename.call(mod, request, parent, isMain, options); // We use `bind` here to avoid referencing outside variables to create potential memory leaks. - }.bind(null, resolveFilename, hookPropertyMapApp, hookPropertyMapPage); + }).bind(null, resolveFilename, hookPropertyMapApp, hookPropertyMapPage); } diff --git a/packages/open-next/src/core/routing/i18n/accept-header.ts b/packages/open-next/src/core/routing/i18n/accept-header.ts index aa2a181b4..0ab7301ff 100644 --- a/packages/open-next/src/core/routing/i18n/accept-header.ts +++ b/packages/open-next/src/core/routing/i18n/accept-header.ts @@ -54,7 +54,7 @@ function parse( throw new Error(`Invalid ${options.type} header`); } - let token = params[0].toLowerCase(); + const token = params[0].toLowerCase(); if (!token) { throw new Error(`Invalid ${options.type} header`); } @@ -74,7 +74,7 @@ function parse( throw new Error(`Invalid ${options.type} header`); } - const score = parseFloat(value); + const score = Number.parseFloat(value); if (score === 0) { continue; } diff --git a/packages/open-next/src/core/routing/matcher.ts b/packages/open-next/src/core/routing/matcher.ts index 4da683fdb..3a01dd8fc 100644 --- a/packages/open-next/src/core/routing/matcher.ts +++ b/packages/open-next/src/core/routing/matcher.ts @@ -207,7 +207,7 @@ export function handleRewrites( const toDestinationQuery = compile( escapeRegex(encodePlusQueryString ?? "") ?? "", ); - let params = { + const params = { // params for the source ...getParamsFromSource(match(escapeRegex(rewrite?.source) ?? ""))( pathToUse, diff --git a/packages/open-next/src/core/routing/util.ts b/packages/open-next/src/core/routing/util.ts index 268691300..bc3ebce27 100644 --- a/packages/open-next/src/core/routing/util.ts +++ b/packages/open-next/src/core/routing/util.ts @@ -146,13 +146,10 @@ export function getMiddlewareMatch(middlewareManifest: MiddlewareManifest) { * @__PURE__ */ export function escapeRegex(str: string) { - let path = str.replace(/\(\.\)/g, "_µ1_"); - - path = path.replace(/\(\.{2}\)/g, "_µ2_"); - - path = path.replace(/\(\.{3}\)/g, "_µ3_"); - - return path; + return str + .replaceAll("(.)", "_µ1_") + .replaceAll("(..)", "_µ2_") + .replaceAll("(...)", "_µ3_"); } /** @@ -160,13 +157,10 @@ export function escapeRegex(str: string) { * @__PURE__ */ export function unescapeRegex(str: string) { - let path = str.replace(/_µ1_/g, "(.)"); - - path = path.replace(/_µ2_/g, "(..)"); - - path = path.replace(/_µ3_/g, "(...)"); - - return path; + return str + .replaceAll("_µ1_", "(.)") + .replaceAll("_µ2_", "(..)") + .replaceAll("_µ3_", "(...)"); } /** @@ -298,7 +292,8 @@ export function fixCacheHeaderForHtmlPages( "private, no-cache, no-store, max-age=0, must-revalidate"; return; } - // WORKAROUND: `NextServer` does not set cache headers for HTML pages — https://github.com/serverless-stack/open-next#workaround-nextserver-does-not-set-cache-headers-for-html-pages + // WORKAROUND: `NextServer` does not set cache headers for HTML pages + // https://opennext.js.org/aws/v2/advanced/workaround#workaround-nextserver-does-not-set-cache-headers-for-html-pages if (HtmlPages.includes(rawPath)) { headers[CommonHeaders.CACHE_CONTROL] = "public, max-age=0, s-maxage=31536000, must-revalidate"; @@ -411,7 +406,7 @@ export function generateMessageGroupId(rawPath: string) { const randomFloat = ((t ^ (t >>> 14)) >>> 0) / 4294967296; // This will generate a random int between 0 and MAX_REVALIDATE_CONCURRENCY // This means that we could have 1000 revalidate request at the same time - const maxConcurrency = parseInt( + const maxConcurrency = Number.parseInt( process.env.MAX_REVALIDATE_CONCURRENCY ?? "10", ); const randomInt = Math.floor(randomFloat * maxConcurrency); @@ -460,7 +455,7 @@ export function fixISRHeaders(headers: OutgoingHttpHeaders) { debug("cache-control", cacheControl, globalThis.lastModified, Date.now()); if (typeof cacheControl !== "string") return; const match = cacheControl.match(regex); - const sMaxAge = match ? parseInt(match[1]) : undefined; + const sMaxAge = match ? Number.parseInt(match[1]) : undefined; // 31536000 is the default s-maxage value for SSG pages if (sMaxAge && sMaxAge !== 31536000) { diff --git a/packages/open-next/src/core/util.ts b/packages/open-next/src/core/util.ts index e97822102..a4a466b86 100644 --- a/packages/open-next/src/core/util.ts +++ b/packages/open-next/src/core/util.ts @@ -16,7 +16,8 @@ import { overrideHooks as overrideNextjsRequireHooks, } from "./require-hooks.js"; -// WORKAROUND: Set `__NEXT_PRIVATE_PREBUNDLED_REACT` to use prebundled React — https://github.com/serverless-stack/open-next#workaround-set-__next_private_prebundled_react-to-use-prebundled-react +// WORKAROUND: Set `__NEXT_PRIVATE_PREBUNDLED_REACT` to use prebundled Reac +// See https://opennext.js.org/aws/v2/advanced/workaround#workaround-set-__next_private_prebundled_react-to-use-prebundled-react // Step 1: Need to override the require hooks for React before Next.js server // overrides them with prebundled ones in the case of app dir // Step 2: Import Next.js server @@ -76,7 +77,8 @@ export function loadMiddlewareManifest(nextDir: string) { //#override setNextjsPrebundledReact export function setNextjsPrebundledReact(rawPath: string) { - // WORKAROUND: Set `__NEXT_PRIVATE_PREBUNDLED_REACT` to use prebundled React — https://github.com/serverless-stack/open-next#workaround-set-__next_private_prebundled_react-to-use-prebundled-react + // WORKAROUND: Set `__NEXT_PRIVATE_PREBUNDLED_REACT` to use prebundled React + // See https://opennext.js.org/aws/v2/advanced/workaround#workaround-set-__next_private_prebundled_react-to-use-prebundled-react const routes = [ ...RoutesManifest.routes.static, diff --git a/packages/open-next/src/http/openNextResponse.ts b/packages/open-next/src/http/openNextResponse.ts index f062eeb10..d2c89cf73 100644 --- a/packages/open-next/src/http/openNextResponse.ts +++ b/packages/open-next/src/http/openNextResponse.ts @@ -28,15 +28,15 @@ export interface StreamCreator { // We only need to implement the methods that are used by next.js export class OpenNextNodeResponse extends Transform implements ServerResponse { statusCode!: number; - statusMessage: string = ""; + statusMessage = ""; headers: OutgoingHttpHeaders = {}; private _cookies: string[] = []; private responseStream?: Writable; - headersSent: boolean = false; + headersSent = false; _chunks: Buffer[] = []; // To comply with the ServerResponse interface : - strictContentLength: boolean = false; + strictContentLength = false; assignSocket(_socket: Socket): void { throw new Error(CANNOT_BE_USED); } @@ -61,10 +61,10 @@ export class OpenNextNodeResponse extends Transform implements ServerResponse { * It will never be defined */ req!: IncomingMessage; - chunkedEncoding: boolean = false; - shouldKeepAlive: boolean = true; - useChunkedEncodingByDefault: boolean = true; - sendDate: boolean = false; + chunkedEncoding = false; + shouldKeepAlive = true; + useChunkedEncodingByDefault = true; + sendDate = false; connection: Socket | null = null; socket: Socket | null = null; setTimeout(_msecs: number, _callback?: (() => void) | undefined): this { @@ -174,8 +174,8 @@ export class OpenNextNodeResponse extends Transform implements ServerResponse { ...this.headers, }; const initialCookies = parseCookies( - (this.initialHeaders[SET_COOKIE_HEADER] as string | string[]) ?? [], - ) as string[]; + this.initialHeaders[SET_COOKIE_HEADER], + ); this._cookies = mergeHeadersPriority === "middleware" ? [...this._cookies, ...initialCookies] diff --git a/packages/open-next/src/http/util.ts b/packages/open-next/src/http/util.ts index 8567e3341..1fe4e9f55 100644 --- a/packages/open-next/src/http/util.ts +++ b/packages/open-next/src/http/util.ts @@ -30,13 +30,13 @@ export const convertHeader = (header: http.OutgoingHttpHeader) => { }; export function parseCookies( - cookies?: string | string[], -): string[] | undefined { - if (!cookies) return; - - if (typeof cookies === "string") { - return cookies.split(/(? c.trim()); + cookies: string | string[] | null | undefined, +): string[] { + if (!cookies) { + return []; } - return cookies; + return typeof cookies === "string" + ? cookies.split(/(? c.trim()) + : cookies; } diff --git a/packages/open-next/src/overrides/converters/aws-cloudfront.ts b/packages/open-next/src/overrides/converters/aws-cloudfront.ts index 2ffbbf9ac..9439eccab 100644 --- a/packages/open-next/src/overrides/converters/aws-cloudfront.ts +++ b/packages/open-next/src/overrides/converters/aws-cloudfront.ts @@ -129,15 +129,13 @@ function convertToCloudfrontHeaders( ) .forEach(([key, value]) => { if (key === "set-cookie") { - const cookies = parseCookies(`${value}`); - if (cookies) { - cloudfrontHeaders[key] = cookies.map((cookie) => ({ - key, - value: cookie, - })); - } + cloudfrontHeaders[key] = parseCookies(`${value}`).map((cookie) => ({ + key, + value: cookie, + })); return; } + cloudfrontHeaders[key] = [ ...(cloudfrontHeaders[key] || []), ...(Array.isArray(value) diff --git a/packages/open-next/src/overrides/converters/edge.ts b/packages/open-next/src/overrides/converters/edge.ts index 35e055125..557fed236 100644 --- a/packages/open-next/src/overrides/converters/edge.ts +++ b/packages/open-next/src/overrides/converters/edge.ts @@ -19,11 +19,11 @@ const converter: Converter< const searchParams = new URL(event.url).searchParams; const query: Record = {}; for (const [key, value] of searchParams.entries()) { - if (query[key]) { + if (key in query) { if (Array.isArray(query[key])) { - (query[key] as string[]).push(value); + query[key].push(value); } else { - query[key] = [query[key] as string, value]; + query[key] = [query[key], value]; } } else { query[key] = value; @@ -38,6 +38,11 @@ const converter: Converter< const rawPath = new URL(event.url).pathname; const method = event.method; const shouldHaveBody = method !== "GET" && method !== "HEAD"; + const cookies: Record = Object.fromEntries( + parseCookies(event.headers.get("cookie")).map((cookie) => + cookie.split("="), + ), + ); return { type: "core", @@ -45,15 +50,10 @@ const converter: Converter< rawPath, url: event.url, body: shouldHaveBody ? Buffer.from(body) : undefined, - headers: headers, - remoteAddress: (event.headers.get("x-forwarded-for") as string) ?? "::1", + headers, + remoteAddress: event.headers.get("x-forwarded-for") ?? "::1", query, - cookies: Object.fromEntries( - parseCookies(event.headers.get("cookie") ?? "")?.map((cookie) => { - const [key, value] = cookie.split("="); - return [key, value]; - }) ?? [], - ), + cookies, }; }, convertTo: async (result) => { diff --git a/packages/open-next/src/overrides/tagCache/constants.ts b/packages/open-next/src/overrides/tagCache/constants.ts index fe1886997..a33d6a3c0 100644 --- a/packages/open-next/src/overrides/tagCache/constants.ts +++ b/packages/open-next/src/overrides/tagCache/constants.ts @@ -11,7 +11,7 @@ export const getDynamoBatchWriteCommandConcurrency = (): number => { process.env.DYNAMO_BATCH_WRITE_COMMAND_CONCURRENCY; const parsedDynamoBatchWriteCommandConcurrencyFromEnv = dynamoBatchWriteCommandConcurrencyFromEnv - ? parseInt(dynamoBatchWriteCommandConcurrencyFromEnv) + ? Number.parseInt(dynamoBatchWriteCommandConcurrencyFromEnv) : undefined; if ( diff --git a/packages/open-next/src/overrides/wrappers/node.ts b/packages/open-next/src/overrides/wrappers/node.ts index 36c1b717f..0c64809fb 100644 --- a/packages/open-next/src/overrides/wrappers/node.ts +++ b/packages/open-next/src/overrides/wrappers/node.ts @@ -50,7 +50,7 @@ const wrapper: WrapperHandler = async (handler, converter) => { resolve(); }); - server.listen(parseInt(process.env.PORT ?? "3000", 10)); + server.listen(Number.parseInt(process.env.PORT ?? "3000", 10)); }); server.on("error", (err) => { diff --git a/packages/open-next/src/plugins/replacement.ts b/packages/open-next/src/plugins/replacement.ts index 1c5f148da..897c1a2c5 100644 --- a/packages/open-next/src/plugins/replacement.ts +++ b/packages/open-next/src/plugins/replacement.ts @@ -68,7 +68,7 @@ export function openNextReplacementPlugin({ ); logger.debug( chalk.blue(`OpenNext Replacement plugin ${name}`), - ` -- Deleting override for ${id}`, + `Delete override ${id}`, ); contents = contents.replace(pattern, ""); }), @@ -90,7 +90,7 @@ export function openNextReplacementPlugin({ ); logger.debug( chalk.blue(`Open-next replacement plugin ${name}`), - `-- Applying override for ${id} from ${filename}`, + `Apply override ${id} from ${filename}`, ); contents = contents.replace(pattern, replacement); } diff --git a/packages/open-next/src/types/aws-lambda.ts b/packages/open-next/src/types/aws-lambda.ts index 818e92571..1b9c2a1fe 100644 --- a/packages/open-next/src/types/aws-lambda.ts +++ b/packages/open-next/src/types/aws-lambda.ts @@ -20,7 +20,7 @@ interface Metadata { declare global { namespace awslambda { function streamifyResponse(handler: Handler): Handler; - module HttpResponseStream { + namespace HttpResponseStream { function from(res: Writable, metadata: Metadata): ResponseStream; } } diff --git a/packages/open-next/src/types/next-types.ts b/packages/open-next/src/types/next-types.ts index c7b2b6e8a..1608dda65 100644 --- a/packages/open-next/src/types/next-types.ts +++ b/packages/open-next/src/types/next-types.ts @@ -173,10 +173,8 @@ export type Options = { buildId: string; isExternalRewrite?: boolean; }; -export interface PluginHandler { - ( - req: IncomingMessage, - res: OpenNextNodeResponse, - options: Options, - ): Promise; -} +export type PluginHandler = ( + req: IncomingMessage, + res: OpenNextNodeResponse, + options: Options, +) => Promise; diff --git a/packages/tests-e2e/tests/appPagesRouter/host.test.ts b/packages/tests-e2e/tests/appPagesRouter/host.test.ts index 99d39e88a..98141106c 100644 --- a/packages/tests-e2e/tests/appPagesRouter/host.test.ts +++ b/packages/tests-e2e/tests/appPagesRouter/host.test.ts @@ -6,6 +6,6 @@ import { expect, test } from "@playwright/test"; test("Request.url is host", async ({ baseURL, page }) => { await page.goto("/api/host"); - let el = page.getByText(`{"url":"${baseURL}/api/host"}`); + const el = page.getByText(`{"url":"${baseURL}/api/host"}`); await expect(el).toBeVisible(); }); diff --git a/packages/tests-e2e/tests/appPagesRouter/image-optimization.test.ts b/packages/tests-e2e/tests/appPagesRouter/image-optimization.test.ts index c26cfe6ec..92fac14f2 100644 --- a/packages/tests-e2e/tests/appPagesRouter/image-optimization.test.ts +++ b/packages/tests-e2e/tests/appPagesRouter/image-optimization.test.ts @@ -14,7 +14,7 @@ test("Image Optimization", async ({ page }) => { const imageContentType = imageResponse.headers()["content-type"]; expect(imageContentType).toBe("image/webp"); - let el = page.locator("img"); + const el = page.locator("img"); await expect(el).toHaveJSProperty("complete", true); await expect(el).not.toHaveJSProperty("naturalWidth", 0); }); diff --git a/packages/tests-e2e/tests/appPagesRouter/ssr.test.ts b/packages/tests-e2e/tests/appPagesRouter/ssr.test.ts index dc03e1fd5..5d97a789e 100644 --- a/packages/tests-e2e/tests/appPagesRouter/ssr.test.ts +++ b/packages/tests-e2e/tests/appPagesRouter/ssr.test.ts @@ -30,6 +30,6 @@ test("Server Side Render", async ({ page }) => { test("Server Side Render with env", async ({ page }) => { await page.goto("/ssr"); - let el = page.getByText("Env:"); + const el = page.getByText("Env:"); expect(await el.textContent()).toEqual("Env: foo"); }); diff --git a/packages/tests-e2e/tests/appRouter/headers.test.ts b/packages/tests-e2e/tests/appRouter/headers.test.ts index 192918a58..258ff96df 100644 --- a/packages/tests-e2e/tests/appRouter/headers.test.ts +++ b/packages/tests-e2e/tests/appRouter/headers.test.ts @@ -18,7 +18,7 @@ test("Headers", async ({ page }) => { expect(headers["e2e-headers"]).toEqual("next.config.js"); // Request header should be available in RSC - let el = page.getByText(`request-header`); + const el = page.getByText(`request-header`); await expect(el).toBeVisible(); // Both these headers should not be present cause poweredByHeader is false in appRouter diff --git a/packages/tests-e2e/tests/appRouter/host.test.ts b/packages/tests-e2e/tests/appRouter/host.test.ts index 99d39e88a..98141106c 100644 --- a/packages/tests-e2e/tests/appRouter/host.test.ts +++ b/packages/tests-e2e/tests/appRouter/host.test.ts @@ -6,6 +6,6 @@ import { expect, test } from "@playwright/test"; test("Request.url is host", async ({ baseURL, page }) => { await page.goto("/api/host"); - let el = page.getByText(`{"url":"${baseURL}/api/host"}`); + const el = page.getByText(`{"url":"${baseURL}/api/host"}`); await expect(el).toBeVisible(); }); diff --git a/packages/tests-e2e/tests/appRouter/image-optimization.test.ts b/packages/tests-e2e/tests/appRouter/image-optimization.test.ts index f67fd54c7..3a91a51af 100644 --- a/packages/tests-e2e/tests/appRouter/image-optimization.test.ts +++ b/packages/tests-e2e/tests/appRouter/image-optimization.test.ts @@ -14,7 +14,7 @@ test("Image Optimization", async ({ page }) => { const imageContentType = imageResponse.headers()["content-type"]; expect(imageContentType).toBe("image/webp"); - let el = page.locator("img"); + const el = page.locator("img"); await expect(el).toHaveJSProperty("complete", true); await expect(el).not.toHaveJSProperty("naturalWidth", 0); }); diff --git a/packages/tests-e2e/tests/appRouter/query.test.ts b/packages/tests-e2e/tests/appRouter/query.test.ts index f931426a6..ca8ccec86 100644 --- a/packages/tests-e2e/tests/appRouter/query.test.ts +++ b/packages/tests-e2e/tests/appRouter/query.test.ts @@ -6,11 +6,11 @@ import { expect, test } from "@playwright/test"; test("SearchQuery", async ({ page }) => { await page.goto("/search-query?searchParams=e2etest&multi=one&multi=two"); - let propsEl = page.getByText(`Search Params via Props: e2etest`); - let mwEl = page.getByText(`Search Params via Middleware: mw/e2etest`); - let multiEl = page.getByText(`Multi-value Params (key: multi): 2`); - let multiOne = page.getByText(`one`); - let multiTwo = page.getByText(`two`); + const propsEl = page.getByText(`Search Params via Props: e2etest`); + const mwEl = page.getByText(`Search Params via Middleware: mw/e2etest`); + const multiEl = page.getByText(`Multi-value Params (key: multi): 2`); + const multiOne = page.getByText(`one`); + const multiTwo = page.getByText(`two`); await expect(propsEl).toBeVisible(); await expect(mwEl).toBeVisible(); await expect(multiEl).toBeVisible(); diff --git a/packages/tests-e2e/tests/appRouter/revalidateTag.test.ts b/packages/tests-e2e/tests/appRouter/revalidateTag.test.ts index da1288a6d..a973e0cd9 100644 --- a/packages/tests-e2e/tests/appRouter/revalidateTag.test.ts +++ b/packages/tests-e2e/tests/appRouter/revalidateTag.test.ts @@ -17,7 +17,7 @@ test("Revalidate tag", async ({ page, request }) => { }); await page.goto("/revalidate-tag"); let elLayout = page.getByText("Fetched time:"); - let time = await elLayout.textContent(); + const time = await elLayout.textContent(); let newTime; let response = await responsePromise; diff --git a/packages/tests-e2e/tests/appRouter/ssr.test.ts b/packages/tests-e2e/tests/appRouter/ssr.test.ts index 2aedd2973..4a2b607b2 100644 --- a/packages/tests-e2e/tests/appRouter/ssr.test.ts +++ b/packages/tests-e2e/tests/appRouter/ssr.test.ts @@ -20,7 +20,7 @@ test("Server Side Render and loading.tsx", async ({ page }) => { loading = page.getByText("Loading..."); await expect(loading).toBeVisible(); - let el = page.getByText("Time:"); + const el = page.getByText("Time:"); await expect(el).toBeVisible(); const time = await el.textContent(); expect(time).not.toEqual(lastTime); diff --git a/packages/tests-e2e/tests/pagesRouter/redirect.test.ts b/packages/tests-e2e/tests/pagesRouter/redirect.test.ts index c558a2f00..3a8b290ad 100644 --- a/packages/tests-e2e/tests/pagesRouter/redirect.test.ts +++ b/packages/tests-e2e/tests/pagesRouter/redirect.test.ts @@ -4,7 +4,7 @@ test("Single redirect", async ({ page }) => { await page.goto("/next-config-redirect-without-locale-support/"); await page.waitForURL("https://opennext.js.org/"); - let el = page.getByRole("heading", { name: "OpenNext" }); + const el = page.getByRole("heading", { name: "OpenNext" }); await expect(el).toBeVisible(); }); @@ -12,7 +12,7 @@ test("Redirect with default locale support", async ({ page }) => { await page.goto("/redirect-with-locale/"); await page.waitForURL("/ssr/"); - let el = page.getByText("SSR"); + const el = page.getByText("SSR"); await expect(el).toBeVisible(); }); @@ -20,6 +20,6 @@ test("Redirect with locale support", async ({ page }) => { await page.goto("/nl/redirect-with-locale/"); await page.waitForURL("/nl/ssr/"); - let el = page.getByText("SSR"); + const el = page.getByText("SSR"); await expect(el).toBeVisible(); }); diff --git a/packages/tests-e2e/tests/pagesRouter/rewrite.test.ts b/packages/tests-e2e/tests/pagesRouter/rewrite.test.ts index ad0b6fa2b..44864b750 100644 --- a/packages/tests-e2e/tests/pagesRouter/rewrite.test.ts +++ b/packages/tests-e2e/tests/pagesRouter/rewrite.test.ts @@ -3,13 +3,13 @@ import { expect, test } from "@playwright/test"; test("Single Rewrite", async ({ page }) => { await page.goto("/rewrite"); - let el = page.getByText("Nextjs Pages Router"); + const el = page.getByText("Nextjs Pages Router"); await expect(el).toBeVisible(); }); test("Rewrite with query", async ({ page }) => { await page.goto("/rewriteUsingQuery?d=ssr"); - let el = page.getByText("SSR"); + const el = page.getByText("SSR"); await expect(el).toBeVisible(); }); diff --git a/packages/tests-e2e/tests/pagesRouter/ssr.test.ts b/packages/tests-e2e/tests/pagesRouter/ssr.test.ts index b518c872d..fb4139e4d 100644 --- a/packages/tests-e2e/tests/pagesRouter/ssr.test.ts +++ b/packages/tests-e2e/tests/pagesRouter/ssr.test.ts @@ -30,6 +30,6 @@ test("Server Side Render", async ({ page }) => { test("Server Side Render with env", async ({ page }) => { await page.goto("/ssr/"); - let el = page.getByText("Env:"); + const el = page.getByText("Env:"); expect(await el.textContent()).toEqual("Env: bar"); }); diff --git a/packages/tests-unit/tests/http/utils.test.ts b/packages/tests-unit/tests/http/utils.test.ts index 040db5319..3377269cd 100644 --- a/packages/tests-unit/tests/http/utils.test.ts +++ b/packages/tests-unit/tests/http/utils.test.ts @@ -1,13 +1,11 @@ import { parseCookies } from "@opennextjs/aws/http/util.js"; describe("parseCookies", () => { - it("returns undefined if cookies is empty", () => { - const cookies = parseCookies(""); - expect(cookies).toBeUndefined(); - }); - it("returns undefined if no cookies", () => { - const cookies = parseCookies(); - expect(cookies).toBeUndefined(); + it("returns an empty list if cookies is emptyish", () => { + expect(parseCookies("")).toEqual([]); + expect(parseCookies(null)).toEqual([]); + expect(parseCookies(undefined)).toEqual([]); + expect(parseCookies([])).toEqual([]); }); it("parse single cookie", () => { const cookies = parseCookies( diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index 5581c85e3..1003455b3 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -9,7 +9,7 @@ export function generateUniqueId() { return Math.random().toString(36).slice(2, 8); } -export async function wait(n: number = 1000) { +export async function wait(n = 1000) { return new Promise((res) => { setTimeout(res, n); }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 42d8cc667..06aca6886 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -224,8 +224,8 @@ importers: specifier: ^1.8.8 version: 1.8.10 typescript: - specifier: ^4.9.3 - version: 4.9.5 + specifier: ^5.6.3 + version: 5.6.3 packages/tests-e2e: devDependencies: @@ -237,7 +237,7 @@ importers: version: 2.0.0 ts-node: specifier: 10.9.1 - version: 10.9.1(@types/node@20.5.0)(typescript@4.9.5) + version: 10.9.1(@types/node@20.5.0)(typescript@5.6.3) packages/tests-unit: dependencies: @@ -271,7 +271,7 @@ importers: version: 20.5.0 tsup: specifier: 7.2.0 - version: 7.2.0(postcss@8.4.47)(ts-node@10.9.1(@types/node@20.5.0)(typescript@4.9.5))(typescript@4.9.5) + version: 7.2.0(postcss@8.4.47)(ts-node@10.9.1(@types/node@20.5.0)(typescript@5.6.3))(typescript@5.6.3) packages: @@ -4760,6 +4760,11 @@ packages: engines: {node: '>=4.2.0'} hasBin: true + typescript@5.6.3: + resolution: {integrity: sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==} + engines: {node: '>=14.17'} + hasBin: true + ufo@1.5.4: resolution: {integrity: sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==} @@ -9995,13 +10000,13 @@ snapshots: postcss: 8.4.27 ts-node: 10.9.1(@types/node@20.5.0)(typescript@4.9.5) - postcss-load-config@4.0.2(postcss@8.4.47)(ts-node@10.9.1(@types/node@20.5.0)(typescript@4.9.5)): + postcss-load-config@4.0.2(postcss@8.4.47)(ts-node@10.9.1(@types/node@20.5.0)(typescript@5.6.3)): dependencies: lilconfig: 3.1.2 yaml: 2.6.0 optionalDependencies: postcss: 8.4.47 - ts-node: 10.9.1(@types/node@20.5.0)(typescript@4.9.5) + ts-node: 10.9.1(@types/node@20.5.0)(typescript@5.6.3) postcss-nested@6.2.0(postcss@8.4.27): dependencies: @@ -10741,6 +10746,25 @@ snapshots: typescript: 4.9.5 v8-compile-cache-lib: 3.0.1 yn: 3.1.1 + optional: true + + ts-node@10.9.1(@types/node@20.5.0)(typescript@5.6.3): + dependencies: + '@cspotcode/source-map-support': 0.8.1 + '@tsconfig/node10': 1.0.11 + '@tsconfig/node12': 1.0.11 + '@tsconfig/node14': 1.0.3 + '@tsconfig/node16': 1.0.4 + '@types/node': 20.5.0 + acorn: 8.13.0 + acorn-walk: 8.3.4 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.2 + make-error: 1.3.6 + typescript: 5.6.3 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 tsc-alias@1.8.10: dependencies: @@ -10755,7 +10779,7 @@ snapshots: tslib@2.8.0: {} - tsup@7.2.0(postcss@8.4.47)(ts-node@10.9.1(@types/node@20.5.0)(typescript@4.9.5))(typescript@4.9.5): + tsup@7.2.0(postcss@8.4.47)(ts-node@10.9.1(@types/node@20.5.0)(typescript@5.6.3))(typescript@5.6.3): dependencies: bundle-require: 4.2.1(esbuild@0.18.20) cac: 6.7.14 @@ -10765,7 +10789,7 @@ snapshots: execa: 5.1.1 globby: 11.1.0 joycon: 3.1.1 - postcss-load-config: 4.0.2(postcss@8.4.47)(ts-node@10.9.1(@types/node@20.5.0)(typescript@4.9.5)) + postcss-load-config: 4.0.2(postcss@8.4.47)(ts-node@10.9.1(@types/node@20.5.0)(typescript@5.6.3)) resolve-from: 5.0.0 rollup: 3.29.5 source-map: 0.8.0-beta.0 @@ -10773,7 +10797,7 @@ snapshots: tree-kill: 1.2.2 optionalDependencies: postcss: 8.4.47 - typescript: 4.9.5 + typescript: 5.6.3 transitivePeerDependencies: - supports-color - ts-node @@ -10826,6 +10850,8 @@ snapshots: typescript@4.9.5: {} + typescript@5.6.3: {} + ufo@1.5.4: {} undici-types@5.26.5: {}