cloudflare-next-intl 0.10.3 → 0.10.5
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.
|
@@ -20,8 +20,8 @@ function parseCallArgs(sourceText, start) {
|
|
|
20
20
|
continue;
|
|
21
21
|
}
|
|
22
22
|
if (ch === '/' && sourceText[i + 1] === '/') {
|
|
23
|
-
const nextNewline = sourceText.indexOf('\n', i);
|
|
24
|
-
i = nextNewline === -1 ? sourceText.length : nextNewline;
|
|
23
|
+
const nextNewline = sourceText.indexOf('\n', i + 2);
|
|
24
|
+
i = nextNewline === -1 ? sourceText.length : nextNewline + 1;
|
|
25
25
|
continue;
|
|
26
26
|
}
|
|
27
27
|
if (ch === '/' && sourceText[i + 1] === '*') {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { dirname, resolve as resolvePath } from "node:path";
|
|
2
3
|
const REQUIRED_AUTH_FIELDS = [
|
|
3
4
|
"apiKey",
|
|
4
5
|
"authDomain",
|
|
@@ -9,16 +10,7 @@ const REQUIRED_AUTH_FIELDS = [
|
|
|
9
10
|
];
|
|
10
11
|
const REQUIRED_APP_CHECK_FIELDS = ["clientEmail", "appId"];
|
|
11
12
|
const OAUTH_TRIPLE = ["oauthClientId", "oauthClientSecret", "oauthRefreshToken"];
|
|
12
|
-
|
|
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;
|
|
13
|
+
function matchBraceLiteral(source, open) {
|
|
22
14
|
let depth = 0;
|
|
23
15
|
let index = open;
|
|
24
16
|
let quote = null;
|
|
@@ -66,8 +58,286 @@ export function extractObjectLiteral(source, key) {
|
|
|
66
58
|
}
|
|
67
59
|
return null;
|
|
68
60
|
}
|
|
61
|
+
function maskCommentsAndStrings(text) {
|
|
62
|
+
let result = "";
|
|
63
|
+
let quote = null;
|
|
64
|
+
let comment = null;
|
|
65
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
66
|
+
const char = text[index];
|
|
67
|
+
const next = text[index + 1];
|
|
68
|
+
if (comment === "line") {
|
|
69
|
+
result += char === "\n" ? "\n" : " ";
|
|
70
|
+
if (char === "\n")
|
|
71
|
+
comment = null;
|
|
72
|
+
}
|
|
73
|
+
else if (comment === "block") {
|
|
74
|
+
if (char === "*" && next === "/") {
|
|
75
|
+
result += " ";
|
|
76
|
+
comment = null;
|
|
77
|
+
index += 1;
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
result += char === "\n" ? "\n" : " ";
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
else if (quote) {
|
|
84
|
+
if (char === "\\") {
|
|
85
|
+
result += " ";
|
|
86
|
+
index += 1;
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
result += char === "\n" ? "\n" : " ";
|
|
90
|
+
if (char === quote)
|
|
91
|
+
quote = null;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
else if (char === "/" && next === "/") {
|
|
95
|
+
comment = "line";
|
|
96
|
+
result += " ";
|
|
97
|
+
index += 1;
|
|
98
|
+
}
|
|
99
|
+
else if (char === "/" && next === "*") {
|
|
100
|
+
comment = "block";
|
|
101
|
+
result += " ";
|
|
102
|
+
index += 1;
|
|
103
|
+
}
|
|
104
|
+
else if (char === '"' || char === "'" || char === "`") {
|
|
105
|
+
quote = char;
|
|
106
|
+
result += " ";
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
result += char;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return result;
|
|
113
|
+
}
|
|
114
|
+
export function extractObjectLiteral(source, key) {
|
|
115
|
+
const masked = maskCommentsAndStrings(source);
|
|
116
|
+
const keyMatch = new RegExp(`(^|[\\s{,])${key}\\s*:`).exec(masked);
|
|
117
|
+
if (!keyMatch)
|
|
118
|
+
return null;
|
|
119
|
+
const afterKey = keyMatch.index + keyMatch[0].length;
|
|
120
|
+
const open = masked.indexOf("{", afterKey);
|
|
121
|
+
if (open === -1)
|
|
122
|
+
return null;
|
|
123
|
+
if (/[,;]/.test(masked.slice(afterKey, open)))
|
|
124
|
+
return null;
|
|
125
|
+
return matchBraceLiteral(source, open);
|
|
126
|
+
}
|
|
127
|
+
function extractAssignedObjectLiteral(source, name) {
|
|
128
|
+
const masked = maskCommentsAndStrings(source);
|
|
129
|
+
const declMatch = new RegExp(`(?:^|[\\s;}])(?:export\\s+)?(?:const|let|var)\\s+${name}\\b[^=;]*=`).exec(masked);
|
|
130
|
+
if (!declMatch)
|
|
131
|
+
return null;
|
|
132
|
+
const afterEq = declMatch.index + declMatch[0].length;
|
|
133
|
+
const open = masked.indexOf("{", afterEq);
|
|
134
|
+
if (open === -1)
|
|
135
|
+
return null;
|
|
136
|
+
if (/[;\n]\S/.test(masked.slice(afterEq, open)))
|
|
137
|
+
return null;
|
|
138
|
+
return matchBraceLiteral(source, open);
|
|
139
|
+
}
|
|
140
|
+
function findTopLevelSpreadNames(body) {
|
|
141
|
+
const names = [];
|
|
142
|
+
let depth = 0;
|
|
143
|
+
let quote = null;
|
|
144
|
+
let comment = null;
|
|
145
|
+
for (let index = 0; index < body.length; index += 1) {
|
|
146
|
+
const char = body[index];
|
|
147
|
+
const next = body[index + 1];
|
|
148
|
+
if (comment === "line") {
|
|
149
|
+
if (char === "\n")
|
|
150
|
+
comment = null;
|
|
151
|
+
}
|
|
152
|
+
else if (comment === "block") {
|
|
153
|
+
if (char === "*" && next === "/") {
|
|
154
|
+
comment = null;
|
|
155
|
+
index += 1;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
else if (quote) {
|
|
159
|
+
if (char === "\\")
|
|
160
|
+
index += 1;
|
|
161
|
+
else if (char === quote)
|
|
162
|
+
quote = null;
|
|
163
|
+
}
|
|
164
|
+
else if (char === "/" && next === "/") {
|
|
165
|
+
comment = "line";
|
|
166
|
+
index += 1;
|
|
167
|
+
}
|
|
168
|
+
else if (char === "/" && next === "*") {
|
|
169
|
+
comment = "block";
|
|
170
|
+
index += 1;
|
|
171
|
+
}
|
|
172
|
+
else if (char === '"' || char === "'" || char === "`") {
|
|
173
|
+
quote = char;
|
|
174
|
+
}
|
|
175
|
+
else if (char === "{" || char === "[" || char === "(") {
|
|
176
|
+
depth += 1;
|
|
177
|
+
}
|
|
178
|
+
else if (char === "}" || char === "]" || char === ")") {
|
|
179
|
+
depth -= 1;
|
|
180
|
+
}
|
|
181
|
+
else if (depth === 0 && char === "." && next === "." && body[index + 2] === ".") {
|
|
182
|
+
const nameMatch = /^([A-Za-z_$][\w$]*)/.exec(body.slice(index + 3));
|
|
183
|
+
if (nameMatch)
|
|
184
|
+
names.push(nameMatch[1]);
|
|
185
|
+
index += 2;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return names;
|
|
189
|
+
}
|
|
190
|
+
const RESOLVE_EXTENSIONS = [".ts", ".tsx", ".mts", ".js", ".jsx", ".mjs"];
|
|
191
|
+
const tsconfigAliasCache = new Map();
|
|
192
|
+
function stripJsonComments(text) {
|
|
193
|
+
let stripped = "";
|
|
194
|
+
let inString = false;
|
|
195
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
196
|
+
const char = text[index];
|
|
197
|
+
const next = text[index + 1];
|
|
198
|
+
if (inString) {
|
|
199
|
+
stripped += char;
|
|
200
|
+
if (char === "\\") {
|
|
201
|
+
stripped += next ?? "";
|
|
202
|
+
index += 1;
|
|
203
|
+
}
|
|
204
|
+
else if (char === '"') {
|
|
205
|
+
inString = false;
|
|
206
|
+
}
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
if (char === '"') {
|
|
210
|
+
inString = true;
|
|
211
|
+
stripped += char;
|
|
212
|
+
}
|
|
213
|
+
else if (char === "/" && next === "/") {
|
|
214
|
+
while (index < text.length && text[index] !== "\n")
|
|
215
|
+
index += 1;
|
|
216
|
+
stripped += "\n";
|
|
217
|
+
}
|
|
218
|
+
else if (char === "/" && next === "*") {
|
|
219
|
+
index += 2;
|
|
220
|
+
while (index < text.length && !(text[index] === "*" && text[index + 1] === "/"))
|
|
221
|
+
index += 1;
|
|
222
|
+
index += 1;
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
stripped += char;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return stripped.replace(/,(\s*[}\]])/g, "$1");
|
|
229
|
+
}
|
|
230
|
+
function findTsconfigAliases(fromDir) {
|
|
231
|
+
if (tsconfigAliasCache.has(fromDir))
|
|
232
|
+
return tsconfigAliasCache.get(fromDir);
|
|
233
|
+
let dir = fromDir;
|
|
234
|
+
let result = null;
|
|
235
|
+
for (let depth = 0; depth < 12; depth += 1) {
|
|
236
|
+
const candidate = resolvePath(dir, "tsconfig.json");
|
|
237
|
+
if (existsSync(candidate)) {
|
|
238
|
+
try {
|
|
239
|
+
const parsed = JSON.parse(stripJsonComments(readFileSync(candidate, "utf8")));
|
|
240
|
+
const paths = parsed?.compilerOptions?.paths;
|
|
241
|
+
if (paths && typeof paths === "object") {
|
|
242
|
+
result = { baseDir: resolvePath(dir, parsed?.compilerOptions?.baseUrl ?? "."), paths };
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
catch {
|
|
246
|
+
result = null;
|
|
247
|
+
}
|
|
248
|
+
break;
|
|
249
|
+
}
|
|
250
|
+
const parent = dirname(dir);
|
|
251
|
+
if (parent === dir)
|
|
252
|
+
break;
|
|
253
|
+
dir = parent;
|
|
254
|
+
}
|
|
255
|
+
tsconfigAliasCache.set(fromDir, result);
|
|
256
|
+
return result;
|
|
257
|
+
}
|
|
258
|
+
function resolveAliasSpecifier(fromFile, specifier) {
|
|
259
|
+
const aliases = findTsconfigAliases(dirname(fromFile));
|
|
260
|
+
if (!aliases)
|
|
261
|
+
return null;
|
|
262
|
+
for (const [pattern, targets] of Object.entries(aliases.paths)) {
|
|
263
|
+
const target = targets[0];
|
|
264
|
+
if (!target)
|
|
265
|
+
continue;
|
|
266
|
+
if (pattern.endsWith("/*") && specifier.startsWith(pattern.slice(0, -1))) {
|
|
267
|
+
return resolvePath(aliases.baseDir, target.slice(0, -1) + specifier.slice(pattern.length - 1));
|
|
268
|
+
}
|
|
269
|
+
if (pattern === specifier) {
|
|
270
|
+
return resolvePath(aliases.baseDir, target);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
return null;
|
|
274
|
+
}
|
|
275
|
+
function resolveModuleFile(fromFile, specifier) {
|
|
276
|
+
const target = specifier.startsWith(".")
|
|
277
|
+
? resolvePath(dirname(fromFile), specifier)
|
|
278
|
+
: resolveAliasSpecifier(fromFile, specifier);
|
|
279
|
+
if (!target)
|
|
280
|
+
return null;
|
|
281
|
+
for (const candidate of [target, ...RESOLVE_EXTENSIONS.map((ext) => target + ext)]) {
|
|
282
|
+
if (existsSync(candidate))
|
|
283
|
+
return candidate;
|
|
284
|
+
}
|
|
285
|
+
return null;
|
|
286
|
+
}
|
|
287
|
+
function findNamedImportSpecifier(source, localName) {
|
|
288
|
+
const importRegex = /import\s*\{([^}]*)\}\s*from\s*["']([^"']+)["']/g;
|
|
289
|
+
let match;
|
|
290
|
+
while ((match = importRegex.exec(source)) !== null) {
|
|
291
|
+
for (const raw of match[1].split(",")) {
|
|
292
|
+
const spec = raw.trim();
|
|
293
|
+
if (!spec)
|
|
294
|
+
continue;
|
|
295
|
+
const asMatch = /^([A-Za-z_$][\w$]*)\s+as\s+([A-Za-z_$][\w$]*)$/.exec(spec);
|
|
296
|
+
const imported = asMatch ? asMatch[1] : spec;
|
|
297
|
+
const local = asMatch ? asMatch[2] : spec;
|
|
298
|
+
if (local === localName)
|
|
299
|
+
return { imported, from: match[2] };
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
return null;
|
|
303
|
+
}
|
|
304
|
+
function resolveSpreadBody(name, source, fromFile, cache) {
|
|
305
|
+
const cacheKey = `${fromFile ?? ""}::${name}`;
|
|
306
|
+
if (cache.has(cacheKey))
|
|
307
|
+
return cache.get(cacheKey);
|
|
308
|
+
let result = extractAssignedObjectLiteral(source, name)?.body ?? null;
|
|
309
|
+
if (result === null && fromFile) {
|
|
310
|
+
const namedImport = findNamedImportSpecifier(source, name);
|
|
311
|
+
if (namedImport) {
|
|
312
|
+
const file = resolveModuleFile(fromFile, namedImport.from);
|
|
313
|
+
if (file) {
|
|
314
|
+
try {
|
|
315
|
+
const fileSource = readFileSync(file, "utf8");
|
|
316
|
+
result = extractAssignedObjectLiteral(fileSource, namedImport.imported)?.body ?? null;
|
|
317
|
+
}
|
|
318
|
+
catch {
|
|
319
|
+
result = null;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
cache.set(cacheKey, result);
|
|
325
|
+
return result;
|
|
326
|
+
}
|
|
327
|
+
function resolveSpreadBodies(mainBody, source, fromFile, cache) {
|
|
328
|
+
const bodies = [mainBody];
|
|
329
|
+
let hasUnresolvedSpread = false;
|
|
330
|
+
for (const name of findTopLevelSpreadNames(mainBody)) {
|
|
331
|
+
const resolved = resolveSpreadBody(name, source, fromFile, cache);
|
|
332
|
+
if (resolved !== null)
|
|
333
|
+
bodies.push(resolved);
|
|
334
|
+
else
|
|
335
|
+
hasUnresolvedSpread = true;
|
|
336
|
+
}
|
|
337
|
+
return { bodies, hasUnresolvedSpread };
|
|
338
|
+
}
|
|
69
339
|
export function extractFieldValue(body, key) {
|
|
70
|
-
const keyMatch = new RegExp(`(^|[\\s{,])${key}\\s*:`).exec(body);
|
|
340
|
+
const keyMatch = new RegExp(`(^|[\\s{,])${key}\\s*:`).exec(maskCommentsAndStrings(body));
|
|
71
341
|
if (!keyMatch)
|
|
72
342
|
return null;
|
|
73
343
|
const start = keyMatch.index + keyMatch[0].length;
|
|
@@ -139,10 +409,20 @@ function evaluateValue(value, env) {
|
|
|
139
409
|
}
|
|
140
410
|
return { ok: true };
|
|
141
411
|
}
|
|
142
|
-
function checkField(
|
|
412
|
+
function checkField(bodies, hasUnresolvedSpread, path, key, severity, env, baseLine) {
|
|
143
413
|
const field = `${path}.${key}`;
|
|
144
|
-
|
|
414
|
+
let found = null;
|
|
415
|
+
let fromMainBody = false;
|
|
416
|
+
for (let index = 0; index < bodies.length; index += 1) {
|
|
417
|
+
found = extractFieldValue(bodies[index], key);
|
|
418
|
+
if (found) {
|
|
419
|
+
fromMainBody = index === 0;
|
|
420
|
+
break;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
145
423
|
if (!found) {
|
|
424
|
+
if (hasUnresolvedSpread)
|
|
425
|
+
return null;
|
|
146
426
|
return { field, severity, reason: "missing from the config" };
|
|
147
427
|
}
|
|
148
428
|
const verdict = evaluateValue(found.value, env);
|
|
@@ -153,7 +433,7 @@ function checkField(body, path, key, severity, env, baseLine) {
|
|
|
153
433
|
severity,
|
|
154
434
|
reason: verdict.reason,
|
|
155
435
|
...(verdict.envVar ? { envVar: verdict.envVar } : {}),
|
|
156
|
-
lineNumber: baseLine + found.lineNumber - 1,
|
|
436
|
+
...(fromMainBody ? { lineNumber: baseLine + found.lineNumber - 1 } : {}),
|
|
157
437
|
};
|
|
158
438
|
}
|
|
159
439
|
export function formatFirebaseAuthConfigMessage(issues, intlConfigPath) {
|
|
@@ -209,23 +489,26 @@ export function checkFirebaseAuthConfig(options = {}) {
|
|
|
209
489
|
}
|
|
210
490
|
const baseLine = source.slice(0, firebaseAuth.start).split("\n").length;
|
|
211
491
|
const issues = [];
|
|
492
|
+
const resolveCache = new Map();
|
|
493
|
+
const { bodies: authBodies, hasUnresolvedSpread: authHasUnresolvedSpread } = resolveSpreadBodies(firebaseAuth.body, source, options.intlConfigPath, resolveCache);
|
|
212
494
|
for (const key of REQUIRED_AUTH_FIELDS) {
|
|
213
|
-
const issue = checkField(
|
|
495
|
+
const issue = checkField(authBodies, authHasUnresolvedSpread, "firebaseAuth", key, "error", env, baseLine);
|
|
214
496
|
if (issue)
|
|
215
497
|
issues.push(issue);
|
|
216
498
|
}
|
|
217
499
|
const appCheck = extractObjectLiteral(firebaseAuth.body, "appCheck");
|
|
218
500
|
if (appCheck) {
|
|
219
501
|
const appCheckLine = baseLine + firebaseAuth.body.slice(0, appCheck.start).split("\n").length - 1;
|
|
220
|
-
const optedOut = /reportMissingServerCredentials\s*:\s*false/.test(appCheck.body);
|
|
502
|
+
const optedOut = /reportMissingServerCredentials\s*:\s*false/.test(maskCommentsAndStrings(appCheck.body));
|
|
503
|
+
const { bodies: appCheckBodies, hasUnresolvedSpread: appCheckHasUnresolvedSpread } = resolveSpreadBodies(appCheck.body, source, options.intlConfigPath, resolveCache);
|
|
221
504
|
if (!optedOut) {
|
|
222
505
|
for (const key of REQUIRED_APP_CHECK_FIELDS) {
|
|
223
|
-
const issue = checkField(
|
|
506
|
+
const issue = checkField(appCheckBodies, appCheckHasUnresolvedSpread, "firebaseAuth.appCheck", key, "warning", env, appCheckLine);
|
|
224
507
|
if (issue)
|
|
225
508
|
issues.push(issue);
|
|
226
509
|
}
|
|
227
|
-
const privateKey = checkField(
|
|
228
|
-
const triple = OAUTH_TRIPLE.map((key) => checkField(
|
|
510
|
+
const privateKey = checkField(appCheckBodies, appCheckHasUnresolvedSpread, "firebaseAuth.appCheck", "privateKey", "warning", env, appCheckLine);
|
|
511
|
+
const triple = OAUTH_TRIPLE.map((key) => checkField(appCheckBodies, appCheckHasUnresolvedSpread, "firebaseAuth.appCheck", key, "warning", env, appCheckLine));
|
|
229
512
|
if (privateKey && triple.some((issue) => issue !== null)) {
|
|
230
513
|
const partialTriple = triple.some((issue) => issue === null);
|
|
231
514
|
if (partialTriple) {
|
|
@@ -27,7 +27,7 @@ export function firebaseAuthCheckPlugin(options = {}) {
|
|
|
27
27
|
if (report.issues.length === 0)
|
|
28
28
|
return;
|
|
29
29
|
console.warn(report.formattedMessage);
|
|
30
|
-
if (!report.valid && options.strict) {
|
|
30
|
+
if (!report.valid && options.strict !== false) {
|
|
31
31
|
throw new Error("[cloudflare-next-intl] Build failed: `firebaseAuth` config is incomplete. See details above.");
|
|
32
32
|
}
|
|
33
33
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cloudflare-next-intl",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.5",
|
|
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",
|
|
@@ -358,7 +358,7 @@
|
|
|
358
358
|
"homepage": "https://github.com/demian-ilnytskyi/cloudflare-next-intl#readme",
|
|
359
359
|
"dependencies": {
|
|
360
360
|
"@microsoft/clarity": "^1.0.2",
|
|
361
|
-
"cloudflare-next-intl-db": "^0.2.
|
|
361
|
+
"cloudflare-next-intl-db": "^0.2.3",
|
|
362
362
|
"cloudflare-next-intl-db-codegen": "^0.1.1",
|
|
363
363
|
"jose": "^6.2.8",
|
|
364
364
|
"sharp": "^0.34.5 || ^0.35.0"
|