@nudojs/service 2.0.0 → 4.0.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.
- package/dist/index.d.ts +70 -3
- package/dist/index.js +247 -22
- package/package.json +5 -5
package/dist/index.d.ts
CHANGED
|
@@ -479,7 +479,7 @@ declare function lookupHarvested(h: PackageHarvest, moduleName: string, exportNa
|
|
|
479
479
|
* 手动 `nudo harvest` 仍保留;这里是分析路径上的按需注入。
|
|
480
480
|
*/
|
|
481
481
|
|
|
482
|
-
/** 裸说明符 → 包名(含 scope);相对/绝对/node: 内建返回 undefined */
|
|
482
|
+
/** 裸说明符 → 包名(含 scope);相对/绝对/node: 与裸 Node 内建返回 undefined */
|
|
483
483
|
declare function barePackageName(spec: string): string | undefined;
|
|
484
484
|
declare function collectBarePackages(source: string): string[];
|
|
485
485
|
declare function harvestPackageCached(pkg: string, fromDir: string): PackageHarvest | null;
|
|
@@ -573,6 +573,34 @@ declare function bareSpecToAbsModules(spec: string, fromFile: string): AbsModule
|
|
|
573
573
|
declare function collectEnvGlobals(envNames: string[]): Record<string, Abs>;
|
|
574
574
|
/** @nudo:env modules(path / node:path / fs…)→ AbsModuleExports */
|
|
575
575
|
declare function collectEnvModules(envNames: string[]): Record<string, AbsModuleExports>;
|
|
576
|
+
/** Conflict when handwritten env overwrote a harvest module/export (B8). */
|
|
577
|
+
type EnvHarvestConflict = {
|
|
578
|
+
module: string;
|
|
579
|
+
/** export names where env replaced a harvest binding (empty + defaulted = default only) */
|
|
580
|
+
exports: string[];
|
|
581
|
+
defaultOverwritten: boolean;
|
|
582
|
+
};
|
|
583
|
+
/**
|
|
584
|
+
* Install conflict collector; returns the previous one so nested/concurrent
|
|
585
|
+
* analyzeFile callers can save/restore (module-global is not re-entrant).
|
|
586
|
+
*/
|
|
587
|
+
declare function setEnvHarvestConflictCollector(collector: ((c: EnvHarvestConflict) => void) | null): ((c: EnvHarvestConflict) => void) | null;
|
|
588
|
+
/** Read-only peek for tests / nested restore. */
|
|
589
|
+
declare function getEnvHarvestConflictCollector(): ((c: EnvHarvestConflict) => void) | null;
|
|
590
|
+
type MergeHarvestOptions = {
|
|
591
|
+
/**
|
|
592
|
+
* Per-call conflict sink. Takes precedence over the module-global collector
|
|
593
|
+
* installed via `setEnvHarvestConflictCollector`.
|
|
594
|
+
*/
|
|
595
|
+
onConflict?: (c: EnvHarvestConflict) => void;
|
|
596
|
+
};
|
|
597
|
+
/**
|
|
598
|
+
* Handwritten `@nudojs/env` wins over harvest / graph modules on overlapping
|
|
599
|
+
* module keys and overlapping export names (docs/versioning.md B8 + website
|
|
600
|
+
* harvester API). Harvest-only modules/exports are kept as fill-in.
|
|
601
|
+
* Overwrites notify `opts.onConflict` or the global collector.
|
|
602
|
+
*/
|
|
603
|
+
declare function mergeHarvestUnderEnv(harvestModules: Record<string, AbsModuleExports>, envModules: Record<string, AbsModuleExports>, opts?: MergeHarvestOptions): Record<string, AbsModuleExports>;
|
|
576
604
|
/** 收集 @nudo:replace + @nudo:as → transpile 注入表 */
|
|
577
605
|
declare function collectBPathReplacements(source: string): {
|
|
578
606
|
targets: Array<{
|
|
@@ -821,18 +849,57 @@ declare function collectBPathDiagnostics(source: string, extraKnown?: Iterable<s
|
|
|
821
849
|
/**
|
|
822
850
|
* 预置 Node API env:从 @types/node 的 .d.ts harvest。
|
|
823
851
|
* 与 packages/env 的手写 env 并行;此路径自动、可刷新。
|
|
852
|
+
*
|
|
853
|
+
* Productization (P0-B B2/B7):
|
|
854
|
+
* - In-process cache keyed by package root + package.json mtime/size.
|
|
855
|
+
* - Terminal failures (`not-found`/`no-dts`/`failed`) also cache — no retry storm.
|
|
856
|
+
* `disabled` is never cached (env var can flip mid-process).
|
|
857
|
+
* - `clearNodeHarvestCache()` for tests / watch invalidation.
|
|
858
|
+
* - `NUDO_HARVEST_NODE=off` disables harvest (explicit skip, not silent).
|
|
859
|
+
* - Defaults stay IDE-budgeted: maxFiles=12, maxMs=2500.
|
|
824
860
|
*/
|
|
825
861
|
|
|
862
|
+
/** IDE-startup budgets for @types/node harvest. Do not raise casually. */
|
|
863
|
+
declare const HARVEST_NODE_DEFAULT_MAX_FILES = 12;
|
|
864
|
+
declare const HARVEST_NODE_DEFAULT_MAX_MS = 2500;
|
|
865
|
+
type HarvestNodeStats = {
|
|
866
|
+
files: number;
|
|
867
|
+
symbols: number;
|
|
868
|
+
skipped: number;
|
|
869
|
+
};
|
|
826
870
|
type NodeEnvResult = {
|
|
827
871
|
ok: true;
|
|
828
872
|
env: HarvestedEnv;
|
|
829
873
|
root: string;
|
|
830
874
|
files: number;
|
|
875
|
+
stats: HarvestNodeStats;
|
|
876
|
+
/** true when this result came from the in-process cache */
|
|
877
|
+
cached: boolean;
|
|
831
878
|
} | {
|
|
832
879
|
ok: false;
|
|
833
880
|
error: string;
|
|
881
|
+
reason: "disabled" | "not-found" | "no-dts" | "failed";
|
|
882
|
+
/** true when this failure came from the in-process cache */
|
|
883
|
+
cached?: boolean;
|
|
834
884
|
};
|
|
835
|
-
/**
|
|
885
|
+
/**
|
|
886
|
+
* Clear the in-process @types/node harvest cache.
|
|
887
|
+
* Drops **success and terminal-failure** entries (including `not-found`), so
|
|
888
|
+
* a mid-session `@types/node` install becomes visible without process restart.
|
|
889
|
+
* Tests, watch-mode dep-hash invalidation, and package-manager hooks should
|
|
890
|
+
* call this when `node_modules/@types/node` changes underneath the process.
|
|
891
|
+
*/
|
|
892
|
+
declare function clearNodeHarvestCache(): void;
|
|
893
|
+
/** Number of cached harvest results (diagnostics / tests). */
|
|
894
|
+
declare function getNodeHarvestCacheSize(): number;
|
|
895
|
+
declare function isHarvestNodeDisabled(env?: NodeJS.ProcessEnv): boolean;
|
|
896
|
+
/**
|
|
897
|
+
* harvest @types/node(限制文件数 + 时间预算,避免拖垮启动)。
|
|
898
|
+
* Success **and** terminal failures (`not-found` / `no-dts` / `failed`) are
|
|
899
|
+
* cached in-process so IDE analysis does not re-walk / re-timeout every file.
|
|
900
|
+
* `disabled` is never cached — the env var can flip mid-process.
|
|
901
|
+
* Call `clearNodeHarvestCache()` after `@types/node` changes.
|
|
902
|
+
*/
|
|
836
903
|
declare function harvestNodeTypes(fromDir?: string, maxFiles?: number, maxMs?: number): NodeEnvResult;
|
|
837
904
|
/** 把 harvest 结果压成「模块名 → 导出名列表」摘要,便于日志/测试 */
|
|
838
905
|
declare function summarizeNodeEnv(env: HarvestedEnv): {
|
|
@@ -1321,4 +1388,4 @@ declare function emitDerivedFromRoot(rootFile: string, opts: {
|
|
|
1321
1388
|
refreshExistingOnly?: boolean;
|
|
1322
1389
|
}): EmitDerivedResult;
|
|
1323
1390
|
|
|
1324
|
-
export { ANALYSIS_ABI, type AbsGraphOptions, type AbsMockSeeds, type AbsModuleCacheEntry, type AbsModuleGraphResult, type AbsModuleLoadIssue, AnalysisConfig, type AnalysisResult, type AnalysisSession, type BPathBuiltinUnknown, type BPathDiagnostics, type BPathRunResult, type BPathUnreachable, type BindingInfo, type BuildSemanticTokensOpts, CallRecord, type CaseHint, type CaseInfo, type CaseResult, type CompletionItem, type ConstraintSourceExpr, type DepContent, type DerivedExport, type DerivedParam, type Diagnostic, type DiagnosticSeverity, type DiagnosticTag, DiagnosticsLevel, DiskCache, type DiskCacheOptions, type DraftEvidence, type EmitDerivedResult, type EmitInterfaceOpts, type EmitInterfaceResult, type EmitInterfaceSkipReason, type EmitResult, type EmitSkipReason, type FunctionAnalysis, type HoverInfo, type InferJson, type InferJsonCase, type InferJsonFunction, type InterfaceDraftEntry, type InterfaceDraftOpts, type InterfaceDraftResult, type InterfaceSurfaceEntry, type InterfaceSurfaceOpts, type LoadModule, type ModuleExports, type ModuleGraphCache, type NodeEnvResult, type PackageHarvest, type ReferenceInfo, type RootDeriveOpts, type RootDeriveResult, SEMANTIC_TOKEN_MODIFIERS, SEMANTIC_TOKEN_TYPES, type SemanticToken, type SourceLocation, type SymbolInfo, type SymbolTable, type WriteDraftResult, absToTSType, absToZodSchema, ambientSourcesOfSidecar, analysisFileCacheKey, analyzeExportsFromSource, analyzeFile, analyzeFileAsync, autoHarvestModules, barePackageName, bareSpecToAbsModules, buildCaseDirective, buildModuleGraph, buildSemanticTokens, checkCacheKey, clearAbsModuleCache, clearAnalysisFileCache, clearAnalysisSessionCaches, clearBPathCache, clearEnvPathDeps, clearFnAnalysisCache, clearHarvestCache, collectAbsBindingsFromGraph, collectBPathDiagnostics, collectBPathReplacements, collectBarePackages, collectCallRecords, collectDependencySpecs, collectDtsFromEntry, collectEnvGlobals, collectEnvModules, collectLoadDepContents, collectParamBodyAccesses, collectStaticImports, computeDirtySet, defaultAbsLoadModule, defaultLoadModule, deriveFromRoot, diagnosticsLevelForFile, draftInterface, emitDerivedFromRoot, emitInterface, encodeSemanticTokens, envPathDependents, evalAbsModuleGraph, evalProgramAbsWithModules, evictAbsModuleCacheFiles, evictAnalysisCachesForFiles, evictAnalysisFileCacheForFiles, evictBPathCacheForFiles, evictFnAnalysisCacheForFiles, extractFnConstraintSources, extractNudoImportSpecs, filterDiagnosticsByLevel, formatDerivedSection, formatDraftModule, formatDraftSummary, formatEmitSummary, formatHarvestSummary, formatInterfaceSurfaceLine, generateDts, generateFunctionDtsLines, generateGuardFunction, generateGuardFunctionFromAbs, getAbsAtPosition, getAbsAtPositionAsync, getAnalysisFileCacheSize, getAnalysisSession, getCasesForFile, getCompletionsAtPosition, getHoverAtPosition, getTypeAtPosition, getTypeAtPositionAsync, harvestNodeTypes, harvestPackage, harvestPackageCached, harvestToAbsModules, harvestedValueToAbs, ifaceCacheKey, insertGeneratedCaseDirectives, interfaceSurface, interfaceTierModifierBit, isBPathCapable, isDraftableEntry, isEnvTemplatePath, isNudoTargetPath, isProjectConfigPath, isSidecarPath, isWatchRelevantPath, lookupHarvested, mockDirectivesToAbsSeeds, noteEnvPathDeps, packageHarvestToAbsModules, relativizePath, resetAllAnalysisCaches, resolvePackageRoot, serializeCaseArg, serializeInferJson, setAnalysisSession, sha256Hex, shouldAnalyzeFile, sidecarDraftPath, hasNudoDirectives as sourceHasNudoDirectives, stripGeneratedCaseDirectives, summarizeNodeEnv, topoSortDirty, tryBPathCall, tryBPathCallFull, tryRunBPath, unifiedDiff, writeInterfaceDraft };
|
|
1391
|
+
export { ANALYSIS_ABI, type AbsGraphOptions, type AbsMockSeeds, type AbsModuleCacheEntry, type AbsModuleGraphResult, type AbsModuleLoadIssue, AnalysisConfig, type AnalysisResult, type AnalysisSession, type BPathBuiltinUnknown, type BPathDiagnostics, type BPathRunResult, type BPathUnreachable, type BindingInfo, type BuildSemanticTokensOpts, CallRecord, type CaseHint, type CaseInfo, type CaseResult, type CompletionItem, type ConstraintSourceExpr, type DepContent, type DerivedExport, type DerivedParam, type Diagnostic, type DiagnosticSeverity, type DiagnosticTag, DiagnosticsLevel, DiskCache, type DiskCacheOptions, type DraftEvidence, type EmitDerivedResult, type EmitInterfaceOpts, type EmitInterfaceResult, type EmitInterfaceSkipReason, type EmitResult, type EmitSkipReason, type EnvHarvestConflict, type FunctionAnalysis, HARVEST_NODE_DEFAULT_MAX_FILES, HARVEST_NODE_DEFAULT_MAX_MS, type HarvestNodeStats, type HoverInfo, type InferJson, type InferJsonCase, type InferJsonFunction, type InterfaceDraftEntry, type InterfaceDraftOpts, type InterfaceDraftResult, type InterfaceSurfaceEntry, type InterfaceSurfaceOpts, type LoadModule, type MergeHarvestOptions, type ModuleExports, type ModuleGraphCache, type NodeEnvResult, type PackageHarvest, type ReferenceInfo, type RootDeriveOpts, type RootDeriveResult, SEMANTIC_TOKEN_MODIFIERS, SEMANTIC_TOKEN_TYPES, type SemanticToken, type SourceLocation, type SymbolInfo, type SymbolTable, type WriteDraftResult, absToTSType, absToZodSchema, ambientSourcesOfSidecar, analysisFileCacheKey, analyzeExportsFromSource, analyzeFile, analyzeFileAsync, autoHarvestModules, barePackageName, bareSpecToAbsModules, buildCaseDirective, buildModuleGraph, buildSemanticTokens, checkCacheKey, clearAbsModuleCache, clearAnalysisFileCache, clearAnalysisSessionCaches, clearBPathCache, clearEnvPathDeps, clearFnAnalysisCache, clearHarvestCache, clearNodeHarvestCache, collectAbsBindingsFromGraph, collectBPathDiagnostics, collectBPathReplacements, collectBarePackages, collectCallRecords, collectDependencySpecs, collectDtsFromEntry, collectEnvGlobals, collectEnvModules, collectLoadDepContents, collectParamBodyAccesses, collectStaticImports, computeDirtySet, defaultAbsLoadModule, defaultLoadModule, deriveFromRoot, diagnosticsLevelForFile, draftInterface, emitDerivedFromRoot, emitInterface, encodeSemanticTokens, envPathDependents, evalAbsModuleGraph, evalProgramAbsWithModules, evictAbsModuleCacheFiles, evictAnalysisCachesForFiles, evictAnalysisFileCacheForFiles, evictBPathCacheForFiles, evictFnAnalysisCacheForFiles, extractFnConstraintSources, extractNudoImportSpecs, filterDiagnosticsByLevel, formatDerivedSection, formatDraftModule, formatDraftSummary, formatEmitSummary, formatHarvestSummary, formatInterfaceSurfaceLine, generateDts, generateFunctionDtsLines, generateGuardFunction, generateGuardFunctionFromAbs, getAbsAtPosition, getAbsAtPositionAsync, getAnalysisFileCacheSize, getAnalysisSession, getCasesForFile, getCompletionsAtPosition, getEnvHarvestConflictCollector, getHoverAtPosition, getNodeHarvestCacheSize, getTypeAtPosition, getTypeAtPositionAsync, harvestNodeTypes, harvestPackage, harvestPackageCached, harvestToAbsModules, harvestedValueToAbs, ifaceCacheKey, insertGeneratedCaseDirectives, interfaceSurface, interfaceTierModifierBit, isBPathCapable, isDraftableEntry, isEnvTemplatePath, isHarvestNodeDisabled, isNudoTargetPath, isProjectConfigPath, isSidecarPath, isWatchRelevantPath, lookupHarvested, mergeHarvestUnderEnv, mockDirectivesToAbsSeeds, noteEnvPathDeps, packageHarvestToAbsModules, relativizePath, resetAllAnalysisCaches, resolvePackageRoot, serializeCaseArg, serializeInferJson, setAnalysisSession, setEnvHarvestConflictCollector, sha256Hex, shouldAnalyzeFile, sidecarDraftPath, hasNudoDirectives as sourceHasNudoDirectives, stripGeneratedCaseDirectives, summarizeNodeEnv, topoSortDirty, tryBPathCall, tryBPathCallFull, tryRunBPath, unifiedDiff, writeInterfaceDraft };
|
package/dist/index.js
CHANGED
|
@@ -778,6 +778,49 @@ function lookupHarvested(h, moduleName, exportName) {
|
|
|
778
778
|
}
|
|
779
779
|
|
|
780
780
|
// src/harvest-auto.ts
|
|
781
|
+
var NODE_BUILTINS = /* @__PURE__ */ new Set([
|
|
782
|
+
"assert",
|
|
783
|
+
"async_hooks",
|
|
784
|
+
"buffer",
|
|
785
|
+
"child_process",
|
|
786
|
+
"cluster",
|
|
787
|
+
"console",
|
|
788
|
+
"constants",
|
|
789
|
+
"crypto",
|
|
790
|
+
"dgram",
|
|
791
|
+
"diagnostics_channel",
|
|
792
|
+
"dns",
|
|
793
|
+
"domain",
|
|
794
|
+
"events",
|
|
795
|
+
"fs",
|
|
796
|
+
"http",
|
|
797
|
+
"http2",
|
|
798
|
+
"https",
|
|
799
|
+
"inspector",
|
|
800
|
+
"module",
|
|
801
|
+
"net",
|
|
802
|
+
"os",
|
|
803
|
+
"path",
|
|
804
|
+
"perf_hooks",
|
|
805
|
+
"process",
|
|
806
|
+
"punycode",
|
|
807
|
+
"querystring",
|
|
808
|
+
"readline",
|
|
809
|
+
"repl",
|
|
810
|
+
"stream",
|
|
811
|
+
"string_decoder",
|
|
812
|
+
"timers",
|
|
813
|
+
"tls",
|
|
814
|
+
"trace_events",
|
|
815
|
+
"tty",
|
|
816
|
+
"url",
|
|
817
|
+
"util",
|
|
818
|
+
"v8",
|
|
819
|
+
"vm",
|
|
820
|
+
"wasi",
|
|
821
|
+
"worker_threads",
|
|
822
|
+
"zlib"
|
|
823
|
+
]);
|
|
781
824
|
function barePackageName(spec) {
|
|
782
825
|
if (!spec) return void 0;
|
|
783
826
|
if (spec.startsWith(".") || spec.startsWith("/") || spec.startsWith("node:")) return void 0;
|
|
@@ -785,7 +828,9 @@ function barePackageName(spec) {
|
|
|
785
828
|
if (spec.startsWith("@")) {
|
|
786
829
|
return parts.length >= 2 ? `${parts[0]}/${parts[1]}` : void 0;
|
|
787
830
|
}
|
|
788
|
-
|
|
831
|
+
const name = parts[0];
|
|
832
|
+
if (NODE_BUILTINS.has(name)) return void 0;
|
|
833
|
+
return name;
|
|
789
834
|
}
|
|
790
835
|
function collectBarePackages(source) {
|
|
791
836
|
try {
|
|
@@ -1314,6 +1359,50 @@ function collectEnvModules(envNames) {
|
|
|
1314
1359
|
}
|
|
1315
1360
|
return out;
|
|
1316
1361
|
}
|
|
1362
|
+
var envHarvestConflictCollector = null;
|
|
1363
|
+
function setEnvHarvestConflictCollector(collector) {
|
|
1364
|
+
const prev = envHarvestConflictCollector;
|
|
1365
|
+
envHarvestConflictCollector = collector;
|
|
1366
|
+
return prev;
|
|
1367
|
+
}
|
|
1368
|
+
function getEnvHarvestConflictCollector() {
|
|
1369
|
+
return envHarvestConflictCollector;
|
|
1370
|
+
}
|
|
1371
|
+
function mergeHarvestUnderEnv(harvestModules, envModules, opts) {
|
|
1372
|
+
const out = {};
|
|
1373
|
+
for (const [mod, exports] of Object.entries(harvestModules)) {
|
|
1374
|
+
const named = { ...exports.named };
|
|
1375
|
+
out[mod] = exports.default !== void 0 ? { named, default: exports.default } : { named };
|
|
1376
|
+
}
|
|
1377
|
+
const notify = opts?.onConflict ?? envHarvestConflictCollector;
|
|
1378
|
+
for (const [mod, envExports] of Object.entries(envModules)) {
|
|
1379
|
+
const existing = out[mod];
|
|
1380
|
+
if (!existing) {
|
|
1381
|
+
const named2 = { ...envExports.named };
|
|
1382
|
+
out[mod] = envExports.default !== void 0 ? { named: named2, default: envExports.default } : { named: named2 };
|
|
1383
|
+
continue;
|
|
1384
|
+
}
|
|
1385
|
+
const overwritten = [];
|
|
1386
|
+
for (const key of Object.keys(envExports.named)) {
|
|
1387
|
+
if (existing.named[key] !== void 0) overwritten.push(key);
|
|
1388
|
+
}
|
|
1389
|
+
const defaultOverwritten = envExports.default !== void 0 && existing.default !== void 0;
|
|
1390
|
+
if (notify && (overwritten.length > 0 || defaultOverwritten)) {
|
|
1391
|
+
try {
|
|
1392
|
+
notify({
|
|
1393
|
+
module: mod,
|
|
1394
|
+
exports: overwritten,
|
|
1395
|
+
defaultOverwritten
|
|
1396
|
+
});
|
|
1397
|
+
} catch {
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
const named = { ...existing.named, ...envExports.named };
|
|
1401
|
+
const defaultAbs = envExports.default !== void 0 ? envExports.default : existing.default;
|
|
1402
|
+
out[mod] = defaultAbs !== void 0 ? { named, default: defaultAbs } : { named };
|
|
1403
|
+
}
|
|
1404
|
+
return out;
|
|
1405
|
+
}
|
|
1317
1406
|
function collectBPathReplacements(source) {
|
|
1318
1407
|
const targets = [];
|
|
1319
1408
|
const values = {};
|
|
@@ -1428,7 +1517,7 @@ function tryRunBPath(source, filePath, opts = {}) {
|
|
|
1428
1517
|
try {
|
|
1429
1518
|
const { modules: graphMods, issues } = evalAbsModuleGraph(source, filePath);
|
|
1430
1519
|
const envMods = collectEnvModules(opts.envNames ?? []);
|
|
1431
|
-
const modules =
|
|
1520
|
+
const modules = mergeHarvestUnderEnv(graphMods, envMods);
|
|
1432
1521
|
const { targets, values, asTargets, asValues } = collectBPathReplacements(source);
|
|
1433
1522
|
const envGlobals = {
|
|
1434
1523
|
...collectEnvGlobals(opts.envNames ?? []),
|
|
@@ -1813,7 +1902,8 @@ function validateMockDirectives(directives, diagnostics) {
|
|
|
1813
1902
|
for (const d of directives) {
|
|
1814
1903
|
if (d.kind !== "mock" || !d.expression) continue;
|
|
1815
1904
|
const expr = d.expression.trim();
|
|
1816
|
-
|
|
1905
|
+
const isTypeExpr = /^(number|string|boolean|any|array|shape|lit|union|fn|partial|pick|omit|record|required|readonly|nonNullable|and)\s*\(/.test(expr) || expr === "unknown" || expr === "any" || expr === "never" || expr === "true" || expr === "false" || expr === "null" || expr === "undefined" || /^-?\d+(\.\d+)?$/.test(expr) || (expr.startsWith('"') && expr.endsWith('"') || expr.startsWith("'") && expr.endsWith("'")) || expr.startsWith("{") || expr.startsWith("[");
|
|
1906
|
+
if (expr.includes("(") && expr.includes(")") && !isTypeExpr && !expr.includes("=>")) {
|
|
1817
1907
|
diagnostics.push({
|
|
1818
1908
|
range: { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } },
|
|
1819
1909
|
severity: "warning",
|
|
@@ -1821,7 +1911,8 @@ function validateMockDirectives(directives, diagnostics) {
|
|
|
1821
1911
|
code: "nudo:mock-invalid",
|
|
1822
1912
|
suggestions: [
|
|
1823
1913
|
"Supported formats: stub(), stub().returns(value), spy(), mock()",
|
|
1824
|
-
"Arrow functions: (args) => expression or (args) => { statements; return value; }"
|
|
1914
|
+
"Arrow functions: (args) => expression or (args) => { statements; return value; }",
|
|
1915
|
+
"Type expressions: number(), string(), shape({...}), union(...), or concrete literals"
|
|
1825
1916
|
]
|
|
1826
1917
|
});
|
|
1827
1918
|
}
|
|
@@ -2352,6 +2443,24 @@ function analyzeFileUncached(filePath, source, activeCases, externalCallRecords,
|
|
|
2352
2443
|
() => analyzeFileUncachedInner(filePath, source, activeCases, externalCallRecords, loadModule)
|
|
2353
2444
|
);
|
|
2354
2445
|
}
|
|
2446
|
+
function findModuleImportLoc(source, module) {
|
|
2447
|
+
const bare = module.startsWith("node:") ? module.slice("node:".length) : module;
|
|
2448
|
+
const alts = [.../* @__PURE__ */ new Set([module, bare, `node:${bare}`])];
|
|
2449
|
+
const lines = source.split(/\r?\n/);
|
|
2450
|
+
for (let i = 0; i < lines.length; i++) {
|
|
2451
|
+
const line = lines[i];
|
|
2452
|
+
for (const alt of alts) {
|
|
2453
|
+
const re = new RegExp(
|
|
2454
|
+
`["'\`]${alt.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}["'\`]`
|
|
2455
|
+
);
|
|
2456
|
+
const m = re.exec(line);
|
|
2457
|
+
if (m && m.index !== void 0) {
|
|
2458
|
+
return { line: i, column: m.index, length: m[0].length };
|
|
2459
|
+
}
|
|
2460
|
+
}
|
|
2461
|
+
}
|
|
2462
|
+
return null;
|
|
2463
|
+
}
|
|
2355
2464
|
function analyzeFileUncachedInner(filePath, source, activeCases, externalCallRecords, loadModule) {
|
|
2356
2465
|
const ast = parse5(source);
|
|
2357
2466
|
const functions = extractDirectives(ast);
|
|
@@ -2360,6 +2469,18 @@ function analyzeFileUncachedInner(filePath, source, activeCases, externalCallRec
|
|
|
2360
2469
|
const nodeAbsMap = /* @__PURE__ */ new Map();
|
|
2361
2470
|
const functionResults = [];
|
|
2362
2471
|
const caseHints = [];
|
|
2472
|
+
const envHarvestConflicts = [];
|
|
2473
|
+
const prevHarvestConflictCollector = setEnvHarvestConflictCollector((c) => {
|
|
2474
|
+
if (!envHarvestConflicts.some((x) => x.module === c.module)) {
|
|
2475
|
+
envHarvestConflicts.push(c);
|
|
2476
|
+
} else {
|
|
2477
|
+
const prev = envHarvestConflicts.find((x) => x.module === c.module);
|
|
2478
|
+
for (const e of c.exports) {
|
|
2479
|
+
if (!prev.exports.includes(e)) prev.exports.push(e);
|
|
2480
|
+
}
|
|
2481
|
+
prev.defaultOverwritten ||= c.defaultOverwritten;
|
|
2482
|
+
}
|
|
2483
|
+
});
|
|
2363
2484
|
const fileDirectives = extractFileDirectives(ast);
|
|
2364
2485
|
const fileEnvNames = fileDirectives.filter((d) => d.kind === "env").flatMap((d) => d.envs);
|
|
2365
2486
|
const projectConfig = findProjectConfig(dirname7(filePath));
|
|
@@ -2449,7 +2570,7 @@ function analyzeFileUncachedInner(filePath, source, activeCases, externalCallRec
|
|
|
2449
2570
|
seedFns: seeds.seedFns,
|
|
2450
2571
|
...loadModule ? { loadModule } : {}
|
|
2451
2572
|
});
|
|
2452
|
-
absGraphModules =
|
|
2573
|
+
absGraphModules = mergeHarvestUnderEnv(g.modules, collectEnvModules(envNames));
|
|
2453
2574
|
pushBModuleIssues(g.issues);
|
|
2454
2575
|
} catch {
|
|
2455
2576
|
}
|
|
@@ -2772,7 +2893,7 @@ function analyzeFileUncachedInner(filePath, source, activeCases, externalCallRec
|
|
|
2772
2893
|
diagnostics.push({
|
|
2773
2894
|
range: { start: { line: directive.commentLine, column: 0 }, end: { line: directive.commentLine, column: 999 } },
|
|
2774
2895
|
severity: "error",
|
|
2775
|
-
message: `
|
|
2896
|
+
message: `debug "${directive.name}": expected ${formatShape2(directive.expected)}, got ${formatShape2(caseAbs)}. The inferred return type does not match the expected type declared in the @nudo:case witness`,
|
|
2776
2897
|
code: "nudo:case-expected"
|
|
2777
2898
|
});
|
|
2778
2899
|
}
|
|
@@ -3055,6 +3176,24 @@ function analyzeFileUncachedInner(filePath, source, activeCases, externalCallRec
|
|
|
3055
3176
|
filePath,
|
|
3056
3177
|
analysisConfig(projectConfig?.config).callSiteBudget
|
|
3057
3178
|
);
|
|
3179
|
+
setEnvHarvestConflictCollector(prevHarvestConflictCollector);
|
|
3180
|
+
for (const c of envHarvestConflicts) {
|
|
3181
|
+
const parts = [];
|
|
3182
|
+
if (c.exports.length > 0) parts.push(`export(s) ${c.exports.join(", ")}`);
|
|
3183
|
+
if (c.defaultOverwritten) parts.push("default");
|
|
3184
|
+
const detail = parts.length > 0 ? ` \u2014 ${parts.join("; ")}` : "";
|
|
3185
|
+
const loc = findModuleImportLoc(source, c.module);
|
|
3186
|
+
const range = loc ? {
|
|
3187
|
+
start: { line: loc.line, column: loc.column },
|
|
3188
|
+
end: { line: loc.line, column: loc.column + loc.length }
|
|
3189
|
+
} : { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } };
|
|
3190
|
+
diagnostics.push({
|
|
3191
|
+
range,
|
|
3192
|
+
severity: "warning",
|
|
3193
|
+
message: `handwritten @nudo:env wins over harvest on module "${c.module}"${detail}; harvest only fills missing slots (B8). code=nudo:env-harvest-conflict`,
|
|
3194
|
+
code: "nudo:env-harvest-conflict"
|
|
3195
|
+
});
|
|
3196
|
+
}
|
|
3058
3197
|
return {
|
|
3059
3198
|
functions: functionResults,
|
|
3060
3199
|
diagnostics,
|
|
@@ -3167,7 +3306,7 @@ function collectAbsCallRecords(source, seeds, filePath, precomputedModules, envN
|
|
|
3167
3306
|
}
|
|
3168
3307
|
}
|
|
3169
3308
|
if (envNames.length > 0) {
|
|
3170
|
-
modules =
|
|
3309
|
+
modules = mergeHarvestUnderEnv(modules ?? {}, collectEnvModules(envNames));
|
|
3171
3310
|
}
|
|
3172
3311
|
try {
|
|
3173
3312
|
importLocals = buildAbsImportLocalMap(source, filePath);
|
|
@@ -4293,19 +4432,97 @@ function collectLoadDepContents(filePath, source, loadModule) {
|
|
|
4293
4432
|
}
|
|
4294
4433
|
|
|
4295
4434
|
// src/harvest-node.ts
|
|
4296
|
-
import { existsSync as existsSync8 } from "fs";
|
|
4435
|
+
import { existsSync as existsSync8, statSync as statSync6 } from "fs";
|
|
4436
|
+
import { join as join6 } from "path";
|
|
4297
4437
|
import { harvestDts as harvestDts2 } from "@nudojs/harvester";
|
|
4298
|
-
|
|
4438
|
+
var HARVEST_NODE_DEFAULT_MAX_FILES = 12;
|
|
4439
|
+
var HARVEST_NODE_DEFAULT_MAX_MS = 2500;
|
|
4440
|
+
var nodeHarvestCache = /* @__PURE__ */ new Map();
|
|
4441
|
+
function notFoundCacheKey(fromDir) {
|
|
4442
|
+
return `not-found|@types/node|${fromDir ?? ""}`;
|
|
4443
|
+
}
|
|
4444
|
+
function nodeHarvestCacheKey(root, maxFiles, maxMs) {
|
|
4445
|
+
let sig = "nostat";
|
|
4446
|
+
try {
|
|
4447
|
+
const st = statSync6(join6(root, "package.json"));
|
|
4448
|
+
sig = `${st.size}:${Math.floor(st.mtimeMs)}`;
|
|
4449
|
+
} catch {
|
|
4450
|
+
}
|
|
4451
|
+
return `${root}|${maxFiles}|${maxMs}|${sig}`;
|
|
4452
|
+
}
|
|
4453
|
+
function clearNodeHarvestCache() {
|
|
4454
|
+
nodeHarvestCache.clear();
|
|
4455
|
+
}
|
|
4456
|
+
function getNodeHarvestCacheSize() {
|
|
4457
|
+
return nodeHarvestCache.size;
|
|
4458
|
+
}
|
|
4459
|
+
function isHarvestNodeDisabled(env = process.env) {
|
|
4460
|
+
return env.NUDO_HARVEST_NODE === "off";
|
|
4461
|
+
}
|
|
4462
|
+
function harvestNodeTypes(fromDir, maxFiles = HARVEST_NODE_DEFAULT_MAX_FILES, maxMs = HARVEST_NODE_DEFAULT_MAX_MS) {
|
|
4463
|
+
if (isHarvestNodeDisabled()) {
|
|
4464
|
+
return {
|
|
4465
|
+
ok: false,
|
|
4466
|
+
error: "harvest disabled via NUDO_HARVEST_NODE=off",
|
|
4467
|
+
reason: "disabled"
|
|
4468
|
+
};
|
|
4469
|
+
}
|
|
4299
4470
|
const root = resolvePackageRoot("@types/node", fromDir) ?? resolvePackageRoot("node", fromDir);
|
|
4300
4471
|
if (!root || !existsSync8(root)) {
|
|
4301
|
-
|
|
4472
|
+
const missKey = notFoundCacheKey(fromDir);
|
|
4473
|
+
const miss = nodeHarvestCache.get(missKey);
|
|
4474
|
+
if (miss && !miss.ok && miss.reason === "not-found") {
|
|
4475
|
+
return { ...miss, cached: true };
|
|
4476
|
+
}
|
|
4477
|
+
const result = {
|
|
4478
|
+
ok: false,
|
|
4479
|
+
error: "@types/node not found",
|
|
4480
|
+
reason: "not-found"
|
|
4481
|
+
};
|
|
4482
|
+
nodeHarvestCache.set(missKey, result);
|
|
4483
|
+
return result;
|
|
4484
|
+
}
|
|
4485
|
+
const key = nodeHarvestCacheKey(root, maxFiles, maxMs);
|
|
4486
|
+
const hit = nodeHarvestCache.get(key);
|
|
4487
|
+
if (hit) {
|
|
4488
|
+
return hit.ok ? { ...hit, cached: true } : { ...hit, cached: true };
|
|
4302
4489
|
}
|
|
4303
4490
|
const dts = collectDtsFiles(root, maxFiles);
|
|
4304
4491
|
if (dts.length === 0) {
|
|
4305
|
-
|
|
4492
|
+
const result = {
|
|
4493
|
+
ok: false,
|
|
4494
|
+
error: `no .d.ts under ${root}`,
|
|
4495
|
+
reason: "no-dts"
|
|
4496
|
+
};
|
|
4497
|
+
nodeHarvestCache.set(key, result);
|
|
4498
|
+
return result;
|
|
4499
|
+
}
|
|
4500
|
+
try {
|
|
4501
|
+
const env = harvestDts2(dts, { maxMs });
|
|
4502
|
+
const stats = {
|
|
4503
|
+
files: env.stats.files,
|
|
4504
|
+
symbols: env.stats.symbols,
|
|
4505
|
+
skipped: env.stats.skipped
|
|
4506
|
+
};
|
|
4507
|
+
const result = {
|
|
4508
|
+
ok: true,
|
|
4509
|
+
env,
|
|
4510
|
+
root,
|
|
4511
|
+
files: stats.files,
|
|
4512
|
+
stats,
|
|
4513
|
+
cached: false
|
|
4514
|
+
};
|
|
4515
|
+
nodeHarvestCache.set(key, result);
|
|
4516
|
+
return result;
|
|
4517
|
+
} catch (e) {
|
|
4518
|
+
const result = {
|
|
4519
|
+
ok: false,
|
|
4520
|
+
error: `harvest failed: ${e.message}`,
|
|
4521
|
+
reason: "failed"
|
|
4522
|
+
};
|
|
4523
|
+
nodeHarvestCache.set(key, result);
|
|
4524
|
+
return result;
|
|
4306
4525
|
}
|
|
4307
|
-
const env = harvestDts2(dts, { maxMs });
|
|
4308
|
-
return { ok: true, env, root, files: env.stats.files };
|
|
4309
4526
|
}
|
|
4310
4527
|
function summarizeNodeEnv(env) {
|
|
4311
4528
|
return {
|
|
@@ -5078,15 +5295,15 @@ function serializeCaseArg(a) {
|
|
|
5078
5295
|
}
|
|
5079
5296
|
switch (s.k) {
|
|
5080
5297
|
case "prim":
|
|
5081
|
-
if (s.type === "number"
|
|
5082
|
-
|
|
5083
|
-
|
|
5298
|
+
if (s.type === "number") return "number()";
|
|
5299
|
+
if (s.type === "string") return "string()";
|
|
5300
|
+
if (s.type === "boolean") return "boolean()";
|
|
5084
5301
|
return null;
|
|
5085
5302
|
case "unknown":
|
|
5086
5303
|
case "any":
|
|
5087
|
-
return "
|
|
5304
|
+
return "any()";
|
|
5088
5305
|
case "never":
|
|
5089
|
-
return "
|
|
5306
|
+
return "never";
|
|
5090
5307
|
case "sum": {
|
|
5091
5308
|
const parts = [];
|
|
5092
5309
|
for (const member of s.members) {
|
|
@@ -5094,11 +5311,11 @@ function serializeCaseArg(a) {
|
|
|
5094
5311
|
if (ser === null) return null;
|
|
5095
5312
|
parts.push(ser);
|
|
5096
5313
|
}
|
|
5097
|
-
return `
|
|
5314
|
+
return `union(${parts.join(", ")})`;
|
|
5098
5315
|
}
|
|
5099
5316
|
case "arr": {
|
|
5100
5317
|
const el = serializeCaseArg(s.element);
|
|
5101
|
-
return el === null ? null : `
|
|
5318
|
+
return el === null ? null : `array(${el})`;
|
|
5102
5319
|
}
|
|
5103
5320
|
case "tuple": {
|
|
5104
5321
|
const parts = [];
|
|
@@ -5916,7 +6133,7 @@ function joinSections(base, sectionTexts) {
|
|
|
5916
6133
|
|
|
5917
6134
|
// src/interface-draft.ts
|
|
5918
6135
|
import { existsSync as existsSync11, readFileSync as readFileSync9, writeFileSync as writeFileSync3, realpathSync as realpathSync2 } from "fs";
|
|
5919
|
-
import { basename as basename3, dirname as dirname13, isAbsolute as isAbsolute2, join as
|
|
6136
|
+
import { basename as basename3, dirname as dirname13, isAbsolute as isAbsolute2, join as join7, relative as relative3 } from "path";
|
|
5920
6137
|
import {
|
|
5921
6138
|
effectiveInterface as effectiveInterface2,
|
|
5922
6139
|
formatConstraint as formatConstraint3,
|
|
@@ -6444,7 +6661,7 @@ function safeRealpath(p) {
|
|
|
6444
6661
|
return realpathSync2(p);
|
|
6445
6662
|
} catch {
|
|
6446
6663
|
try {
|
|
6447
|
-
return
|
|
6664
|
+
return join7(realpathSync2(dirname13(p)), basename3(p));
|
|
6448
6665
|
} catch {
|
|
6449
6666
|
return p;
|
|
6450
6667
|
}
|
|
@@ -7454,6 +7671,8 @@ export {
|
|
|
7454
7671
|
ANALYSIS_ABI,
|
|
7455
7672
|
DEFAULT_ANALYSIS_MODE,
|
|
7456
7673
|
DiskCache,
|
|
7674
|
+
HARVEST_NODE_DEFAULT_MAX_FILES,
|
|
7675
|
+
HARVEST_NODE_DEFAULT_MAX_MS,
|
|
7457
7676
|
SEMANTIC_TOKEN_MODIFIERS,
|
|
7458
7677
|
SEMANTIC_TOKEN_TYPES,
|
|
7459
7678
|
absToTSType,
|
|
@@ -7478,6 +7697,7 @@ export {
|
|
|
7478
7697
|
clearEnvPathDeps,
|
|
7479
7698
|
clearFnAnalysisCache,
|
|
7480
7699
|
clearHarvestCache,
|
|
7700
|
+
clearNodeHarvestCache,
|
|
7481
7701
|
clearPathEnvCaches,
|
|
7482
7702
|
collectAbsBindingsFromGraph,
|
|
7483
7703
|
collectAbsInlays,
|
|
@@ -7530,7 +7750,9 @@ export {
|
|
|
7530
7750
|
getAnalysisSession,
|
|
7531
7751
|
getCasesForFile,
|
|
7532
7752
|
getCompletionsAtPosition,
|
|
7753
|
+
getEnvHarvestConflictCollector,
|
|
7533
7754
|
getHoverAtPosition,
|
|
7755
|
+
getNodeHarvestCacheSize,
|
|
7534
7756
|
getTypeAtPosition,
|
|
7535
7757
|
getTypeAtPositionAsync,
|
|
7536
7758
|
harvestNodeTypes,
|
|
@@ -7546,12 +7768,14 @@ export {
|
|
|
7546
7768
|
isBPathCapable,
|
|
7547
7769
|
isDraftableEntry,
|
|
7548
7770
|
isEnvTemplatePath,
|
|
7771
|
+
isHarvestNodeDisabled,
|
|
7549
7772
|
isNudoTargetPath,
|
|
7550
7773
|
isProjectConfigPath,
|
|
7551
7774
|
isSidecarPath,
|
|
7552
7775
|
isWatchRelevantPath,
|
|
7553
7776
|
lookupHarvested,
|
|
7554
7777
|
matchesEmitAllowlist,
|
|
7778
|
+
mergeHarvestUnderEnv,
|
|
7555
7779
|
mockDirectivesToAbsSeeds,
|
|
7556
7780
|
noteEnvPathDeps,
|
|
7557
7781
|
packageHarvestToAbsModules,
|
|
@@ -7561,6 +7785,7 @@ export {
|
|
|
7561
7785
|
serializeCaseArg,
|
|
7562
7786
|
serializeInferJson,
|
|
7563
7787
|
setAnalysisSession,
|
|
7788
|
+
setEnvHarvestConflictCollector,
|
|
7564
7789
|
sha256Hex,
|
|
7565
7790
|
shouldAnalyzeFile,
|
|
7566
7791
|
sidecarDraftPath,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nudojs/service",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0",
|
|
4
4
|
"engines": {
|
|
5
5
|
"node": ">=20"
|
|
6
6
|
},
|
|
@@ -45,10 +45,10 @@
|
|
|
45
45
|
"dependencies": {
|
|
46
46
|
"@babel/traverse": "^7.29.0",
|
|
47
47
|
"@babel/types": "^7.29.0",
|
|
48
|
-
"@nudojs/core": "2.
|
|
49
|
-
"@nudojs/parser": "0.
|
|
50
|
-
"@nudojs/env": "0.
|
|
51
|
-
"@nudojs/harvester": "0.2.
|
|
48
|
+
"@nudojs/core": "2.1.0",
|
|
49
|
+
"@nudojs/parser": "1.0.0",
|
|
50
|
+
"@nudojs/env": "0.4.1",
|
|
51
|
+
"@nudojs/harvester": "0.2.7"
|
|
52
52
|
},
|
|
53
53
|
"scripts": {
|
|
54
54
|
"build": "tsup src/index.ts src/evaluator/evaluator-api.ts --format esm --dts"
|