@nudojs/service 1.0.0 → 1.1.0

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.
@@ -0,0 +1,815 @@
1
+ // src/evaluator/builtins/builtin-prototype.ts
2
+ import {
3
+ abs,
4
+ objOf,
5
+ formatAbs,
6
+ relationFn
7
+ } from "@nudojs/core";
8
+ function hasOwnProp(props, name) {
9
+ return Object.prototype.hasOwnProperty.call(props, name);
10
+ }
11
+ var BUILTIN_ERROR_CLASSES = /* @__PURE__ */ new Set([
12
+ "Error",
13
+ "TypeError",
14
+ "SyntaxError",
15
+ "RangeError",
16
+ "ReferenceError",
17
+ "URIError",
18
+ "EvalError"
19
+ ]);
20
+ var BUILTIN_PROTOTYPE_CLASSES = /* @__PURE__ */ new Set([
21
+ ...BUILTIN_ERROR_CLASSES,
22
+ "Date",
23
+ "Object",
24
+ "Map",
25
+ "Set",
26
+ "Promise",
27
+ "RegExp",
28
+ "Array",
29
+ "Function",
30
+ "String",
31
+ "Number",
32
+ "Boolean",
33
+ "Symbol",
34
+ "WeakMap",
35
+ "WeakSet",
36
+ "Buffer"
37
+ ]);
38
+ var numA = { shape: { k: "prim", type: "number" }, conf: "exact" };
39
+ var strA = { shape: { k: "prim", type: "string" }, conf: "exact" };
40
+ var boolA = { shape: { k: "prim", type: "boolean" }, conf: "exact" };
41
+ var unkA = { shape: { k: "unknown" }, conf: "partial" };
42
+ var undefA = abs({ k: "unknown" }, { op: "lit", value: void 0 }, void 0, "exact");
43
+ var nullA = abs({ k: "unknown" }, { op: "lit", value: null }, void 0, "exact");
44
+ function arrOf(el) {
45
+ return { shape: { k: "arr", element: el }, conf: "exact" };
46
+ }
47
+ function tupleOf(els) {
48
+ return { shape: { k: "tuple", elements: els }, conf: "exact" };
49
+ }
50
+ function promiseOf(inner) {
51
+ return { shape: { k: "eff", eff: "promise", inner }, conf: "exact" };
52
+ }
53
+ function sumOf(...members) {
54
+ if (members.length === 1) return members[0];
55
+ return { shape: { k: "sum", members }, conf: "exact" };
56
+ }
57
+ function brand(name) {
58
+ return { shape: { k: "brand", name, shape: unkA }, conf: "path" };
59
+ }
60
+ function emptyObj() {
61
+ return objOf({});
62
+ }
63
+ function sig(params, ret) {
64
+ return relationFn(params, ret, {
65
+ conf: "exact",
66
+ params: params.map((_, i) => `_arg${i}`)
67
+ });
68
+ }
69
+ var OBJECT_PROTOTYPE_METHODS = {
70
+ hasOwnProperty: sig([unkA], boolA),
71
+ isPrototypeOf: sig([unkA], boolA),
72
+ propertyIsEnumerable: sig([unkA], boolA),
73
+ toString: sig([], strA),
74
+ toLocaleString: sig([], strA),
75
+ valueOf: sig([], unkA)
76
+ };
77
+ var BUILTIN_PROTOTYPE_METHOD_APPROXIMATIONS = {
78
+ Object: { ...OBJECT_PROTOTYPE_METHODS },
79
+ Array: {
80
+ push: sig([unkA], numA),
81
+ pop: sig([], unkA),
82
+ shift: sig([], unkA),
83
+ unshift: sig([unkA], numA),
84
+ slice: sig([numA, numA], arrOf(unkA)),
85
+ splice: sig([numA, numA], arrOf(unkA)),
86
+ concat: sig([unkA], arrOf(unkA)),
87
+ join: sig([strA], strA),
88
+ indexOf: sig([unkA], numA),
89
+ lastIndexOf: sig([unkA], numA),
90
+ includes: sig([unkA], boolA),
91
+ map: sig([unkA], arrOf(unkA)),
92
+ flatMap: sig([unkA], arrOf(unkA)),
93
+ filter: sig([unkA], arrOf(unkA)),
94
+ forEach: sig([unkA], undefA),
95
+ find: sig([unkA], unkA),
96
+ findIndex: sig([unkA], numA),
97
+ some: sig([unkA], boolA),
98
+ every: sig([unkA], boolA),
99
+ reduce: sig([unkA, unkA], unkA),
100
+ sort: sig([unkA], arrOf(unkA)),
101
+ reverse: sig([], arrOf(unkA)),
102
+ toString: sig([], strA)
103
+ },
104
+ Function: {
105
+ call: sig([unkA], unkA),
106
+ apply: sig([unkA, unkA], unkA),
107
+ bind: sig([unkA], unkA),
108
+ toString: sig([], strA)
109
+ },
110
+ Map: {
111
+ get: sig([unkA], unkA),
112
+ set: sig([unkA, unkA], unkA),
113
+ has: sig([unkA], boolA),
114
+ delete: sig([unkA], boolA),
115
+ clear: sig([], undefA),
116
+ forEach: sig([unkA], undefA),
117
+ keys: sig([], arrOf(unkA)),
118
+ values: sig([], arrOf(unkA)),
119
+ entries: sig([], arrOf(tupleOf([unkA, unkA]))),
120
+ toString: sig([], strA)
121
+ },
122
+ Set: {
123
+ add: sig([unkA], unkA),
124
+ has: sig([unkA], boolA),
125
+ delete: sig([unkA], boolA),
126
+ clear: sig([], undefA),
127
+ forEach: sig([unkA], undefA),
128
+ keys: sig([], arrOf(unkA)),
129
+ values: sig([], arrOf(unkA)),
130
+ entries: sig([], arrOf(tupleOf([unkA, unkA]))),
131
+ toString: sig([], strA)
132
+ },
133
+ WeakMap: {
134
+ get: sig([unkA], unkA),
135
+ set: sig([unkA, unkA], unkA),
136
+ has: sig([unkA], boolA),
137
+ delete: sig([unkA], boolA),
138
+ toString: sig([], strA)
139
+ },
140
+ WeakSet: {
141
+ add: sig([unkA], unkA),
142
+ has: sig([unkA], boolA),
143
+ delete: sig([unkA], boolA),
144
+ toString: sig([], strA)
145
+ },
146
+ Promise: {
147
+ then: sig([unkA], promiseOf(unkA)),
148
+ catch: sig([unkA], promiseOf(unkA)),
149
+ finally: sig([unkA], promiseOf(unkA)),
150
+ toString: sig([], strA)
151
+ },
152
+ Date: {
153
+ getTime: sig([], numA),
154
+ valueOf: sig([], numA),
155
+ toISOString: sig([], strA),
156
+ toJSON: sig([], strA),
157
+ toLocaleString: sig([], strA),
158
+ toString: sig([], strA)
159
+ },
160
+ RegExp: {
161
+ test: sig([strA], boolA),
162
+ exec: sig([strA], sumOf(emptyObj(), nullA)),
163
+ toString: sig([], strA)
164
+ },
165
+ String: {
166
+ charAt: sig([numA], strA),
167
+ charCodeAt: sig([numA], numA),
168
+ indexOf: sig([strA], numA),
169
+ lastIndexOf: sig([strA], numA),
170
+ includes: sig([strA], boolA),
171
+ startsWith: sig([strA], boolA),
172
+ endsWith: sig([strA], boolA),
173
+ slice: sig([numA, numA], strA),
174
+ substring: sig([numA, numA], strA),
175
+ toUpperCase: sig([], strA),
176
+ toLowerCase: sig([], strA),
177
+ trim: sig([], strA),
178
+ replace: sig([unkA, strA], strA),
179
+ split: sig([strA], arrOf(strA)),
180
+ toString: sig([], strA),
181
+ valueOf: sig([], strA)
182
+ },
183
+ Number: {
184
+ toFixed: sig([numA], strA),
185
+ toPrecision: sig([numA], strA),
186
+ valueOf: sig([], numA),
187
+ toString: sig([numA], strA)
188
+ },
189
+ Boolean: {
190
+ valueOf: sig([], boolA),
191
+ toString: sig([], strA)
192
+ },
193
+ Symbol: {
194
+ toString: sig([], strA),
195
+ valueOf: sig([], brand("Symbol"))
196
+ },
197
+ Buffer: {
198
+ equals: sig([unkA], boolA),
199
+ compare: sig([unkA], numA),
200
+ toString: sig([unkA], strA),
201
+ toJSON: sig([], unkA)
202
+ },
203
+ Error: {
204
+ toString: sig([], strA)
205
+ }
206
+ };
207
+ function fmtNoConf(a) {
208
+ return formatAbs(a).replace(/\s+#(exact|path|widened|mock|partial|opaque)$/, "");
209
+ }
210
+ function describeAbsMember(a) {
211
+ if (a.shape.k !== "fn") return fmtNoConf(a);
212
+ const s = a.shape;
213
+ const pts = s.paramTypes ?? [];
214
+ const params = pts.map((p, i) => `${s.params[i] ?? `arg${i}`}: ${fmtNoConf(p)}`).join(", ");
215
+ const ret = s.returnType ? fmtNoConf(s.returnType) : "unknown";
216
+ return `(${params}) => ${ret}`;
217
+ }
218
+ function builtinProtoMemberNames(className) {
219
+ const table = hasOwnProp(BUILTIN_PROTOTYPE_METHOD_APPROXIMATIONS, className) ? BUILTIN_PROTOTYPE_METHOD_APPROXIMATIONS[className] : void 0;
220
+ return table ? Object.keys(table) : [];
221
+ }
222
+ function builtinProtoMember(className, member) {
223
+ const table = hasOwnProp(BUILTIN_PROTOTYPE_METHOD_APPROXIMATIONS, className) ? BUILTIN_PROTOTYPE_METHOD_APPROXIMATIONS[className] : BUILTIN_ERROR_CLASSES.has(className) ? BUILTIN_PROTOTYPE_METHOD_APPROXIMATIONS.Error : void 0;
224
+ if (!table) return null;
225
+ return table[member] ?? null;
226
+ }
227
+
228
+ // src/evaluator/env-loader.ts
229
+ import { readFileSync, existsSync, statSync, mkdirSync, writeFileSync } from "fs";
230
+ import { resolve as resolvePath, join as joinPath } from "path";
231
+ import { pathToFileURL } from "url";
232
+ import { createRequire } from "module";
233
+ import { createHash } from "crypto";
234
+ import { tmpdir } from "os";
235
+ import { defineEnv as defineEsEnv } from "@nudojs/env/es";
236
+ import { defineEnv as defineWebEnv } from "@nudojs/env/web";
237
+ import { defineEnv as defineNodeEnv } from "@nudojs/env/node";
238
+
239
+ // src/lru-map.ts
240
+ var BoundedLruMap = class {
241
+ map = /* @__PURE__ */ new Map();
242
+ max;
243
+ constructor(max) {
244
+ this.max = max;
245
+ }
246
+ get size() {
247
+ return this.map.size;
248
+ }
249
+ /** 当前上限(可动态收紧;收紧时立刻按 LRU 压到新上限) */
250
+ getMax() {
251
+ return this.max;
252
+ }
253
+ setMax(max) {
254
+ this.max = max;
255
+ this.trim();
256
+ }
257
+ has(key) {
258
+ return this.map.has(key);
259
+ }
260
+ get(key) {
261
+ const hit = this.map.get(key);
262
+ if (hit === void 0) return void 0;
263
+ this.map.delete(key);
264
+ this.map.set(key, hit);
265
+ return hit;
266
+ }
267
+ /** 命中不改变 LRU 序(只读探测) */
268
+ peek(key) {
269
+ return this.map.get(key);
270
+ }
271
+ set(key, value) {
272
+ if (this.max <= 0) return;
273
+ while (this.map.size >= this.max && !this.map.has(key)) {
274
+ const oldest = this.map.keys().next().value;
275
+ if (oldest === void 0) break;
276
+ this.map.delete(oldest);
277
+ }
278
+ this.map.delete(key);
279
+ this.map.set(key, value);
280
+ }
281
+ delete(key) {
282
+ return this.map.delete(key);
283
+ }
284
+ clear() {
285
+ this.map.clear();
286
+ }
287
+ keys() {
288
+ return this.map.keys();
289
+ }
290
+ values() {
291
+ return this.map.values();
292
+ }
293
+ entries() {
294
+ return this.map.entries();
295
+ }
296
+ /** 压到当前 max(调低上限时收内存) */
297
+ trim() {
298
+ while (this.map.size > this.max) {
299
+ const oldest = this.map.keys().next().value;
300
+ if (oldest === void 0) break;
301
+ this.map.delete(oldest);
302
+ }
303
+ }
304
+ };
305
+
306
+ // src/evaluator/env-loader.ts
307
+ var envFactories = {
308
+ es: defineEsEnv,
309
+ web: defineWebEnv,
310
+ node: defineNodeEnv
311
+ };
312
+ var impliedDeps = {
313
+ web: ["es"],
314
+ node: ["es"]
315
+ };
316
+ function resolveEnvNames(names) {
317
+ const resolved = /* @__PURE__ */ new Set();
318
+ const visit = (name) => {
319
+ if (resolved.has(name)) return;
320
+ const deps = impliedDeps[name];
321
+ if (deps) deps.forEach(visit);
322
+ resolved.add(name);
323
+ };
324
+ names.forEach(visit);
325
+ return [...resolved];
326
+ }
327
+ var PATH_ENV_CACHE_MAX = 64;
328
+ var PATH_ENV_BY_PATH_MAX = 64;
329
+ var PATH_ENV_BASE_DIRS_MAX = 64;
330
+ var pathEnvCache = new BoundedLruMap(PATH_ENV_CACHE_MAX);
331
+ var pathEnvByPath = new BoundedLruMap(PATH_ENV_BY_PATH_MAX);
332
+ var pathEnvBaseDirs = /* @__PURE__ */ new Set();
333
+ function addPathEnvBaseDir(baseDir) {
334
+ pathEnvBaseDirs.delete(baseDir);
335
+ pathEnvBaseDirs.add(baseDir);
336
+ while (pathEnvBaseDirs.size > PATH_ENV_BASE_DIRS_MAX) {
337
+ const oldest = pathEnvBaseDirs.values().next().value;
338
+ if (oldest === void 0) break;
339
+ pathEnvBaseDirs.delete(oldest);
340
+ }
341
+ }
342
+ function getPathEnvCacheSizes() {
343
+ return {
344
+ byKey: pathEnvCache.size,
345
+ byPath: pathEnvByPath.size,
346
+ baseDirs: pathEnvBaseDirs.size
347
+ };
348
+ }
349
+ function isPathEnvName(name, baseDir) {
350
+ if (name in envFactories) return false;
351
+ if (name.includes("/") || name.startsWith("./") || name.startsWith("../")) return true;
352
+ const resolved = resolvePath(baseDir, name);
353
+ return resolved.endsWith(".ts") && existsSync(resolved);
354
+ }
355
+ function rewriteBareImports(text) {
356
+ const require2 = createRequire(import.meta.url);
357
+ let rewrote = false;
358
+ const out = text.replace(/(["'])(@nudojs\/[a-z0-9-]+)\1/g, (_m, quote, spec) => {
359
+ try {
360
+ const entry = require2.resolve(spec);
361
+ const url = pathToFileURL(entry).href;
362
+ rewrote = true;
363
+ return `${quote}${url}${quote}`;
364
+ } catch {
365
+ return `${quote}${spec}${quote}`;
366
+ }
367
+ });
368
+ return rewrote ? out : null;
369
+ }
370
+ async function importPathEnv(resolvedPath, mtimeMs) {
371
+ const cacheKey = `${resolvedPath}:${mtimeMs}`;
372
+ if (pathEnvCache.get(cacheKey)) return;
373
+ let mod = null;
374
+ try {
375
+ const url = pathToFileURL(resolvedPath).href + `?mtime=${mtimeMs}`;
376
+ mod = await import(url);
377
+ } catch {
378
+ mod = null;
379
+ }
380
+ if (!mod) {
381
+ try {
382
+ const text = readFileSync(resolvedPath, "utf-8");
383
+ const rewritten = rewriteBareImports(text);
384
+ if (rewritten === null) return;
385
+ const cacheDir = joinPath(tmpdir(), "nudo-env");
386
+ mkdirSync(cacheDir, { recursive: true });
387
+ const hash = createHash("md5").update(`${resolvedPath}:${mtimeMs}`).digest("hex").slice(0, 16);
388
+ const copyPath = joinPath(cacheDir, `${hash}.ts`);
389
+ writeFileSync(copyPath, rewritten, "utf-8");
390
+ mod = await import(pathToFileURL(copyPath).href);
391
+ } catch {
392
+ return;
393
+ }
394
+ }
395
+ if (mod && typeof mod.defineEnv === "function") {
396
+ pathEnvCache.set(cacheKey, mod.defineEnv);
397
+ pathEnvByPath.set(resolvedPath, {
398
+ factory: mod.defineEnv,
399
+ mtimeMs
400
+ });
401
+ }
402
+ }
403
+ function lookupPathEnv(name) {
404
+ if (pathEnvByPath.size === 0) return void 0;
405
+ const candidates = name.startsWith("/") ? [name] : [name, ...[...pathEnvBaseDirs].map((d) => resolvePath(d, name))];
406
+ for (const candidate of candidates) {
407
+ const hit = pathEnvByPath.get(candidate);
408
+ if (!hit) continue;
409
+ try {
410
+ const { mtimeMs } = statSync(candidate);
411
+ if (mtimeMs !== hit.mtimeMs) {
412
+ pathEnvByPath.delete(candidate);
413
+ return void 0;
414
+ }
415
+ } catch {
416
+ pathEnvByPath.delete(candidate);
417
+ return void 0;
418
+ }
419
+ return hit.factory;
420
+ }
421
+ return void 0;
422
+ }
423
+ function clearPathEnvCaches() {
424
+ pathEnvCache.clear();
425
+ pathEnvByPath.clear();
426
+ pathEnvBaseDirs.clear();
427
+ }
428
+ async function preloadPathEnvs(envNames, baseDir) {
429
+ addPathEnvBaseDir(baseDir);
430
+ for (const name of envNames) {
431
+ if (!isPathEnvName(name, baseDir)) continue;
432
+ const resolved = resolvePath(baseDir, name);
433
+ if (!existsSync(resolved)) continue;
434
+ try {
435
+ const { mtimeMs } = statSync(resolved);
436
+ await importPathEnv(resolved, mtimeMs);
437
+ } catch {
438
+ }
439
+ }
440
+ }
441
+ async function loadEnvsAsync(envNames, globalEnv, baseDir = process.cwd()) {
442
+ await preloadPathEnvs(envNames, baseDir);
443
+ return loadEnvs(envNames, globalEnv);
444
+ }
445
+ function loadEnvs(envNames, globalEnv) {
446
+ const allModules = {};
447
+ const allGlobals = {};
448
+ const resolved = resolveEnvNames(envNames);
449
+ for (const name of resolved) {
450
+ const factory = envFactories[name] ?? lookupPathEnv(name);
451
+ if (!factory) continue;
452
+ const def = factory();
453
+ for (const [key, value] of Object.entries(def.globals)) {
454
+ allGlobals[key] = value;
455
+ globalEnv.bind(key, value);
456
+ }
457
+ if (def.modules) {
458
+ for (const [modName, exports] of Object.entries(def.modules)) {
459
+ allModules[modName] = { ...allModules[modName], ...exports };
460
+ }
461
+ }
462
+ }
463
+ return { modules: allModules, globals: allGlobals };
464
+ }
465
+
466
+ // src/evaluator/config.ts
467
+ import { readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
468
+ import { resolve, dirname, relative, sep } from "path";
469
+
470
+ // src/session-cache-limits.ts
471
+ var DEFAULT_SESSION_CACHE_LIMITS = {
472
+ maxFiles: 64,
473
+ maxFns: 1024,
474
+ maxBRuns: 32
475
+ };
476
+ var HARD_CAP = 65536;
477
+ function clampEntries(n, fallback) {
478
+ if (typeof n !== "number" || !Number.isFinite(n)) return fallback;
479
+ return Math.max(0, Math.min(HARD_CAP, Math.floor(n)));
480
+ }
481
+ function parseLimit(raw, fallback) {
482
+ if (raw === void 0 || raw === "") return fallback;
483
+ if (raw === "off" || raw === "0") return 0;
484
+ const n = Number(raw);
485
+ return clampEntries(n, fallback);
486
+ }
487
+ var explicit = {};
488
+ var fromProject = {};
489
+ var envCache = null;
490
+ function envLimits(env) {
491
+ return {
492
+ maxFiles: parseLimit(env.NUDO_CACHE_MAX_FILES, NaN),
493
+ maxFns: parseLimit(env.NUDO_CACHE_MAX_FNS, NaN),
494
+ maxBRuns: parseLimit(env.NUDO_CACHE_MAX_BRUNS, NaN)
495
+ };
496
+ }
497
+ function pick(key, env, fallback) {
498
+ const e = env[key];
499
+ if (typeof e === "number" && Number.isFinite(e)) return e;
500
+ const x = explicit[key];
501
+ if (typeof x === "number" && Number.isFinite(x)) return clampEntries(x, fallback);
502
+ const p = fromProject[key];
503
+ if (typeof p === "number" && Number.isFinite(p)) return clampEntries(p, fallback);
504
+ return fallback;
505
+ }
506
+ function getSessionCacheLimits(env = process.env) {
507
+ if (envCache === null) envCache = envLimits(env);
508
+ return {
509
+ maxFiles: pick("maxFiles", envCache, DEFAULT_SESSION_CACHE_LIMITS.maxFiles),
510
+ maxFns: pick("maxFns", envCache, DEFAULT_SESSION_CACHE_LIMITS.maxFns),
511
+ maxBRuns: pick("maxBRuns", envCache, DEFAULT_SESSION_CACHE_LIMITS.maxBRuns)
512
+ };
513
+ }
514
+ function setSessionCacheLimits(partial) {
515
+ explicit = partial ? { ...partial } : {};
516
+ return getSessionCacheLimits();
517
+ }
518
+ function setSessionCacheFromProject(partial) {
519
+ fromProject = partial ? { ...partial } : {};
520
+ }
521
+ function resetSessionCacheLimitState() {
522
+ explicit = {};
523
+ fromProject = {};
524
+ envCache = null;
525
+ }
526
+
527
+ // src/evaluator/config.ts
528
+ import { setBForkBudgetLimit, getBForkBudgetLimit, MAX_B_TOTAL_FORKS } from "@nudojs/core/internal";
529
+ function checkConfig(config) {
530
+ const raw = config?.check;
531
+ const rawEntry = raw?.entryThrows;
532
+ let entryThrows = "error";
533
+ if (rawEntry === "off" || rawEntry === "warning" || rawEntry === "error") {
534
+ entryThrows = rawEntry;
535
+ } else if (rawEntry !== void 0) {
536
+ process.stderr?.write?.(
537
+ `nudo.check.entryThrows: invalid value ${JSON.stringify(rawEntry)} (expected error|warning|off); using error
538
+ `
539
+ );
540
+ }
541
+ const ignore = Array.isArray(raw?.ignoreThrows) ? raw.ignoreThrows.filter((s) => typeof s === "string" && s.length > 0) : [];
542
+ return { entryThrows, ignoreThrows: ignore };
543
+ }
544
+ var DEFAULT_ANALYSIS_EXCLUDE = [
545
+ "**/node_modules/**",
546
+ "**/dist/**",
547
+ "**/coverage/**"
548
+ ];
549
+ var DEFAULT_ANALYSIS_MODE = "exports";
550
+ function toStringArray(raw, fallback) {
551
+ if (raw === void 0) return fallback;
552
+ const arr = Array.isArray(raw) ? raw : [raw];
553
+ const out = arr.filter((s) => typeof s === "string" && s.length > 0);
554
+ return out.length > 0 ? out : fallback;
555
+ }
556
+ function analysisConfig(config) {
557
+ const raw = config?.analysis;
558
+ const modeRaw = raw?.mode;
559
+ const mode = modeRaw === "exports" || modeRaw === "all" || modeRaw === "directives" ? modeRaw : DEFAULT_ANALYSIS_MODE;
560
+ const diagRaw = raw?.diagnostics;
561
+ const diagnostics = diagRaw === "off" || diagRaw === "errors" || diagRaw === "default" || diagRaw === "verbose" ? diagRaw : mode === "directives" ? "errors" : "default";
562
+ const budgetRaw = raw?.callSiteBudget;
563
+ const callSiteBudget = typeof budgetRaw === "number" && Number.isFinite(budgetRaw) && budgetRaw >= 1 ? Math.min(Math.floor(budgetRaw), 64) : 3;
564
+ return {
565
+ // include 空数组 = 不过滤(与「省略」同义)
566
+ include: toStringArray(raw?.include, []),
567
+ // exclude 空数组回落默认安全列表,避免误关 node_modules 保护
568
+ exclude: toStringArray(raw?.exclude, DEFAULT_ANALYSIS_EXCLUDE),
569
+ mode,
570
+ diagnostics,
571
+ callSiteBudget,
572
+ evalMissingSlot: raw?.evalMissingSlot === "warning" ? "warning" : "off",
573
+ maxForks: parseMaxForks(raw?.maxForks) ?? MAX_B_TOTAL_FORKS
574
+ };
575
+ }
576
+ function parseMaxForks(raw) {
577
+ if (typeof raw !== "number" || !Number.isFinite(raw) || raw < 1) return void 0;
578
+ return Math.floor(raw);
579
+ }
580
+ function applyBForkBudgetFromConfig(config, env = process.env) {
581
+ const fromEnv = parseMaxForks(
582
+ env.NUDO_MAX_FORKS === void 0 ? void 0 : Number(env.NUDO_MAX_FORKS)
583
+ );
584
+ const fromCfg = parseMaxForks(config?.analysis?.maxForks);
585
+ return setBForkBudgetLimit(fromEnv ?? fromCfg ?? MAX_B_TOTAL_FORKS);
586
+ }
587
+ function currentBForkBudgetLimit() {
588
+ return getBForkBudgetLimit();
589
+ }
590
+ function diskCacheRoot(config, projectDir) {
591
+ const raw = config?.cache;
592
+ if (raw === false) return void 0;
593
+ if (typeof raw === "string" && raw.length > 0) {
594
+ return projectDir ? resolve(projectDir, raw) : raw;
595
+ }
596
+ if (raw === true) {
597
+ return projectDir ? resolve(projectDir, ".nudo/cache") : void 0;
598
+ }
599
+ const env = process.env.NUDO_CACHE_DIR;
600
+ if (env === "off" || env === "0") return void 0;
601
+ if (env && env.length > 0) return env;
602
+ return void 0;
603
+ }
604
+ function interfaceConfig(config) {
605
+ const raw = config?.contract?.emit;
606
+ const emit = raw === void 0 ? [] : Array.isArray(raw) ? raw.filter((s) => typeof s === "string" && s.length > 0) : typeof raw === "string" && raw.length > 0 ? [raw] : [];
607
+ return {
608
+ autoBind: config?.contract?.autoBind ?? true,
609
+ emit
610
+ };
611
+ }
612
+ function matchesEmitAllowlist(absPath, projectDir, patterns) {
613
+ if (patterns.length === 0) return true;
614
+ if (!projectDir) return false;
615
+ const rel = relative(projectDir, absPath).split(sep).join("/");
616
+ if (rel.startsWith("..")) return false;
617
+ return patterns.some(
618
+ (p) => globMatch(
619
+ p.split(sep).join("/").replace(/^\.\//, ""),
620
+ rel
621
+ )
622
+ );
623
+ }
624
+ function globMatch(pattern, path) {
625
+ let rx = "";
626
+ for (let i = 0; i < pattern.length; i++) {
627
+ const ch = pattern[i];
628
+ if (ch === "*" && pattern[i + 1] === "*") {
629
+ if (pattern[i + 2] === "/") {
630
+ rx += "(?:.*/)?";
631
+ i += 2;
632
+ } else {
633
+ rx += ".*";
634
+ i += 1;
635
+ }
636
+ continue;
637
+ }
638
+ if (ch === "*") {
639
+ rx += "[^/]*";
640
+ continue;
641
+ }
642
+ if (ch === "?") {
643
+ rx += "[^/]";
644
+ continue;
645
+ }
646
+ rx += /[.+^${}()|[\]\\]/.test(ch) ? `\\${ch}` : ch;
647
+ }
648
+ return new RegExp(`^${rx}$`).test(path);
649
+ }
650
+ function findProjectConfig(startDir) {
651
+ let dir = resolve(startDir);
652
+ const root = resolve("/");
653
+ while (dir !== root) {
654
+ const pkgPath = resolve(dir, "package.json");
655
+ if (existsSync2(pkgPath)) {
656
+ try {
657
+ const pkg = JSON.parse(readFileSync2(pkgPath, "utf-8"));
658
+ if (pkg.nudo) {
659
+ const nudo = pkg.nudo;
660
+ setSessionCacheFromProject(nudo.sessionCache);
661
+ applyBForkBudgetFromConfig(nudo);
662
+ return { config: nudo, projectDir: dir };
663
+ }
664
+ } catch {
665
+ }
666
+ }
667
+ const parent = dirname(dir);
668
+ if (parent === dir) break;
669
+ dir = parent;
670
+ }
671
+ applyBForkBudgetFromConfig(null);
672
+ return null;
673
+ }
674
+
675
+ // src/evaluator/resolve-npm.ts
676
+ import { readFileSync as readFileSync3, existsSync as existsSync3, statSync as statSync2 } from "fs";
677
+ import { resolve as resolve2, join, dirname as dirname2 } from "path";
678
+ function findNodeModules(startDir) {
679
+ let dir = resolve2(startDir);
680
+ const root = resolve2("/");
681
+ while (dir !== root) {
682
+ const nmPath = join(dir, "node_modules");
683
+ if (existsSync3(nmPath)) return nmPath;
684
+ const parent = dirname2(dir);
685
+ if (parent === dir) break;
686
+ dir = parent;
687
+ }
688
+ return null;
689
+ }
690
+ function resolveNpmJsEntry(source, fromDir) {
691
+ if (!source || source.startsWith(".") || source.startsWith("/") || source.startsWith("node:")) {
692
+ return null;
693
+ }
694
+ const parts = source.startsWith("@") ? source.split("/").slice(0, 2) : source.split("/").slice(0, 1);
695
+ const pkgName = parts.join("/");
696
+ const subpath = source.slice(pkgName.length).replace(/^\//, "") || ".";
697
+ const nodeModules = findNodeModules(fromDir);
698
+ if (!nodeModules) return null;
699
+ const pkgDir = join(nodeModules, pkgName);
700
+ const pkgJsonPath = join(pkgDir, "package.json");
701
+ if (!existsSync3(pkgJsonPath)) return null;
702
+ const tryFile = (p) => {
703
+ for (const c of [p, `${p}.js`, `${p}.cjs`, `${p}.mjs`, join(p, "index.js"), join(p, "index.cjs")]) {
704
+ try {
705
+ if (existsSync3(c) && statSync2(c).isFile()) return c;
706
+ } catch {
707
+ }
708
+ }
709
+ return null;
710
+ };
711
+ let pkg = {};
712
+ try {
713
+ pkg = JSON.parse(readFileSync3(pkgJsonPath, "utf-8"));
714
+ } catch {
715
+ return null;
716
+ }
717
+ const exportsField = pkg.exports;
718
+ const pickExport = (entry) => {
719
+ if (typeof entry === "string") return entry;
720
+ if (entry && typeof entry === "object") {
721
+ const o = entry;
722
+ for (const k of ["nudo", "node", "require", "import", "default"]) {
723
+ const v = o[k];
724
+ const r = pickExport(v);
725
+ if (r) return r;
726
+ }
727
+ }
728
+ return null;
729
+ };
730
+ if (exportsField) {
731
+ let rel = null;
732
+ if (typeof exportsField === "string" && subpath === ".") rel = exportsField;
733
+ else if (exportsField && typeof exportsField === "object") {
734
+ const map = exportsField;
735
+ const key = subpath === "." ? "." : `./${subpath}`;
736
+ rel = pickExport(map[key] ?? (subpath === "." ? map : void 0));
737
+ }
738
+ if (rel) {
739
+ const hit = tryFile(resolve2(pkgDir, rel));
740
+ if (hit) return hit;
741
+ }
742
+ }
743
+ if (subpath === ".") {
744
+ const main = typeof pkg.main === "string" ? pkg.main : void 0;
745
+ const hit = tryFile(resolve2(pkgDir, main ?? "."));
746
+ if (hit) return hit;
747
+ const mod = typeof pkg.module === "string" ? pkg.module : void 0;
748
+ if (mod) {
749
+ const hitMod = tryFile(resolve2(pkgDir, mod));
750
+ if (hitMod) return hitMod;
751
+ }
752
+ return null;
753
+ }
754
+ return tryFile(resolve2(pkgDir, subpath));
755
+ }
756
+ function resolveExportsNudo(exports, subpath) {
757
+ if (!exports || typeof exports !== "object") return null;
758
+ const entry = exports[subpath];
759
+ if (!entry) return null;
760
+ if (typeof entry === "object" && entry !== null && "nudo" in entry) {
761
+ const nudoEntry = entry["nudo"];
762
+ if (typeof nudoEntry === "string") return nudoEntry;
763
+ }
764
+ return null;
765
+ }
766
+ function resolveNpmNudo(source, fromDir) {
767
+ const isRelative = source.startsWith(".") || source.startsWith("/");
768
+ if (isRelative) return null;
769
+ const parts = source.startsWith("@") ? source.split("/").slice(0, 2) : source.split("/").slice(0, 1);
770
+ const pkgName = parts.join("/");
771
+ const subpath = source.slice(pkgName.length) || ".";
772
+ const nodeModules = findNodeModules(fromDir);
773
+ if (!nodeModules) return null;
774
+ const pkgJsonPath = join(nodeModules, pkgName, "package.json");
775
+ if (!existsSync3(pkgJsonPath)) return null;
776
+ try {
777
+ const pkg = JSON.parse(readFileSync3(pkgJsonPath, "utf-8"));
778
+ const nudoEntry = resolveExportsNudo(pkg.exports, subpath);
779
+ if (nudoEntry) {
780
+ const resolved = resolve2(dirname2(pkgJsonPath), nudoEntry);
781
+ if (existsSync3(resolved)) return resolved;
782
+ }
783
+ } catch {
784
+ }
785
+ return null;
786
+ }
787
+
788
+ export {
789
+ resolveNpmJsEntry,
790
+ resolveNpmNudo,
791
+ BUILTIN_PROTOTYPE_METHOD_APPROXIMATIONS,
792
+ describeAbsMember,
793
+ builtinProtoMemberNames,
794
+ builtinProtoMember,
795
+ BoundedLruMap,
796
+ getPathEnvCacheSizes,
797
+ clearPathEnvCaches,
798
+ preloadPathEnvs,
799
+ loadEnvsAsync,
800
+ loadEnvs,
801
+ DEFAULT_SESSION_CACHE_LIMITS,
802
+ getSessionCacheLimits,
803
+ setSessionCacheLimits,
804
+ setSessionCacheFromProject,
805
+ resetSessionCacheLimitState,
806
+ checkConfig,
807
+ DEFAULT_ANALYSIS_MODE,
808
+ analysisConfig,
809
+ applyBForkBudgetFromConfig,
810
+ currentBForkBudgetLimit,
811
+ diskCacheRoot,
812
+ interfaceConfig,
813
+ matchesEmitAllowlist,
814
+ findProjectConfig
815
+ };