@edgeone/opennextjs-pages 0.1.5 → 0.1.6-beta.1
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/functions/middleware/compiler.js +65 -18
- package/dist/build/functions/middleware/middleware.js +12 -7
- package/dist/build/functions/middleware/polyfills/index.js +24 -0
- package/dist/build/functions/middleware/wrapper.js +51 -32
- package/dist/build/plugin-context.js +44 -13
- package/dist/build/route-utils.js +698 -0
- package/dist/build/routes.js +203 -100
- package/dist/run/handlers/tags-handler.cjs +1 -1
- package/package.json +1 -1
|
@@ -2455,11 +2455,14 @@ async function compileTurbopack(inputPath, middlewareCode, options = {}) {
|
|
|
2455
2455
|
}
|
|
2456
2456
|
const externalChunks = [];
|
|
2457
2457
|
const middlewareChunks = [];
|
|
2458
|
+
const libraryChunks = [];
|
|
2458
2459
|
for (const chunkPath of chunkPaths) {
|
|
2459
2460
|
if (chunkPath.includes("[externals]")) {
|
|
2460
2461
|
externalChunks.push(chunkPath);
|
|
2461
2462
|
} else if (chunkPath.includes("[root-of-the-server]")) {
|
|
2462
2463
|
middlewareChunks.push(chunkPath);
|
|
2464
|
+
} else {
|
|
2465
|
+
libraryChunks.push(chunkPath);
|
|
2463
2466
|
}
|
|
2464
2467
|
}
|
|
2465
2468
|
const chunksCode = [];
|
|
@@ -2474,6 +2477,17 @@ async function compileTurbopack(inputPath, middlewareCode, options = {}) {
|
|
|
2474
2477
|
warnings.push(`External chunk not found: ${fullPath}`);
|
|
2475
2478
|
}
|
|
2476
2479
|
}
|
|
2480
|
+
for (const chunkPath of libraryChunks) {
|
|
2481
|
+
const relativePath = chunkPath.replace(/^server\//, "");
|
|
2482
|
+
const fullPath = join(middlewareDir, relativePath);
|
|
2483
|
+
if (existsSync(fullPath)) {
|
|
2484
|
+
let code = readFileSync(fullPath, "utf-8");
|
|
2485
|
+
code = transformForEdgeOneRuntime(code);
|
|
2486
|
+
chunksCode.push(code);
|
|
2487
|
+
} else {
|
|
2488
|
+
warnings.push(`Library chunk not found: ${fullPath}`);
|
|
2489
|
+
}
|
|
2490
|
+
}
|
|
2477
2491
|
for (const chunkPath of middlewareChunks) {
|
|
2478
2492
|
const relativePath = chunkPath.replace(/^server\//, "");
|
|
2479
2493
|
const fullPath = join(middlewareDir, relativePath);
|
|
@@ -3525,28 +3539,61 @@ function matchesPath(pathname, matcher) {
|
|
|
3525
3539
|
continue;
|
|
3526
3540
|
}
|
|
3527
3541
|
} else {
|
|
3528
|
-
// \u8DEF\u5F84\u6A21\u5F0F\uFF0C\u9700\u8981\u8F6C\u6362
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
regexPattern =
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
|
|
3542
|
+
// \u8DEF\u5F84\u6A21\u5F0F\uFF0C\u9700\u8981\u8F6C\u6362\u4E3A\u6B63\u5219\u8868\u8FBE\u5F0F
|
|
3543
|
+
// Next.js middleware matcher \u4F7F\u7528\u7C7B\u4F3C path-to-regexp \u7684\u8BED\u6CD5:
|
|
3544
|
+
// /dashboard/:path* -> \u5339\u914D /dashboard \u548C /dashboard/xxx\uFF08\u96F6\u4E2A\u6216\u591A\u4E2A\u8DEF\u5F84\u6BB5\uFF09
|
|
3545
|
+
// /api/:path -> \u5339\u914D /api/xxx\uFF08\u4E00\u4E2A\u8DEF\u5F84\u6BB5\uFF09
|
|
3546
|
+
// /api/:path+ -> \u5339\u914D /api/xxx/yyy\uFF08\u4E00\u4E2A\u6216\u591A\u4E2A\u8DEF\u5F84\u6BB5\uFF09
|
|
3547
|
+
let regexPattern = '';
|
|
3548
|
+
let ci = 0;
|
|
3549
|
+
const p = pattern;
|
|
3550
|
+
while (ci < p.length) {
|
|
3551
|
+
if (p[ci] === ':') {
|
|
3552
|
+
// \u89E3\u6790\u53C2\u6570\u540D
|
|
3553
|
+
ci++;
|
|
3554
|
+
while (ci < p.length && /[a-zA-Z0-9_]/.test(p[ci])) {
|
|
3555
|
+
ci++;
|
|
3556
|
+
}
|
|
3557
|
+
// \u68C0\u67E5\u4FEE\u9970\u7B26
|
|
3558
|
+
if (ci < p.length && p[ci] === '*') {
|
|
3559
|
+
// :param* -> \u96F6\u4E2A\u6216\u591A\u4E2A\u8DEF\u5F84\u6BB5\uFF0C\u524D\u9762\u7684 / \u4E5F\u53D8\u4E3A\u53EF\u9009
|
|
3560
|
+
if (regexPattern.endsWith('\\\\/')) {
|
|
3561
|
+
regexPattern = regexPattern.slice(0, -2);
|
|
3562
|
+
}
|
|
3563
|
+
regexPattern += '(?:\\\\/.*)?';
|
|
3564
|
+
ci++;
|
|
3565
|
+
} else if (ci < p.length && p[ci] === '+') {
|
|
3566
|
+
// :param+ -> \u4E00\u4E2A\u6216\u591A\u4E2A\u8DEF\u5F84\u6BB5
|
|
3567
|
+
regexPattern += '.+';
|
|
3568
|
+
ci++;
|
|
3569
|
+
} else {
|
|
3570
|
+
// :param -> \u5339\u914D\u4E00\u4E2A\u8DEF\u5F84\u6BB5
|
|
3571
|
+
regexPattern += '[^/]+';
|
|
3572
|
+
}
|
|
3573
|
+
} else if (p[ci] === '/') {
|
|
3574
|
+
regexPattern += '\\\\/';
|
|
3575
|
+
ci++;
|
|
3576
|
+
} else if (p[ci] === '*') {
|
|
3577
|
+
if (ci + 1 < p.length && p[ci + 1] === '*') {
|
|
3578
|
+
regexPattern += '.*';
|
|
3579
|
+
ci += 2;
|
|
3580
|
+
} else {
|
|
3581
|
+
regexPattern += '[^/]*';
|
|
3582
|
+
ci++;
|
|
3583
|
+
}
|
|
3542
3584
|
} else {
|
|
3543
|
-
|
|
3585
|
+
// \u8F6C\u4E49\u6B63\u5219\u7279\u6B8A\u5B57\u7B26
|
|
3586
|
+
const ch = p[ci];
|
|
3587
|
+
const specialChars = '.+?^$|(){}[]\\\\';
|
|
3588
|
+
if (specialChars.indexOf(ch) !== -1) {
|
|
3589
|
+
regexPattern += '\\\\' + ch;
|
|
3590
|
+
} else {
|
|
3591
|
+
regexPattern += ch;
|
|
3592
|
+
}
|
|
3593
|
+
ci++;
|
|
3544
3594
|
}
|
|
3545
3595
|
}
|
|
3546
3596
|
|
|
3547
|
-
regexPattern = regexPattern.split('**').join('.*');
|
|
3548
|
-
regexPattern = regexPattern.split('\\\\*').join('[^/]*');
|
|
3549
|
-
|
|
3550
3597
|
regex = new RegExp('^' + regexPattern + '$');
|
|
3551
3598
|
}
|
|
3552
3599
|
|
|
@@ -11,16 +11,20 @@ import { compile } from "./compiler.js";
|
|
|
11
11
|
import { join } from "node:path";
|
|
12
12
|
import { existsSync, writeFileSync, mkdirSync, readFileSync } from "node:fs";
|
|
13
13
|
var compileMiddleware = async (ctx) => {
|
|
14
|
+
const nextDistDir = ctx.nextDistDir || ".next";
|
|
14
15
|
const possiblePaths = [
|
|
15
|
-
// 1. Next.js 15+: distDir/server/middleware.js (直接在
|
|
16
|
+
// 1. Next.js 15+: distDir/server/middleware.js (直接在 distDir 目录下)
|
|
16
17
|
join(ctx.distDir, "server/middleware.js"),
|
|
17
|
-
// 2. Next.js 15+: standaloneDir
|
|
18
|
-
join(ctx.standaloneDir, "
|
|
19
|
-
// 3. 旧版: standaloneDir
|
|
20
|
-
join(ctx.standaloneDir, "
|
|
18
|
+
// 2. Next.js 15+: standaloneDir/{nextDistDir}/server/middleware.js
|
|
19
|
+
join(ctx.standaloneDir, nextDistDir, "server/middleware.js"),
|
|
20
|
+
// 3. 旧版: standaloneDir/{nextDistDir}/server/src/middleware.js
|
|
21
|
+
join(ctx.standaloneDir, nextDistDir, "server/src/middleware.js"),
|
|
21
22
|
// 4. 旧版: distDir/server/src/middleware.js
|
|
22
23
|
join(ctx.distDir, "server/src/middleware.js"),
|
|
23
|
-
// 5.
|
|
24
|
+
// 5. 相对于当前工作目录(使用配置的 distDir)
|
|
25
|
+
join(process.cwd(), nextDistDir, "server/middleware.js"),
|
|
26
|
+
join(process.cwd(), nextDistDir, "server/src/middleware.js"),
|
|
27
|
+
// 6. 兼容默认 .next 目录(以防 nextDistDir 获取失败)
|
|
24
28
|
join(process.cwd(), ".next/server/middleware.js"),
|
|
25
29
|
join(process.cwd(), ".next/server/src/middleware.js")
|
|
26
30
|
];
|
|
@@ -41,7 +45,8 @@ var compileMiddleware = async (ctx) => {
|
|
|
41
45
|
mkdirSync(outputDir, { recursive: true });
|
|
42
46
|
const outputPath = join(outputDir, "compiled-middleware.js");
|
|
43
47
|
writeFileSync(outputPath, result.code || "", "utf-8");
|
|
44
|
-
const
|
|
48
|
+
const edgeoneDir = join(process.cwd(), ".edgeone");
|
|
49
|
+
const edgeFunctionPath = join(edgeoneDir, "edge-functions/index.js");
|
|
45
50
|
if (existsSync(edgeFunctionPath)) {
|
|
46
51
|
let edgeFunctionCode = readFileSync(edgeFunctionPath, "utf-8");
|
|
47
52
|
if (edgeFunctionCode.includes(`'__MIDDLEWARE_BUNDLE_CODE__'`)) {
|
|
@@ -73,6 +73,22 @@ var responsePolyfill = `
|
|
|
73
73
|
return originalRedirect.call(OriginalResponse, urlString, status);
|
|
74
74
|
};
|
|
75
75
|
|
|
76
|
+
// Response.json \u9759\u6001\u65B9\u6CD5 polyfill
|
|
77
|
+
// \u67D0\u4E9B Edge \u8FD0\u884C\u65F6\u4E0D\u652F\u6301\u8FD9\u4E2A\u8F83\u65B0\u7684 Web API
|
|
78
|
+
const patchedJson = function(data, init) {
|
|
79
|
+
const body = JSON.stringify(data);
|
|
80
|
+
const responseInit = cleanResponseInit(init) || {};
|
|
81
|
+
|
|
82
|
+
// \u8BBE\u7F6E Content-Type header
|
|
83
|
+
const headers = new Headers(responseInit.headers || {});
|
|
84
|
+
if (!headers.has('Content-Type')) {
|
|
85
|
+
headers.set('Content-Type', 'application/json');
|
|
86
|
+
}
|
|
87
|
+
responseInit.headers = headers;
|
|
88
|
+
|
|
89
|
+
return new OriginalResponse(body, responseInit);
|
|
90
|
+
};
|
|
91
|
+
|
|
76
92
|
// \u521B\u5EFA cookies \u5BF9\u8C61\u7684\u5DE5\u5382\u51FD\u6570
|
|
77
93
|
function createResponseCookies(response) {
|
|
78
94
|
const cookieStore = new Map();
|
|
@@ -137,6 +153,14 @@ var responsePolyfill = `
|
|
|
137
153
|
if (prop === 'redirect') {
|
|
138
154
|
return patchedRedirect;
|
|
139
155
|
}
|
|
156
|
+
// \u62E6\u622A json \u9759\u6001\u65B9\u6CD5\uFF08polyfill for Edge runtime\uFF09
|
|
157
|
+
if (prop === 'json') {
|
|
158
|
+
// \u5982\u679C\u539F\u751F\u652F\u6301\u5C31\u7528\u539F\u751F\u7684\uFF0C\u5426\u5219\u7528 polyfill
|
|
159
|
+
if (typeof OriginalResponse.json === 'function') {
|
|
160
|
+
return OriginalResponse.json.bind(OriginalResponse);
|
|
161
|
+
}
|
|
162
|
+
return patchedJson;
|
|
163
|
+
}
|
|
140
164
|
// \u5176\u4ED6\u9759\u6001\u65B9\u6CD5\u76F4\u63A5\u4ECE\u539F\u59CB Response \u83B7\u53D6
|
|
141
165
|
return Reflect.get(target, prop, receiver);
|
|
142
166
|
}
|
|
@@ -258,42 +258,61 @@ function matchesPath(pathname, matcher) {
|
|
|
258
258
|
continue;
|
|
259
259
|
}
|
|
260
260
|
} else {
|
|
261
|
-
// \u8DEF\u5F84\u6A21\u5F0F\uFF0C\u9700\u8981\u8F6C\u6362
|
|
262
|
-
//
|
|
263
|
-
//
|
|
264
|
-
// \
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
261
|
+
// \u8DEF\u5F84\u6A21\u5F0F\uFF0C\u9700\u8981\u8F6C\u6362\u4E3A\u6B63\u5219\u8868\u8FBE\u5F0F
|
|
262
|
+
// Next.js middleware matcher \u4F7F\u7528\u7C7B\u4F3C path-to-regexp \u7684\u8BED\u6CD5:
|
|
263
|
+
// /dashboard/:path* -> \u5339\u914D /dashboard \u548C /dashboard/xxx\uFF08\u96F6\u4E2A\u6216\u591A\u4E2A\u8DEF\u5F84\u6BB5\uFF09
|
|
264
|
+
// /api/:path -> \u5339\u914D /api/xxx\uFF08\u4E00\u4E2A\u8DEF\u5F84\u6BB5\uFF09
|
|
265
|
+
// /api/:path+ -> \u5339\u914D /api/xxx/yyy\uFF08\u4E00\u4E2A\u6216\u591A\u4E2A\u8DEF\u5F84\u6BB5\uFF09
|
|
266
|
+
let regexPattern = '';
|
|
267
|
+
let ci = 0;
|
|
268
|
+
const p = pattern;
|
|
269
|
+
while (ci < p.length) {
|
|
270
|
+
if (p[ci] === ':') {
|
|
271
|
+
// \u89E3\u6790\u53C2\u6570\u540D
|
|
272
|
+
ci++;
|
|
273
|
+
while (ci < p.length && /[a-zA-Z0-9_]/.test(p[ci])) {
|
|
274
|
+
ci++;
|
|
275
|
+
}
|
|
276
|
+
// \u68C0\u67E5\u4FEE\u9970\u7B26
|
|
277
|
+
if (ci < p.length && p[ci] === '*') {
|
|
278
|
+
// :param* -> \u96F6\u4E2A\u6216\u591A\u4E2A\u8DEF\u5F84\u6BB5\uFF0C\u524D\u9762\u7684 / \u4E5F\u53D8\u4E3A\u53EF\u9009
|
|
279
|
+
if (regexPattern.endsWith('\\/')) {
|
|
280
|
+
regexPattern = regexPattern.slice(0, -2);
|
|
281
|
+
}
|
|
282
|
+
regexPattern += '(?:\\/.*)?';
|
|
283
|
+
ci++;
|
|
284
|
+
} else if (ci < p.length && p[ci] === '+') {
|
|
285
|
+
// :param+ -> \u4E00\u4E2A\u6216\u591A\u4E2A\u8DEF\u5F84\u6BB5
|
|
286
|
+
regexPattern += '.+';
|
|
287
|
+
ci++;
|
|
288
|
+
} else {
|
|
289
|
+
// :param -> \u5339\u914D\u4E00\u4E2A\u8DEF\u5F84\u6BB5
|
|
290
|
+
regexPattern += '[^/]+';
|
|
291
|
+
}
|
|
292
|
+
} else if (p[ci] === '/') {
|
|
293
|
+
regexPattern += '\\/';
|
|
294
|
+
ci++;
|
|
295
|
+
} else if (p[ci] === '*') {
|
|
296
|
+
if (ci + 1 < p.length && p[ci + 1] === '*') {
|
|
297
|
+
regexPattern += '.*';
|
|
298
|
+
ci += 2;
|
|
299
|
+
} else {
|
|
300
|
+
regexPattern += '[^/]*';
|
|
301
|
+
ci++;
|
|
302
|
+
}
|
|
285
303
|
} else {
|
|
286
|
-
//
|
|
287
|
-
|
|
304
|
+
// \u8F6C\u4E49\u6B63\u5219\u7279\u6B8A\u5B57\u7B26
|
|
305
|
+
const ch = p[ci];
|
|
306
|
+
const specialChars = '.+?^$|(){}[]\\\\';
|
|
307
|
+
if (specialChars.indexOf(ch) !== -1) {
|
|
308
|
+
regexPattern += '\\' + ch;
|
|
309
|
+
} else {
|
|
310
|
+
regexPattern += ch;
|
|
311
|
+
}
|
|
312
|
+
ci++;
|
|
288
313
|
}
|
|
289
314
|
}
|
|
290
315
|
|
|
291
|
-
// ** -> .*
|
|
292
|
-
regexPattern = regexPattern.split('**').join('.*');
|
|
293
|
-
// * -> [^/]* (\u4F46\u4E0D\u5F71\u54CD\u5DF2\u7ECF\u8F6C\u6362\u7684 .*)
|
|
294
|
-
// \u7B80\u5355\u5904\u7406\uFF1A\u53EA\u66FF\u6362\u72EC\u7ACB\u7684 *
|
|
295
|
-
regexPattern = regexPattern.split('\\*').join('[^/]*');
|
|
296
|
-
|
|
297
316
|
regex = new RegExp('^' + regexPattern + '$');
|
|
298
317
|
}
|
|
299
318
|
|
|
@@ -22,7 +22,7 @@ import { fileURLToPath } from "node:url";
|
|
|
22
22
|
var MODULE_DIR = fileURLToPath(new URL(".", import.meta.url));
|
|
23
23
|
var PLUGIN_DIR = join(MODULE_DIR, "../..");
|
|
24
24
|
var DEFAULT_PUBLISH_DIR = ".next";
|
|
25
|
-
var SERVER_HANDLER_NAME = "
|
|
25
|
+
var SERVER_HANDLER_NAME = "cloud-functions/ssr-node";
|
|
26
26
|
var EDGE_HANDLER_NAME = "edgeone-edge-handler";
|
|
27
27
|
var PluginContext = class {
|
|
28
28
|
edgeoneConfig;
|
|
@@ -40,7 +40,7 @@ var PluginContext = class {
|
|
|
40
40
|
}
|
|
41
41
|
/** Temporary directory for stashing the build output */
|
|
42
42
|
get tempPublishDir() {
|
|
43
|
-
return this.resolveFromPackagePath(".edgeone
|
|
43
|
+
return this.resolveFromPackagePath(join(".edgeone", this.nextDistDir));
|
|
44
44
|
}
|
|
45
45
|
/** Absolute path of the publish directory */
|
|
46
46
|
get publishDir() {
|
|
@@ -152,13 +152,15 @@ var PluginContext = class {
|
|
|
152
152
|
return join(this.edgeFunctionsDir, EDGE_HANDLER_NAME);
|
|
153
153
|
}
|
|
154
154
|
constructor(options) {
|
|
155
|
+
const detectedPublishDir = this.detectPublishDir(options?.constants?.PACKAGE_PATH);
|
|
155
156
|
options = {
|
|
156
157
|
...options,
|
|
157
158
|
functions: {
|
|
158
159
|
"*": {}
|
|
159
160
|
},
|
|
160
161
|
constants: {
|
|
161
|
-
|
|
162
|
+
...options?.constants,
|
|
163
|
+
PUBLISH_DIR: detectedPublishDir
|
|
162
164
|
// BUILD_VERSION: '32.1.4',
|
|
163
165
|
}
|
|
164
166
|
};
|
|
@@ -171,6 +173,32 @@ var PluginContext = class {
|
|
|
171
173
|
this.serverlessWrapHandler = options.serverlessWrapHandler;
|
|
172
174
|
this.getRuntimeShim = options.getRuntimeShim;
|
|
173
175
|
}
|
|
176
|
+
/**
|
|
177
|
+
* 自动检测 Next.js 构建输出目录
|
|
178
|
+
* 搜索包含 BUILD_ID 文件的目录,支持自定义 distDir 配置
|
|
179
|
+
*/
|
|
180
|
+
detectPublishDir(packagePath) {
|
|
181
|
+
const basePath = packagePath || process.cwd();
|
|
182
|
+
const possibleDirs = [
|
|
183
|
+
".next",
|
|
184
|
+
// 默认
|
|
185
|
+
"build",
|
|
186
|
+
// 常见自定义
|
|
187
|
+
"dist",
|
|
188
|
+
// 常见自定义
|
|
189
|
+
"out",
|
|
190
|
+
// 常见自定义
|
|
191
|
+
".build"
|
|
192
|
+
// 其他可能
|
|
193
|
+
];
|
|
194
|
+
for (const dir of possibleDirs) {
|
|
195
|
+
const fullPath = resolve(basePath, dir);
|
|
196
|
+
if (existsSync(join(fullPath, "BUILD_ID"))) {
|
|
197
|
+
return dir;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return DEFAULT_PUBLISH_DIR;
|
|
201
|
+
}
|
|
174
202
|
/** Resolves a path correctly with mono repository awareness for .edgeone directories mainly */
|
|
175
203
|
resolveFromPackagePath(...args) {
|
|
176
204
|
return resolve(this.constants.PACKAGE_PATH || "", ...args);
|
|
@@ -201,18 +229,21 @@ var PluginContext = class {
|
|
|
201
229
|
/**
|
|
202
230
|
* Uses various heuristics to try to find the .next dir.
|
|
203
231
|
* Works by looking for BUILD_ID, so requires the site to have been built
|
|
232
|
+
* 支持自定义 distDir 配置(如 distDir: 'build')
|
|
204
233
|
*/
|
|
205
234
|
findDotNext() {
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
resolve(
|
|
213
|
-
|
|
214
|
-
resolve(this.constants.PACKAGE_PATH || "",
|
|
215
|
-
|
|
235
|
+
const possibleDirNames = [".next", "build", "dist", "out", ".build"];
|
|
236
|
+
const searchDirs = [
|
|
237
|
+
// The publish directory (已检测的目录)
|
|
238
|
+
this.publishDir
|
|
239
|
+
];
|
|
240
|
+
for (const dirName of possibleDirNames) {
|
|
241
|
+
searchDirs.push(resolve(dirName));
|
|
242
|
+
searchDirs.push(resolve(this.publishDir, "..", dirName));
|
|
243
|
+
searchDirs.push(resolve(this.constants.PACKAGE_PATH || "", dirName));
|
|
244
|
+
}
|
|
245
|
+
const uniqueDirs = [...new Set(searchDirs)];
|
|
246
|
+
for (const dir of uniqueDirs) {
|
|
216
247
|
if (existsSync(join(dir, "BUILD_ID"))) {
|
|
217
248
|
return dir;
|
|
218
249
|
}
|