cloudflare-next-intl 0.9.58 → 0.9.60
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/README.md +6 -1
- package/dist/src/firebase_auth_check/check_firebase_auth_config.d.ts +29 -0
- package/dist/src/firebase_auth_check/check_firebase_auth_config.js +250 -0
- package/dist/src/firebase_auth_check/index.d.ts +1 -0
- package/dist/src/firebase_auth_check/index.js +1 -0
- package/dist/src/server/components/helper_script.js +30 -3
- package/dist/src/vite/firebase_auth_check_plugin.d.ts +7 -0
- package/dist/src/vite/firebase_auth_check_plugin.js +35 -0
- package/dist/src/vite/index.d.ts +1 -0
- package/dist/src/vite/index.js +1 -0
- package/dist/src/vite/plugin.d.ts +2 -0
- package/dist/src/vite/plugin.js +6 -0
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -269,6 +269,7 @@ export default defineConfig({
|
|
|
269
269
|
8. **Build ID Asset Emission (`buildIdAsset`)**: Emits `BUILD_ID` static asset in the client build directory from `process.env.__VINEXT_SHARED_BUILD_ID` or `process.env.__VINEXT_BUILD_ID`.
|
|
270
270
|
9. **Vinext Route Wiring & Optimistic Prefetch Fix (`vinextRouteWiringFix`)**: ⚠️ Monkey-patches Vinext's compiled runtime on disk (`node_modules/vinext/dist/**`) — **on by default** since 0.9.51 via `experimentalRouteLoadingFixes` (also defaulting to on). Fixes route wiring (a route-specific `loading.tsx` losing to an ancestor/root skeleton), route matching and optimistic (client-side) routing for leading `:locale` segments, prefetch learning (an in-flight prefetch losing the race so the previous page stays on screen), and a `refresh()` navigation cancelling an in-flight page-to-page navigation because both share the same counter. Every sub-patch is regex-based against vinext's exact compiled shape and checks that the vinext-internal identifiers it relies on (`makeThenableParams`, `trieMatch`, `getPrefetchCache`, …) are still present before touching a file — a future vinext release that renames or removes one is left unpatched (with a console warning) rather than being patched into a broken half-state. Set `experimentalRouteLoadingFixes: false` (or `vinextRouteWiringFix: false`) to opt out entirely; see **Plugin Options** below for the individual sub-patch flags (`routeWiring`, `routeMatching`, `optimisticRouting`, `prefetchLearning`, `suspenseProbe`, `renderDependency`, `optimisticLearningTimeout`, `pageInvokerSuspensionRelease`, `refreshDeferral`, `unblockRenderDependencies`, `unblockPageElementDependencies`).
|
|
271
271
|
10. **Layout DB Query Check (`layoutQueriesCheck`)**: Scans each `layout.tsx`/`layout.ts` and its reachable server-component import tree (also usable standalone from `cloudflare-next-intl/checkLayoutQueries`, or via the `cfni-check-layout-queries` CLI bin) for blocking `withUserDb()` / `withPublicDb()` calls. A layout re-renders on every route transition within its group, so a DB query anywhere in that tree blocks every page switch under it — the scan stops at Client Components (`'use client'`), which don't block server layout streaming. Prints a visible terminal warning with concrete fixes (move to a Client Component, wrap in `unstable_cache`, or move the query out of the shared layout into the page) by default; pass `{ strict: true }` to fail the build instead, or `false` to disable.
|
|
272
|
+
12. **Firebase Auth Config Check (`firebaseAuthCheck`)**: Statically validates the `firebaseAuth` block of your `@intl-config` file on both `vite dev` and `vite build` (also usable standalone from `cloudflare-next-intl/checkFirebaseAuthConfig`). Errors on the fields nothing works without (`apiKey`, `authDomain`, `projectId`, `appId`, `redirectAuthPath`, `homePath`); warns when `appCheck` is present but server-side App Check minting can't run — `clientEmail`/`appId` plus **one** of `privateKey` or the full `oauthClientId`/`oauthClientSecret`/`oauthRefreshToken` triple — the misconfiguration whose only other symptom is a signed-in user rendering as signed-out on a cold navigation. Env-var-backed fields are resolved against `.env*` (Vite `loadEnv`, empty prefix) + `process.env`, so an unset `FIREBASE_SERVICE_ACCOUNT_*` secret is caught before deploy. Deliberately conservative: a value it can't evaluate (a call, an imported constant, a `??` fallback) counts as present. Prints a warning by default; pass `{ strict: true }` to fail the build, `false` to disable, or `appCheck.reportMissingServerCredentials: false` in your config to silence just the App Check warnings. No-op when there is no `firebaseAuth` block.
|
|
272
273
|
11. **Lucide & Next.js Specifier Optimizer (`lucideOptimizer`)**: Auto-detects `lucide-react` in project dependencies, rewrites named imports to direct deep icon paths (`lucide-react/dist/esm/icons/<icon>.mjs`) to avoid browser socket exhaustion (`ERR_INSUFFICIENT_RESOURCES`), and normalizes Next.js `.js` specifiers (`next/dynamic.js` -> `next/dynamic`) to prevent mid-session Vite re-optimization and React dispatcher splitting.
|
|
273
274
|
|
|
274
275
|
##### Plugin Options
|
|
@@ -304,6 +305,10 @@ export default defineConfig({
|
|
|
304
305
|
strict: false, // Fail the build on violations instead of just warning (default: false)
|
|
305
306
|
runOnDev: true, // Also run on `vite dev`, not just build (default: true)
|
|
306
307
|
},
|
|
308
|
+
firebaseAuthCheck: { // Validate the `firebaseAuth` config block (or `false` to disable)
|
|
309
|
+
strict: false, // Fail the build on missing required fields instead of warning (default: false)
|
|
310
|
+
runOnDev: true, // Also run on `vite dev`, not just build (default: true)
|
|
311
|
+
},
|
|
307
312
|
messagesDir: "./messages", // Path to locale JSON files (default: './messages')
|
|
308
313
|
intlConfigPath: "./src/l18n/intl_config.ts", // Path to intl config (auto-detected if omitted)
|
|
309
314
|
buildIdAsset: true, // Emit BUILD_ID asset (or custom string filename, default: true)
|
|
@@ -333,7 +338,7 @@ export default defineConfig({
|
|
|
333
338
|
> **Note:** `unblockPageElementDependencies: true` was measured, in a real app, to make page-to-page navigation noticeably slower rather than faster — it strips ordering vinext's own page-element wiring relies on. Leave it off unless you've verified otherwise for your app.
|
|
334
339
|
|
|
335
340
|
Individual standalone plugins are also exported if you only need a specific feature:
|
|
336
|
-
`imageOptimizerPlugin` (or `imageOptimizer`), `autoLocaleParamsPlugin`, `layoutQueriesPlugin` (or `layoutQueriesCheck`), `buildIdAsset`, `localeFilePlugin`, `userAgentStubPlugin`, `cfWorkersClientStubPlugin`, `vinextRouteWiringFixPlugin`, `lucideOptimizerPlugin`.
|
|
341
|
+
`imageOptimizerPlugin` (or `imageOptimizer`), `autoLocaleParamsPlugin`, `layoutQueriesPlugin` (or `layoutQueriesCheck`), `firebaseAuthCheckPlugin` (or `firebaseAuthCheck`), `buildIdAsset`, `localeFilePlugin`, `userAgentStubPlugin`, `cfWorkersClientStubPlugin`, `vinextRouteWiringFixPlugin`, `lucideOptimizerPlugin`.
|
|
337
342
|
|
|
338
343
|
##### Per-Image Optimizer Settings
|
|
339
344
|
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export interface FirebaseAuthConfigIssue {
|
|
2
|
+
field: string;
|
|
3
|
+
severity: "error" | "warning";
|
|
4
|
+
reason: string;
|
|
5
|
+
envVar?: string;
|
|
6
|
+
lineNumber?: number;
|
|
7
|
+
}
|
|
8
|
+
export interface CheckFirebaseAuthConfigOptions {
|
|
9
|
+
intlConfigPath?: string;
|
|
10
|
+
source?: string;
|
|
11
|
+
env?: Record<string, string | undefined>;
|
|
12
|
+
throwOnError?: boolean;
|
|
13
|
+
}
|
|
14
|
+
export interface CheckFirebaseAuthConfigReport {
|
|
15
|
+
valid: boolean;
|
|
16
|
+
checked: boolean;
|
|
17
|
+
issues: FirebaseAuthConfigIssue[];
|
|
18
|
+
formattedMessage: string;
|
|
19
|
+
}
|
|
20
|
+
export declare function extractObjectLiteral(source: string, key: string): {
|
|
21
|
+
body: string;
|
|
22
|
+
start: number;
|
|
23
|
+
} | null;
|
|
24
|
+
export declare function extractFieldValue(body: string, key: string): {
|
|
25
|
+
value: string;
|
|
26
|
+
lineNumber: number;
|
|
27
|
+
} | null;
|
|
28
|
+
export declare function formatFirebaseAuthConfigMessage(issues: FirebaseAuthConfigIssue[], intlConfigPath?: string): string;
|
|
29
|
+
export declare function checkFirebaseAuthConfig(options?: CheckFirebaseAuthConfigOptions): CheckFirebaseAuthConfigReport;
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
const REQUIRED_AUTH_FIELDS = [
|
|
3
|
+
"apiKey",
|
|
4
|
+
"authDomain",
|
|
5
|
+
"projectId",
|
|
6
|
+
"appId",
|
|
7
|
+
"redirectAuthPath",
|
|
8
|
+
"homePath",
|
|
9
|
+
];
|
|
10
|
+
const REQUIRED_APP_CHECK_FIELDS = ["clientEmail", "appId"];
|
|
11
|
+
const OAUTH_TRIPLE = ["oauthClientId", "oauthClientSecret", "oauthRefreshToken"];
|
|
12
|
+
export function extractObjectLiteral(source, key) {
|
|
13
|
+
const keyMatch = new RegExp(`(^|[\\s{,])${key}\\s*:`).exec(source);
|
|
14
|
+
if (!keyMatch)
|
|
15
|
+
return null;
|
|
16
|
+
const afterKey = keyMatch.index + keyMatch[0].length;
|
|
17
|
+
const open = source.indexOf("{", afterKey);
|
|
18
|
+
if (open === -1)
|
|
19
|
+
return null;
|
|
20
|
+
if (/[,;]/.test(source.slice(afterKey, open)))
|
|
21
|
+
return null;
|
|
22
|
+
let depth = 0;
|
|
23
|
+
let index = open;
|
|
24
|
+
let quote = null;
|
|
25
|
+
let comment = null;
|
|
26
|
+
while (index < source.length) {
|
|
27
|
+
const char = source[index];
|
|
28
|
+
const next = source[index + 1];
|
|
29
|
+
if (comment === "line") {
|
|
30
|
+
if (char === "\n")
|
|
31
|
+
comment = null;
|
|
32
|
+
}
|
|
33
|
+
else if (comment === "block") {
|
|
34
|
+
if (char === "*" && next === "/") {
|
|
35
|
+
comment = null;
|
|
36
|
+
index += 1;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
else if (quote) {
|
|
40
|
+
if (char === "\\")
|
|
41
|
+
index += 1;
|
|
42
|
+
else if (char === quote)
|
|
43
|
+
quote = null;
|
|
44
|
+
}
|
|
45
|
+
else if (char === "/" && next === "/") {
|
|
46
|
+
comment = "line";
|
|
47
|
+
index += 1;
|
|
48
|
+
}
|
|
49
|
+
else if (char === "/" && next === "*") {
|
|
50
|
+
comment = "block";
|
|
51
|
+
index += 1;
|
|
52
|
+
}
|
|
53
|
+
else if (char === '"' || char === "'" || char === "`") {
|
|
54
|
+
quote = char;
|
|
55
|
+
}
|
|
56
|
+
else if (char === "{") {
|
|
57
|
+
depth += 1;
|
|
58
|
+
}
|
|
59
|
+
else if (char === "}") {
|
|
60
|
+
depth -= 1;
|
|
61
|
+
if (depth === 0) {
|
|
62
|
+
return { body: source.slice(open + 1, index), start: open };
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
index += 1;
|
|
66
|
+
}
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
export function extractFieldValue(body, key) {
|
|
70
|
+
const keyMatch = new RegExp(`(^|[\\s{,])${key}\\s*:`).exec(body);
|
|
71
|
+
if (!keyMatch)
|
|
72
|
+
return null;
|
|
73
|
+
const start = keyMatch.index + keyMatch[0].length;
|
|
74
|
+
let index = start;
|
|
75
|
+
let depth = 0;
|
|
76
|
+
let quote = null;
|
|
77
|
+
while (index < body.length) {
|
|
78
|
+
const char = body[index];
|
|
79
|
+
if (quote) {
|
|
80
|
+
if (char === "\\")
|
|
81
|
+
index += 1;
|
|
82
|
+
else if (char === quote)
|
|
83
|
+
quote = null;
|
|
84
|
+
}
|
|
85
|
+
else if (char === '"' || char === "'" || char === "`") {
|
|
86
|
+
quote = char;
|
|
87
|
+
}
|
|
88
|
+
else if (char === "{" || char === "[" || char === "(") {
|
|
89
|
+
depth += 1;
|
|
90
|
+
}
|
|
91
|
+
else if (char === "}" || char === "]" || char === ")") {
|
|
92
|
+
if (depth === 0)
|
|
93
|
+
break;
|
|
94
|
+
depth -= 1;
|
|
95
|
+
}
|
|
96
|
+
else if (char === "," && depth === 0) {
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
index += 1;
|
|
100
|
+
}
|
|
101
|
+
return {
|
|
102
|
+
value: body.slice(start, index).trim(),
|
|
103
|
+
lineNumber: body.slice(0, start).split("\n").length,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
function envVarsIn(value) {
|
|
107
|
+
const names = [];
|
|
108
|
+
const pattern = /process\.env\s*(?:\.\s*([A-Za-z_$][\w$]*)|\[\s*["'`]([^"'`]+)["'`]\s*\])/g;
|
|
109
|
+
let match;
|
|
110
|
+
while ((match = pattern.exec(value)) !== null) {
|
|
111
|
+
const name = match[1] ?? match[2];
|
|
112
|
+
if (name)
|
|
113
|
+
names.push(name);
|
|
114
|
+
}
|
|
115
|
+
return names;
|
|
116
|
+
}
|
|
117
|
+
function evaluateValue(value, env) {
|
|
118
|
+
if (value === "" || value === "undefined" || value === "null") {
|
|
119
|
+
return { ok: false, reason: "set to `" + (value || "nothing") + "`" };
|
|
120
|
+
}
|
|
121
|
+
const literal = /^(["'`])(.*)\1$/s.exec(value);
|
|
122
|
+
if (literal && literal[2].trim() === "") {
|
|
123
|
+
return { ok: false, reason: "an empty string literal" };
|
|
124
|
+
}
|
|
125
|
+
if (literal)
|
|
126
|
+
return { ok: true };
|
|
127
|
+
const envVars = envVarsIn(value);
|
|
128
|
+
if (envVars.length === 0)
|
|
129
|
+
return { ok: true };
|
|
130
|
+
const withoutEnv = value.replace(/process\.env\s*(?:\.\s*[A-Za-z_$][\w$]*|\[\s*["'`][^"'`]+["'`]\s*\])/g, "");
|
|
131
|
+
const hasFallback = /[A-Za-z0-9_$"'`]/.test(withoutEnv.replace(/\bas\b|\bstring\b|\bundefined\b|\bnull\b/g, ""));
|
|
132
|
+
const unset = envVars.filter((name) => !env[name] || env[name].trim() === "");
|
|
133
|
+
if (unset.length === envVars.length && !hasFallback) {
|
|
134
|
+
return {
|
|
135
|
+
ok: false,
|
|
136
|
+
reason: "read from `process.env." + unset[0] + "`, which is not set",
|
|
137
|
+
envVar: unset[0],
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
return { ok: true };
|
|
141
|
+
}
|
|
142
|
+
function checkField(body, path, key, severity, env, baseLine) {
|
|
143
|
+
const field = `${path}.${key}`;
|
|
144
|
+
const found = extractFieldValue(body, key);
|
|
145
|
+
if (!found) {
|
|
146
|
+
return { field, severity, reason: "missing from the config" };
|
|
147
|
+
}
|
|
148
|
+
const verdict = evaluateValue(found.value, env);
|
|
149
|
+
if (verdict.ok)
|
|
150
|
+
return null;
|
|
151
|
+
return {
|
|
152
|
+
field,
|
|
153
|
+
severity,
|
|
154
|
+
reason: verdict.reason,
|
|
155
|
+
...(verdict.envVar ? { envVar: verdict.envVar } : {}),
|
|
156
|
+
lineNumber: baseLine + found.lineNumber - 1,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
export function formatFirebaseAuthConfigMessage(issues, intlConfigPath) {
|
|
160
|
+
if (issues.length === 0)
|
|
161
|
+
return "";
|
|
162
|
+
const separator = "=".repeat(84);
|
|
163
|
+
const thinSeparator = "-".repeat(84);
|
|
164
|
+
const errors = issues.filter((issue) => issue.severity === "error");
|
|
165
|
+
const lines = [
|
|
166
|
+
"",
|
|
167
|
+
separator,
|
|
168
|
+
errors.length > 0
|
|
169
|
+
? "🚨 [cloudflare-next-intl] INCOMPLETE `firebaseAuth` CONFIG"
|
|
170
|
+
: "⚠️ [cloudflare-next-intl] INCOMPLETE `firebaseAuth` CONFIG",
|
|
171
|
+
separator,
|
|
172
|
+
"",
|
|
173
|
+
];
|
|
174
|
+
if (intlConfigPath)
|
|
175
|
+
lines.push("📄 Config: " + intlConfigPath, "");
|
|
176
|
+
lines.push("📋 ISSUES FOUND (" + issues.length + "):", thinSeparator);
|
|
177
|
+
issues.forEach((issue, index) => {
|
|
178
|
+
const icon = issue.severity === "error" ? "❌" : "⚠️ ";
|
|
179
|
+
lines.push(` [${index + 1}] ${icon} ${issue.field} — ${issue.reason}`);
|
|
180
|
+
if (issue.envVar) {
|
|
181
|
+
lines.push(` Set ${issue.envVar} in your .env / Cloudflare secrets.`);
|
|
182
|
+
}
|
|
183
|
+
if (issue.lineNumber) {
|
|
184
|
+
lines.push(` Around line ${issue.lineNumber}.`);
|
|
185
|
+
}
|
|
186
|
+
lines.push(thinSeparator);
|
|
187
|
+
});
|
|
188
|
+
lines.push("", "💡 WHAT BREAKS OTHERWISE:", " • Missing core fields (apiKey / authDomain / projectId / appId): the Firebase SDK", " rejects init, so every sign-in and every server-side session lookup fails.", " • Missing redirectAuthPath / homePath: the middleware's guest and auth-page", " redirects never match a pathname and silently do nothing.", " • Incomplete appCheck server credentials (clientEmail + appId, plus privateKey OR the", " full oauthClientId/oauthClientSecret/oauthRefreshToken triple): a signed-in user", " renders as signed-out on any cold navigation that beats the client's App Check cookie.", " Silence just this one with `appCheck.reportMissingServerCredentials: false`.", "", separator, "");
|
|
189
|
+
return lines.join("\n");
|
|
190
|
+
}
|
|
191
|
+
export function checkFirebaseAuthConfig(options = {}) {
|
|
192
|
+
const env = options.env ?? process.env;
|
|
193
|
+
let source = options.source;
|
|
194
|
+
if (source === undefined) {
|
|
195
|
+
const path = options.intlConfigPath;
|
|
196
|
+
if (!path || !existsSync(path)) {
|
|
197
|
+
return { valid: true, checked: false, issues: [], formattedMessage: "" };
|
|
198
|
+
}
|
|
199
|
+
try {
|
|
200
|
+
source = readFileSync(path, "utf8");
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
return { valid: true, checked: false, issues: [], formattedMessage: "" };
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
const firebaseAuth = extractObjectLiteral(source, "firebaseAuth");
|
|
207
|
+
if (!firebaseAuth) {
|
|
208
|
+
return { valid: true, checked: false, issues: [], formattedMessage: "" };
|
|
209
|
+
}
|
|
210
|
+
const baseLine = source.slice(0, firebaseAuth.start).split("\n").length;
|
|
211
|
+
const issues = [];
|
|
212
|
+
for (const key of REQUIRED_AUTH_FIELDS) {
|
|
213
|
+
const issue = checkField(firebaseAuth.body, "firebaseAuth", key, "error", env, baseLine);
|
|
214
|
+
if (issue)
|
|
215
|
+
issues.push(issue);
|
|
216
|
+
}
|
|
217
|
+
const appCheck = extractObjectLiteral(firebaseAuth.body, "appCheck");
|
|
218
|
+
if (appCheck) {
|
|
219
|
+
const appCheckLine = baseLine + firebaseAuth.body.slice(0, appCheck.start).split("\n").length - 1;
|
|
220
|
+
const optedOut = /reportMissingServerCredentials\s*:\s*false/.test(appCheck.body);
|
|
221
|
+
if (!optedOut) {
|
|
222
|
+
for (const key of REQUIRED_APP_CHECK_FIELDS) {
|
|
223
|
+
const issue = checkField(appCheck.body, "firebaseAuth.appCheck", key, "warning", env, appCheckLine);
|
|
224
|
+
if (issue)
|
|
225
|
+
issues.push(issue);
|
|
226
|
+
}
|
|
227
|
+
const privateKey = checkField(appCheck.body, "firebaseAuth.appCheck", "privateKey", "warning", env, appCheckLine);
|
|
228
|
+
const triple = OAUTH_TRIPLE.map((key) => checkField(appCheck.body, "firebaseAuth.appCheck", key, "warning", env, appCheckLine));
|
|
229
|
+
if (privateKey && triple.some((issue) => issue !== null)) {
|
|
230
|
+
const partialTriple = triple.some((issue) => issue === null);
|
|
231
|
+
if (partialTriple) {
|
|
232
|
+
issues.push(...triple.filter((issue) => issue !== null));
|
|
233
|
+
}
|
|
234
|
+
else {
|
|
235
|
+
issues.push({
|
|
236
|
+
...privateKey,
|
|
237
|
+
reason: privateKey.reason
|
|
238
|
+
+ " — set it, or the full oauthClientId/oauthClientSecret/oauthRefreshToken triple",
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
const valid = !issues.some((issue) => issue.severity === "error");
|
|
245
|
+
const formattedMessage = formatFirebaseAuthConfigMessage(issues, options.intlConfigPath);
|
|
246
|
+
if (!valid && options.throwOnError) {
|
|
247
|
+
throw new Error(formattedMessage);
|
|
248
|
+
}
|
|
249
|
+
return { valid, checked: true, issues, formattedMessage };
|
|
250
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { checkFirebaseAuthConfig, formatFirebaseAuthConfigMessage, extractObjectLiteral, extractFieldValue, type FirebaseAuthConfigIssue, type CheckFirebaseAuthConfigOptions, type CheckFirebaseAuthConfigReport, } from "./check_firebase_auth_config.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { checkFirebaseAuthConfig, formatFirebaseAuthConfigMessage, extractObjectLiteral, extractFieldValue, } from "./check_firebase_auth_config.js";
|
|
@@ -41,15 +41,36 @@ export default function HelperScript() {
|
|
|
41
41
|
// against the tree and every removeChild/insertBefore throws,
|
|
42
42
|
// which crashes it into the very error UI this is hiding.
|
|
43
43
|
var overlayId = 'cfni-stale-deploy-overlay';
|
|
44
|
+
var overlayStyleId = 'cfni-stale-deploy-style';
|
|
45
|
+
var overlayWanted = false;
|
|
44
46
|
function showOverlay() {
|
|
47
|
+
overlayWanted = true;
|
|
45
48
|
try {
|
|
46
|
-
|
|
47
|
-
|
|
49
|
+
// A chunk can fail while the parser is still inside
|
|
50
|
+
// <head>, before <body> or the error UI exist. A style
|
|
51
|
+
// rule can be installed right then and applies to
|
|
52
|
+
// whatever the parser produces next, so the error UI
|
|
53
|
+
// never gets a frame; the spinner is appended once
|
|
54
|
+
// there is a <body> to hold it.
|
|
55
|
+
if (!document.getElementById(overlayStyleId)) {
|
|
56
|
+
var st = document.createElement('style');
|
|
57
|
+
st.id = overlayStyleId;
|
|
58
|
+
st.textContent = 'html{background:#ffffff !important}body>*:not(#' + overlayId + '){visibility:hidden !important}';
|
|
59
|
+
(document.head || document.documentElement).appendChild(st);
|
|
60
|
+
}
|
|
61
|
+
if (document.getElementById(overlayId)) return;
|
|
62
|
+
if (!document.body) {
|
|
63
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
64
|
+
if (overlayWanted) showOverlay();
|
|
65
|
+
}, { once: true });
|
|
66
|
+
setTimeout(function() { if (overlayWanted) showOverlay(); }, 0);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
48
69
|
var el = document.createElement('div');
|
|
49
70
|
el.id = overlayId;
|
|
50
71
|
el.setAttribute('style', 'position:fixed;inset:0;z-index:2147483647;background:#ffffff;');
|
|
51
72
|
el.innerHTML = ${JSON.stringify(reloadHtml)};
|
|
52
|
-
|
|
73
|
+
document.body.appendChild(el);
|
|
53
74
|
} catch (e) {}
|
|
54
75
|
}
|
|
55
76
|
function doReload() {
|
|
@@ -141,6 +162,12 @@ export default function HelperScript() {
|
|
|
141
162
|
var sameOrigin = false;
|
|
142
163
|
try { sameOrigin = new URL(src, window.location.href).origin === window.location.origin; } catch (err2) { return; }
|
|
143
164
|
if (!sameOrigin) return;
|
|
165
|
+
// Cloudflare serves its bot-challenge scripts from the
|
|
166
|
+
// site's own origin, so they pass the same-origin test
|
|
167
|
+
// while having nothing to do with the build. Probing
|
|
168
|
+
// one never yields JavaScript and a failed challenge
|
|
169
|
+
// cannot break the module graph.
|
|
170
|
+
try { if (new URL(src, window.location.href).pathname.indexOf('/cdn-cgi/') === 0) return; } catch (err3) { return; }
|
|
144
171
|
if (!probeUrl) probeUrl = src;
|
|
145
172
|
recover('chunk resource failed to load: ' + src, 'resource-error');
|
|
146
173
|
} catch (err) {}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Plugin } from "vite";
|
|
2
|
+
import { type CheckFirebaseAuthConfigOptions } from "../firebase_auth_check/index.js";
|
|
3
|
+
export interface FirebaseAuthCheckPluginOptions extends Pick<CheckFirebaseAuthConfigOptions, "intlConfigPath" | "env"> {
|
|
4
|
+
strict?: boolean;
|
|
5
|
+
runOnDev?: boolean;
|
|
6
|
+
}
|
|
7
|
+
export declare function firebaseAuthCheckPlugin(options?: FirebaseAuthCheckPluginOptions): Plugin;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { checkFirebaseAuthConfig } from "../firebase_auth_check/index.js";
|
|
2
|
+
import { resolveDefaultIntlConfigPath } from "./locale_file_plugin.js";
|
|
3
|
+
export function firebaseAuthCheckPlugin(options = {}) {
|
|
4
|
+
let ran = false;
|
|
5
|
+
return {
|
|
6
|
+
name: "cloudflare-next-intl-firebase-auth-check",
|
|
7
|
+
enforce: "pre",
|
|
8
|
+
async configResolved(config) {
|
|
9
|
+
if (ran)
|
|
10
|
+
return;
|
|
11
|
+
const isBuild = config.command === "build";
|
|
12
|
+
const isDev = config.command === "serve";
|
|
13
|
+
if (!isBuild && !(isDev && (options.runOnDev ?? true)))
|
|
14
|
+
return;
|
|
15
|
+
ran = true;
|
|
16
|
+
const root = config.root || process.cwd();
|
|
17
|
+
const intlConfigPath = options.intlConfigPath ?? resolveDefaultIntlConfigPath(root);
|
|
18
|
+
let env = options.env;
|
|
19
|
+
if (!env) {
|
|
20
|
+
const { loadEnv } = await import("vite");
|
|
21
|
+
env = {
|
|
22
|
+
...loadEnv(config.mode, config.envDir || root, ""),
|
|
23
|
+
...process.env,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
const report = checkFirebaseAuthConfig({ intlConfigPath, env, throwOnError: false });
|
|
27
|
+
if (report.issues.length === 0)
|
|
28
|
+
return;
|
|
29
|
+
console.warn(report.formattedMessage);
|
|
30
|
+
if (!report.valid && options.strict) {
|
|
31
|
+
throw new Error("[cloudflare-next-intl] Build failed: `firebaseAuth` config is incomplete. See details above.");
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
}
|
package/dist/src/vite/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export { autoDynamicPagesPlugin, type AutoDynamicPagesPluginOptions } from "./auto_dynamic_pages_plugin.js";
|
|
2
2
|
export { autoLocaleParamsPlugin, type AutoLocaleParamsPluginOptions } from "./auto_locale_params_plugin.js";
|
|
3
3
|
export { layoutQueriesPlugin, type LayoutQueriesPluginOptions } from "./layout_queries_plugin.js";
|
|
4
|
+
export { firebaseAuthCheckPlugin, type FirebaseAuthCheckPluginOptions } from "./firebase_auth_check_plugin.js";
|
|
4
5
|
export { buildIdAsset } from "./build_id_asset.js";
|
|
5
6
|
export { userAgentStubPlugin, USER_AGENT_STUB_ID, USER_AGENT_STUB_CODE } from "./user_agent_stub.js";
|
|
6
7
|
export { cfWorkersClientStubPlugin, CF_WORKERS_CLIENT_STUB_ID, CF_WORKERS_CLIENT_STUB_CODE } from "./cf_workers_client_stub.js";
|
package/dist/src/vite/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export { autoDynamicPagesPlugin } from "./auto_dynamic_pages_plugin.js";
|
|
2
2
|
export { autoLocaleParamsPlugin } from "./auto_locale_params_plugin.js";
|
|
3
3
|
export { layoutQueriesPlugin } from "./layout_queries_plugin.js";
|
|
4
|
+
export { firebaseAuthCheckPlugin } from "./firebase_auth_check_plugin.js";
|
|
4
5
|
export { buildIdAsset } from "./build_id_asset.js";
|
|
5
6
|
export { userAgentStubPlugin, USER_AGENT_STUB_ID, USER_AGENT_STUB_CODE } from "./user_agent_stub.js";
|
|
6
7
|
export { cfWorkersClientStubPlugin, CF_WORKERS_CLIENT_STUB_ID, CF_WORKERS_CLIENT_STUB_CODE } from "./cf_workers_client_stub.js";
|
|
@@ -4,6 +4,7 @@ import { type ImageOptimizerPluginOptions } from "../image_optimizer/index.js";
|
|
|
4
4
|
import { type AutoDynamicPagesPluginOptions } from "./auto_dynamic_pages_plugin.js";
|
|
5
5
|
import { type AutoLocaleParamsPluginOptions } from "./auto_locale_params_plugin.js";
|
|
6
6
|
import { type LayoutQueriesPluginOptions } from "./layout_queries_plugin.js";
|
|
7
|
+
import { type FirebaseAuthCheckPluginOptions } from "./firebase_auth_check_plugin.js";
|
|
7
8
|
import { type VinextRouteWiringFixPluginOptions } from "./vinext_route_wiring_fix.js";
|
|
8
9
|
import { type LucideOptimizerPluginOptions } from "./lucide_optimizer_plugin.js";
|
|
9
10
|
export interface CloudflareNextIntlOptions extends LocaleFilePluginOptions {
|
|
@@ -17,6 +18,7 @@ export interface CloudflareNextIntlOptions extends LocaleFilePluginOptions {
|
|
|
17
18
|
vinextRouteWiringFix?: boolean | VinextRouteWiringFixPluginOptions;
|
|
18
19
|
autoLocaleParams?: boolean | AutoLocaleParamsPluginOptions;
|
|
19
20
|
layoutQueriesCheck?: boolean | LayoutQueriesPluginOptions;
|
|
21
|
+
firebaseAuthCheck?: boolean | FirebaseAuthCheckPluginOptions;
|
|
20
22
|
experimentalRouteLoadingFixes?: boolean;
|
|
21
23
|
}
|
|
22
24
|
export declare function cloudflareNextIntl(options?: CloudflareNextIntlOptions): Plugin[];
|
package/dist/src/vite/plugin.js
CHANGED
|
@@ -6,6 +6,7 @@ import { imageOptimizerPlugin } from "../image_optimizer/index.js";
|
|
|
6
6
|
import { autoDynamicPagesPlugin } from "./auto_dynamic_pages_plugin.js";
|
|
7
7
|
import { autoLocaleParamsPlugin } from "./auto_locale_params_plugin.js";
|
|
8
8
|
import { layoutQueriesPlugin } from "./layout_queries_plugin.js";
|
|
9
|
+
import { firebaseAuthCheckPlugin } from "./firebase_auth_check_plugin.js";
|
|
9
10
|
import { vinextRouteWiringFixPlugin } from "./vinext_route_wiring_fix.js";
|
|
10
11
|
import { lucideOptimizerPlugin } from "./lucide_optimizer_plugin.js";
|
|
11
12
|
export function cloudflareNextIntl(options = {}) {
|
|
@@ -15,6 +16,11 @@ export function cloudflareNextIntl(options = {}) {
|
|
|
15
16
|
? options.layoutQueriesCheck
|
|
16
17
|
: undefined));
|
|
17
18
|
}
|
|
19
|
+
if (options.firebaseAuthCheck !== false) {
|
|
20
|
+
plugins.push(firebaseAuthCheckPlugin(typeof options.firebaseAuthCheck === "object"
|
|
21
|
+
? options.firebaseAuthCheck
|
|
22
|
+
: { intlConfigPath: options.intlConfigPath }));
|
|
23
|
+
}
|
|
18
24
|
if (options.lucideOptimizer !== false) {
|
|
19
25
|
plugins.push(lucideOptimizerPlugin(typeof options.lucideOptimizer === "object"
|
|
20
26
|
? options.lucideOptimizer
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cloudflare-next-intl",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.60",
|
|
4
4
|
"description": "Optimized Next Intl Package Special for App Router and Cloudflare",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -248,6 +248,10 @@
|
|
|
248
248
|
"types": "./dist/src/layout_queries_check/index.d.ts",
|
|
249
249
|
"import": "./dist/src/layout_queries_check/index.js"
|
|
250
250
|
},
|
|
251
|
+
"./checkFirebaseAuthConfig": {
|
|
252
|
+
"types": "./dist/src/firebase_auth_check/index.d.ts",
|
|
253
|
+
"import": "./dist/src/firebase_auth_check/index.js"
|
|
254
|
+
},
|
|
251
255
|
"./db": {
|
|
252
256
|
"types": "./dist/src/db/index.d.ts",
|
|
253
257
|
"import": "./dist/src/db/index.js"
|