@opennextjs/aws 3.5.0 → 3.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/build/copyTracedFiles.js +14 -5
- package/dist/core/routing/i18n/index.d.ts +28 -2
- package/dist/core/routing/i18n/index.js +97 -3
- package/dist/core/routing/matcher.d.ts +1 -1
- package/dist/core/routing/matcher.js +13 -2
- package/dist/core/routing/util.js +2 -1
- package/dist/types/next-types.d.ts +7 -0
- package/package.json +1 -1
|
@@ -2,9 +2,21 @@ import url from "node:url";
|
|
|
2
2
|
import { copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, readlinkSync, statSync, symlinkSync, writeFileSync, } from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { loadConfig, loadPrerenderManifest } from "../adapters/config/util.js";
|
|
5
|
+
import { getCrossPlatformPathRegex } from "../utils/regex.js";
|
|
5
6
|
import logger from "../logger.js";
|
|
6
7
|
import { MIDDLEWARE_TRACE_FILE } from "./constant.js";
|
|
7
8
|
const __dirname = url.fileURLToPath(new URL(".", import.meta.url));
|
|
9
|
+
//TODO: we need to figure which packages we could safely remove
|
|
10
|
+
const EXCLUDED_PACKAGES = [
|
|
11
|
+
"caniuse-lite",
|
|
12
|
+
"sharp",
|
|
13
|
+
// This seems to be only in Next 15
|
|
14
|
+
// Some of sharp deps are under the @img scope
|
|
15
|
+
"@img",
|
|
16
|
+
];
|
|
17
|
+
function isExcluded(srcPath) {
|
|
18
|
+
return EXCLUDED_PACKAGES.some((excluded) => srcPath.match(getCrossPlatformPathRegex(`/node_modules/${excluded}/`)));
|
|
19
|
+
}
|
|
8
20
|
function copyPatchFile(outputDir) {
|
|
9
21
|
const patchFile = path.join(__dirname, "patch", "patchedAsyncStorage.js");
|
|
10
22
|
const outputPatchFile = path.join(outputDir, "patchedAsyncStorage.cjs");
|
|
@@ -119,11 +131,8 @@ File ${fullFilePath} does not exist
|
|
|
119
131
|
});
|
|
120
132
|
//Actually copy the files
|
|
121
133
|
filesToCopy.forEach((to, from) => {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
from.includes(path.join("node_modules", "caniuse-lite")) ||
|
|
125
|
-
// from.includes("jest-worker") || This ones seems necessary for next 12
|
|
126
|
-
from.includes(path.join("node_modules", "sharp"))) {
|
|
134
|
+
// We don't want to copy excluded packages (i.e sharp)
|
|
135
|
+
if (isExcluded(from)) {
|
|
127
136
|
return;
|
|
128
137
|
}
|
|
129
138
|
mkdirSync(path.dirname(to), { recursive: true });
|
|
@@ -1,4 +1,30 @@
|
|
|
1
|
-
import type { i18nConfig } from "../../../types/next-types";
|
|
2
|
-
import type { InternalEvent } from "../../../types/open-next";
|
|
1
|
+
import type { DomainLocale, i18nConfig } from "../../../types/next-types";
|
|
2
|
+
import type { InternalEvent, InternalResult } from "../../../types/open-next";
|
|
3
|
+
/**
|
|
4
|
+
* @param arg an object containing the hostname and detectedLocale
|
|
5
|
+
* @returns The `DomainLocale` object if a domain is detected, `undefined` otherwise
|
|
6
|
+
*/
|
|
7
|
+
export declare function detectDomainLocale({ hostname, detectedLocale, }: {
|
|
8
|
+
hostname?: string;
|
|
9
|
+
detectedLocale?: string;
|
|
10
|
+
}): DomainLocale | undefined;
|
|
11
|
+
/**
|
|
12
|
+
*
|
|
13
|
+
* @param internalEvent
|
|
14
|
+
* @param i18n
|
|
15
|
+
* @returns The detected locale, if `localeDetection` is set to `false` it will return the default locale **or** the domain default locale if a domain is detected.
|
|
16
|
+
*/
|
|
3
17
|
export declare function detectLocale(internalEvent: InternalEvent, i18n: i18nConfig): string;
|
|
18
|
+
/**
|
|
19
|
+
* This function is used for OpenNext internal routing to localize the path for next config rewrite/redirects/headers and the middleware
|
|
20
|
+
* @param internalEvent
|
|
21
|
+
* @returns The localized path
|
|
22
|
+
*/
|
|
4
23
|
export declare function localizePath(internalEvent: InternalEvent): string;
|
|
24
|
+
/**
|
|
25
|
+
*
|
|
26
|
+
* @param internalEvent
|
|
27
|
+
* In this function, for domain locale redirect we need to rely on the host to be present and correct
|
|
28
|
+
* @returns `false` if no redirect is needed, `InternalResult` if a redirect is needed
|
|
29
|
+
*/
|
|
30
|
+
export declare function handleLocaleRedirect(internalEvent: InternalEvent): false | InternalResult;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { NextConfig } from "../../../adapters/config/index.js";
|
|
2
|
+
import { emptyReadableStream } from "../../../utils/stream.js";
|
|
2
3
|
import { debug } from "../../../adapters/logger.js";
|
|
4
|
+
import { constructNextUrl } from "../util.js";
|
|
3
5
|
import { acceptLanguage } from "./accept-header";
|
|
4
6
|
function isLocalizedPath(path) {
|
|
5
7
|
return (NextConfig.i18n?.locales.includes(path.split("/")[1].toLowerCase()) ?? false);
|
|
@@ -12,9 +14,40 @@ function getLocaleFromCookie(cookies) {
|
|
|
12
14
|
? i18n?.locales.find((locale) => nextLocale === locale.toLowerCase())
|
|
13
15
|
: undefined;
|
|
14
16
|
}
|
|
17
|
+
// Inspired by https://github.com/vercel/next.js/blob/6d93d652e0e7ba72d9a3b66e78746dce2069db03/packages/next/src/shared/lib/i18n/detect-domain-locale.ts#L3-L25
|
|
18
|
+
/**
|
|
19
|
+
* @param arg an object containing the hostname and detectedLocale
|
|
20
|
+
* @returns The `DomainLocale` object if a domain is detected, `undefined` otherwise
|
|
21
|
+
*/
|
|
22
|
+
export function detectDomainLocale({ hostname, detectedLocale, }) {
|
|
23
|
+
const i18n = NextConfig.i18n;
|
|
24
|
+
const domains = i18n?.domains;
|
|
25
|
+
if (!domains) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const lowercasedLocale = detectedLocale?.toLowerCase();
|
|
29
|
+
for (const domain of domains) {
|
|
30
|
+
// We remove the port if present
|
|
31
|
+
const domainHostname = domain.domain.split(":", 1)[0].toLowerCase();
|
|
32
|
+
if (hostname === domainHostname ||
|
|
33
|
+
lowercasedLocale === domain.defaultLocale.toLowerCase() ||
|
|
34
|
+
domain.locales?.some((locale) => lowercasedLocale === locale.toLowerCase())) {
|
|
35
|
+
return domain;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
*
|
|
41
|
+
* @param internalEvent
|
|
42
|
+
* @param i18n
|
|
43
|
+
* @returns The detected locale, if `localeDetection` is set to `false` it will return the default locale **or** the domain default locale if a domain is detected.
|
|
44
|
+
*/
|
|
15
45
|
export function detectLocale(internalEvent, i18n) {
|
|
46
|
+
const domainLocale = detectDomainLocale({
|
|
47
|
+
hostname: internalEvent.headers.host,
|
|
48
|
+
});
|
|
16
49
|
if (i18n.localeDetection === false) {
|
|
17
|
-
return i18n.defaultLocale;
|
|
50
|
+
return domainLocale?.defaultLocale ?? i18n.defaultLocale;
|
|
18
51
|
}
|
|
19
52
|
const cookiesLocale = getLocaleFromCookie(internalEvent.cookies);
|
|
20
53
|
const preferredLocale = acceptLanguage(internalEvent.headers["accept-language"], i18n?.locales);
|
|
@@ -22,18 +55,79 @@ export function detectLocale(internalEvent, i18n) {
|
|
|
22
55
|
cookiesLocale,
|
|
23
56
|
preferredLocale,
|
|
24
57
|
defaultLocale: i18n.defaultLocale,
|
|
58
|
+
domainLocale,
|
|
25
59
|
});
|
|
26
|
-
return
|
|
27
|
-
|
|
60
|
+
return (domainLocale?.defaultLocale ??
|
|
61
|
+
cookiesLocale ??
|
|
62
|
+
preferredLocale ??
|
|
63
|
+
i18n.defaultLocale);
|
|
28
64
|
}
|
|
65
|
+
/**
|
|
66
|
+
* This function is used for OpenNext internal routing to localize the path for next config rewrite/redirects/headers and the middleware
|
|
67
|
+
* @param internalEvent
|
|
68
|
+
* @returns The localized path
|
|
69
|
+
*/
|
|
29
70
|
export function localizePath(internalEvent) {
|
|
30
71
|
const i18n = NextConfig.i18n;
|
|
31
72
|
if (!i18n) {
|
|
32
73
|
return internalEvent.rawPath;
|
|
33
74
|
}
|
|
75
|
+
// When the path is already localized we don't need to do anything
|
|
34
76
|
if (isLocalizedPath(internalEvent.rawPath)) {
|
|
35
77
|
return internalEvent.rawPath;
|
|
36
78
|
}
|
|
37
79
|
const detectedLocale = detectLocale(internalEvent, i18n);
|
|
38
80
|
return `/${detectedLocale}${internalEvent.rawPath}`;
|
|
39
81
|
}
|
|
82
|
+
/**
|
|
83
|
+
*
|
|
84
|
+
* @param internalEvent
|
|
85
|
+
* In this function, for domain locale redirect we need to rely on the host to be present and correct
|
|
86
|
+
* @returns `false` if no redirect is needed, `InternalResult` if a redirect is needed
|
|
87
|
+
*/
|
|
88
|
+
export function handleLocaleRedirect(internalEvent) {
|
|
89
|
+
const i18n = NextConfig.i18n;
|
|
90
|
+
if (!i18n ||
|
|
91
|
+
i18n.localeDetection === false ||
|
|
92
|
+
internalEvent.rawPath !== "/") {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
const preferredLocale = acceptLanguage(internalEvent.headers["accept-language"], i18n?.locales);
|
|
96
|
+
const detectedLocale = detectLocale(internalEvent, i18n);
|
|
97
|
+
const domainLocale = detectDomainLocale({
|
|
98
|
+
hostname: internalEvent.headers.host,
|
|
99
|
+
});
|
|
100
|
+
const preferredDomain = detectDomainLocale({
|
|
101
|
+
detectedLocale: preferredLocale,
|
|
102
|
+
});
|
|
103
|
+
if (domainLocale && preferredDomain) {
|
|
104
|
+
const isPDomain = preferredDomain.domain === domainLocale.domain;
|
|
105
|
+
const isPLocale = preferredDomain.defaultLocale === preferredLocale;
|
|
106
|
+
if (!isPDomain || !isPLocale) {
|
|
107
|
+
const scheme = `http${preferredDomain.http ? "" : "s"}`;
|
|
108
|
+
const rlocale = isPLocale ? "" : preferredLocale;
|
|
109
|
+
return {
|
|
110
|
+
type: "core",
|
|
111
|
+
statusCode: 307,
|
|
112
|
+
headers: {
|
|
113
|
+
Location: `${scheme}://${preferredDomain.domain}/${rlocale}`,
|
|
114
|
+
},
|
|
115
|
+
body: emptyReadableStream(),
|
|
116
|
+
isBase64Encoded: false,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
const defaultLocale = domainLocale?.defaultLocale ?? i18n.defaultLocale;
|
|
121
|
+
if (detectedLocale.toLowerCase() !== defaultLocale.toLowerCase()) {
|
|
122
|
+
return {
|
|
123
|
+
type: "core",
|
|
124
|
+
statusCode: 307,
|
|
125
|
+
headers: {
|
|
126
|
+
Location: constructNextUrl(internalEvent.url, `/${detectedLocale}`),
|
|
127
|
+
},
|
|
128
|
+
body: emptyReadableStream(),
|
|
129
|
+
isBase64Encoded: false,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
@@ -3,12 +3,12 @@ import type { InternalEvent, InternalResult } from "../../types/open-next";
|
|
|
3
3
|
export declare function getNextConfigHeaders(event: InternalEvent, configHeaders?: Header[] | undefined): Record<string, string | undefined>;
|
|
4
4
|
export declare function handleRewrites<T extends RewriteDefinition>(event: InternalEvent, rewrites: T[]): {
|
|
5
5
|
internalEvent: {
|
|
6
|
+
query: Record<string, string | string[]>;
|
|
6
7
|
rawPath: string;
|
|
7
8
|
url: string;
|
|
8
9
|
method: string;
|
|
9
10
|
body?: Buffer;
|
|
10
11
|
headers: Record<string, string>;
|
|
11
|
-
query: Record<string, string | string[]>;
|
|
12
12
|
cookies: Record<string, string>;
|
|
13
13
|
remoteAddress: string;
|
|
14
14
|
type: "core";
|
|
@@ -2,7 +2,7 @@ import { NextConfig } from "../../adapters/config/index";
|
|
|
2
2
|
import { compile, match } from "path-to-regexp";
|
|
3
3
|
import { emptyReadableStream, toReadableStream } from "../../utils/stream";
|
|
4
4
|
import { debug } from "../../adapters/logger";
|
|
5
|
-
import { localizePath } from "./i18n";
|
|
5
|
+
import { handleLocaleRedirect, localizePath } from "./i18n";
|
|
6
6
|
import { constructNextUrl, convertFromQueryString, convertToQueryString, escapeRegex, getUrlParts, isExternal, unescapeRegex, } from "./util";
|
|
7
7
|
const routeHasMatcher = (headers, cookies, query) => (redirect) => {
|
|
8
8
|
switch (redirect.type) {
|
|
@@ -145,10 +145,17 @@ export function handleRewrites(event, rewrites) {
|
|
|
145
145
|
rewrittenHost = unescapeRegex(toDestinationHost(params));
|
|
146
146
|
rewrittenQuery = unescapeRegex(toDestinationQuery(params));
|
|
147
147
|
}
|
|
148
|
+
// We need to strip the locale from the path if it's a local api route
|
|
149
|
+
if (NextConfig.i18n && !isExternalRewrite) {
|
|
150
|
+
const strippedPathLocale = rewrittenPath.replace(new RegExp(`^/(${NextConfig.i18n.locales.join("|")})`), "");
|
|
151
|
+
if (strippedPathLocale.startsWith("/api/")) {
|
|
152
|
+
rewrittenPath = strippedPathLocale;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
148
155
|
rewrittenUrl = isExternalRewrite
|
|
149
156
|
? `${protocol}//${rewrittenHost}${rewrittenPath}`
|
|
150
157
|
: new URL(rewrittenPath, event.url).href;
|
|
151
|
-
//
|
|
158
|
+
// We merge query params from the source and the destination
|
|
152
159
|
finalQuery = {
|
|
153
160
|
...query,
|
|
154
161
|
...convertFromQueryString(rewrittenQuery),
|
|
@@ -159,6 +166,7 @@ export function handleRewrites(event, rewrites) {
|
|
|
159
166
|
return {
|
|
160
167
|
internalEvent: {
|
|
161
168
|
...event,
|
|
169
|
+
query: finalQuery,
|
|
162
170
|
rawPath: new URL(rewrittenUrl).pathname,
|
|
163
171
|
url: rewrittenUrl,
|
|
164
172
|
},
|
|
@@ -215,6 +223,9 @@ export function handleRedirects(event, redirects) {
|
|
|
215
223
|
const trailingSlashRedirect = handleTrailingSlashRedirect(event);
|
|
216
224
|
if (trailingSlashRedirect)
|
|
217
225
|
return trailingSlashRedirect;
|
|
226
|
+
const localeRedirect = handleLocaleRedirect(event);
|
|
227
|
+
if (localeRedirect)
|
|
228
|
+
return localeRedirect;
|
|
218
229
|
const { internalEvent, __rewrite } = handleRewrites(event, redirects.filter((r) => !r.internal));
|
|
219
230
|
if (__rewrite && !__rewrite.internal) {
|
|
220
231
|
return {
|
|
@@ -67,7 +67,8 @@ export function getUrlParts(url, isExternal) {
|
|
|
67
67
|
* @__PURE__
|
|
68
68
|
*/
|
|
69
69
|
export function constructNextUrl(baseUrl, path) {
|
|
70
|
-
|
|
70
|
+
// basePath is generated as "" if not provided on Next.js 15 (not sure about older versions)
|
|
71
|
+
const nextBasePath = NextConfig.basePath ?? "";
|
|
71
72
|
const url = new URL(`${nextBasePath}${path}`, baseUrl);
|
|
72
73
|
return url.href;
|
|
73
74
|
}
|
|
@@ -52,9 +52,16 @@ export type Header = {
|
|
|
52
52
|
has?: RouteHas[];
|
|
53
53
|
missing?: RouteHas[];
|
|
54
54
|
};
|
|
55
|
+
export interface DomainLocale {
|
|
56
|
+
defaultLocale: string;
|
|
57
|
+
domain: string;
|
|
58
|
+
http?: true;
|
|
59
|
+
locales: readonly string[];
|
|
60
|
+
}
|
|
55
61
|
export interface i18nConfig {
|
|
56
62
|
locales: string[];
|
|
57
63
|
defaultLocale: string;
|
|
64
|
+
domains?: DomainLocale[];
|
|
58
65
|
localeDetection?: false;
|
|
59
66
|
}
|
|
60
67
|
export interface NextConfig {
|