From 38ce6279cd9785acf1d903ae0ba16f738dbba9e6 Mon Sep 17 00:00:00 2001 From: Vladimir Matveev Date: Mon, 29 Aug 2016 22:21:58 -0700 Subject: [PATCH] add tsserver specific resolution pass that will load typings from cache locations if auto discovery is enabled --- src/compiler/core.ts | 9 +- src/compiler/diagnosticMessages.json | 4 + src/compiler/program.ts | 2 +- .../unittests/tsserverProjectSystem.ts | 127 +++++++++++++++++- src/server/editorServices.ts | 7 +- src/server/lsHost.ts | 29 +++- src/server/server.ts | 50 +++++-- src/server/types.d.ts | 1 + src/server/typingsCache.ts | 4 +- .../typingsInstaller/nodeTypingsInstaller.ts | 52 +++---- 10 files changed, 223 insertions(+), 62 deletions(-) diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 479c04bfeff..50900c6761b 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -1571,7 +1571,8 @@ namespace ts { return true; } - function createResolvedModule(resolvedFileName: string, isExternalLibraryImport: boolean, failedLookupLocations: string[]): ResolvedModuleWithFailedLookupLocations { + /* @internal */ + export function createResolvedModule(resolvedFileName: string, isExternalLibraryImport: boolean, failedLookupLocations: string[]): ResolvedModuleWithFailedLookupLocations { return { resolvedModule: resolvedFileName ? { resolvedFileName, isExternalLibraryImport } : undefined, failedLookupLocations }; } @@ -1989,7 +1990,7 @@ namespace ts { if (traceEnabled) { trace(host, Diagnostics.Loading_module_0_from_node_modules_folder, moduleName); } - resolvedFileName = loadModuleFromNodeModules(moduleName, containingDirectory, failedLookupLocations, state); + resolvedFileName = loadModuleFromNodeModules(moduleName, containingDirectory, failedLookupLocations, state, /*checkOneLevel*/ false); isExternalLibraryImport = resolvedFileName !== undefined; } else { @@ -2138,7 +2139,7 @@ namespace ts { } /* @internal */ - export function loadModuleFromNodeModules(moduleName: string, directory: string, failedLookupLocations: string[], state: ModuleResolutionState): string { + export function loadModuleFromNodeModules(moduleName: string, directory: string, failedLookupLocations: string[], state: ModuleResolutionState, checkOneLevel: boolean): string { directory = normalizeSlashes(directory); while (true) { const baseName = getBaseFileName(directory); @@ -2159,7 +2160,7 @@ namespace ts { } const parentPath = getDirectoryPath(directory); - if (parentPath === directory) { + if (parentPath === directory || checkOneLevel) { break; } diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index b8978b32571..fefe1f982ac 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -2840,6 +2840,10 @@ "category": "Error", "code": 6138 }, + "Auto discovery for typings is enabled in project '{0}'. Running extra resolution pass for module '{1}' using cache location '{2}'.": { + "category": "Error", + "code": 6139 + }, "Variable '{0}' implicitly has an '{1}' type.": { "category": "Error", "code": 7005 diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 31d87f1a6c0..9fadc24e1a4 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -174,7 +174,7 @@ namespace ts { if (traceEnabled) { trace(host, Diagnostics.Looking_up_in_node_modules_folder_initial_location_0, initialLocationForSecondaryLookup); } - resolvedFile = loadModuleFromNodeModules(typeReferenceDirectiveName, initialLocationForSecondaryLookup, failedLookupLocations, moduleResolutionState); + resolvedFile = loadModuleFromNodeModules(typeReferenceDirectiveName, initialLocationForSecondaryLookup, failedLookupLocations, moduleResolutionState, /*checkOneLevel*/ false); if (traceEnabled) { if (resolvedFile) { trace(host, Diagnostics.Type_reference_directive_0_was_successfully_resolved_to_1_primary_Colon_2, typeReferenceDirectiveName, resolvedFile, false); diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index 46f84a39af3..12318ac2a7e 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -30,8 +30,8 @@ namespace ts { class TestTypingsInstaller extends server.typingsInstaller.TypingsInstaller implements server.ITypingsInstaller { protected projectService: server.ProjectService; - constructor(readonly cachePath: string, readonly installTypingHost: server.ServerHost) { - super(cachePath, ""); + constructor(readonly globalTypingsCacheLocation: string, readonly installTypingHost: server.ServerHost) { + super(globalTypingsCacheLocation, ""); this.init(); } @@ -75,7 +75,7 @@ namespace ts { } enqueueInstallTypingsRequest(project: server.Project, typingOptions: TypingOptions) { - const request = server.createInstallTypingsRequest(project, typingOptions, this.cachePath); + const request = server.createInstallTypingsRequest(project, typingOptions, this.globalTypingsCacheLocation); this.install(request); } } @@ -2030,7 +2030,7 @@ namespace ts { const p = projectService.configuredProjects[0]; checkProjectActualFiles(p, [file1.path]); - assert(host.fileExists(combinePaths(installer.cachePath, "tsd.json"))); + assert(host.fileExists(combinePaths(installer.globalTypingsCacheLocation, "tsd.json"))); installer.runPostInstallActions(t => { assert.deepEqual(t, ["jquery"]); @@ -2070,7 +2070,7 @@ namespace ts { const p = projectService.inferredProjects[0]; checkProjectActualFiles(p, [file1.path]); - assert(host.fileExists(combinePaths(installer.cachePath, "tsd.json"))); + assert(host.fileExists(combinePaths(installer.globalTypingsCacheLocation, "tsd.json"))); installer.runPostInstallActions(t => { assert.deepEqual(t, ["jquery"]); @@ -2398,4 +2398,121 @@ namespace ts { assert.isTrue(typingOptions.enableAutoDiscovery, "Typing autodiscovery should be enabled"); }); }); + + describe("extra resolution pass in lshost", () => { + it("can load typings that are proper modules", () => { + const file1 = { + path: "/a/b/app.js", + content: `var x = require("lib")` + }; + const lib = { + path: "/a/cache/node_modules/@types/lib/index.d.ts", + content: "export let x = 1" + }; + const host: TestServerHost & ModuleResolutionHost = createServerHost([file1, lib]); + const resolutionTrace: string[] = []; + host.trace = resolutionTrace.push.bind(resolutionTrace); + const projectService = createProjectService(host, { typingsInstaller: new TestTypingsInstaller("/a/cache", host) }); + + projectService.setCompilerOptionsForInferredProjects({ traceResolution: true, allowJs: true }); + projectService.openClientFile(file1.path); + projectService.checkNumberOfProjects({ inferredProjects: 1 }); + const proj = projectService.inferredProjects[0]; + + assert.deepEqual(resolutionTrace, [ + "======== Resolving module 'lib' from '/a/b/app.js'. ========", + "Module resolution kind is not specified, using 'NodeJs'.", + "Loading module 'lib' from 'node_modules' folder.", + "File '/a/b/node_modules/lib.ts' does not exist.", + "File '/a/b/node_modules/lib.tsx' does not exist.", + "File '/a/b/node_modules/lib.d.ts' does not exist.", + "File '/a/b/node_modules/lib.js' does not exist.", + "File '/a/b/node_modules/lib.jsx' does not exist.", + "File '/a/b/node_modules/lib/package.json' does not exist.", + "File '/a/b/node_modules/lib/index.ts' does not exist.", + "File '/a/b/node_modules/lib/index.tsx' does not exist.", + "File '/a/b/node_modules/lib/index.d.ts' does not exist.", + "File '/a/b/node_modules/lib/index.js' does not exist.", + "File '/a/b/node_modules/lib/index.jsx' does not exist.", + "File '/a/b/node_modules/@types/lib.ts' does not exist.", + "File '/a/b/node_modules/@types/lib.tsx' does not exist.", + "File '/a/b/node_modules/@types/lib.d.ts' does not exist.", + "File '/a/b/node_modules/@types/lib.js' does not exist.", + "File '/a/b/node_modules/@types/lib.jsx' does not exist.", + "File '/a/b/node_modules/@types/lib/package.json' does not exist.", + "File '/a/b/node_modules/@types/lib/index.ts' does not exist.", + "File '/a/b/node_modules/@types/lib/index.tsx' does not exist.", + "File '/a/b/node_modules/@types/lib/index.d.ts' does not exist.", + "File '/a/b/node_modules/@types/lib/index.js' does not exist.", + "File '/a/b/node_modules/@types/lib/index.jsx' does not exist.", + "File '/a/node_modules/lib.ts' does not exist.", + "File '/a/node_modules/lib.tsx' does not exist.", + "File '/a/node_modules/lib.d.ts' does not exist.", + "File '/a/node_modules/lib.js' does not exist.", + "File '/a/node_modules/lib.jsx' does not exist.", + "File '/a/node_modules/lib/package.json' does not exist.", + "File '/a/node_modules/lib/index.ts' does not exist.", + "File '/a/node_modules/lib/index.tsx' does not exist.", + "File '/a/node_modules/lib/index.d.ts' does not exist.", + "File '/a/node_modules/lib/index.js' does not exist.", + "File '/a/node_modules/lib/index.jsx' does not exist.", + "File '/a/node_modules/@types/lib.ts' does not exist.", + "File '/a/node_modules/@types/lib.tsx' does not exist.", + "File '/a/node_modules/@types/lib.d.ts' does not exist.", + "File '/a/node_modules/@types/lib.js' does not exist.", + "File '/a/node_modules/@types/lib.jsx' does not exist.", + "File '/a/node_modules/@types/lib/package.json' does not exist.", + "File '/a/node_modules/@types/lib/index.ts' does not exist.", + "File '/a/node_modules/@types/lib/index.tsx' does not exist.", + "File '/a/node_modules/@types/lib/index.d.ts' does not exist.", + "File '/a/node_modules/@types/lib/index.js' does not exist.", + "File '/a/node_modules/@types/lib/index.jsx' does not exist.", + "File '/node_modules/lib.ts' does not exist.", + "File '/node_modules/lib.tsx' does not exist.", + "File '/node_modules/lib.d.ts' does not exist.", + "File '/node_modules/lib.js' does not exist.", + "File '/node_modules/lib.jsx' does not exist.", + "File '/node_modules/lib/package.json' does not exist.", + "File '/node_modules/lib/index.ts' does not exist.", + "File '/node_modules/lib/index.tsx' does not exist.", + "File '/node_modules/lib/index.d.ts' does not exist.", + "File '/node_modules/lib/index.js' does not exist.", + "File '/node_modules/lib/index.jsx' does not exist.", + "File '/node_modules/@types/lib.ts' does not exist.", + "File '/node_modules/@types/lib.tsx' does not exist.", + "File '/node_modules/@types/lib.d.ts' does not exist.", + "File '/node_modules/@types/lib.js' does not exist.", + "File '/node_modules/@types/lib.jsx' does not exist.", + "File '/node_modules/@types/lib/package.json' does not exist.", + "File '/node_modules/@types/lib/index.ts' does not exist.", + "File '/node_modules/@types/lib/index.tsx' does not exist.", + "File '/node_modules/@types/lib/index.d.ts' does not exist.", + "File '/node_modules/@types/lib/index.js' does not exist.", + "File '/node_modules/@types/lib/index.jsx' does not exist.", + "======== Module name 'lib' was not resolved. ========", + `Auto discovery for typings is enabled in project '${proj.getProjectName()}'. Running extra resolution pass for module 'lib' using cache location '/a/cache'.`, + "File '/a/cache/node_modules/lib.ts' does not exist.", + "File '/a/cache/node_modules/lib.tsx' does not exist.", + "File '/a/cache/node_modules/lib.d.ts' does not exist.", + "File '/a/cache/node_modules/lib.js' does not exist.", + "File '/a/cache/node_modules/lib.jsx' does not exist.", + "File '/a/cache/node_modules/lib/package.json' does not exist.", + "File '/a/cache/node_modules/lib/index.ts' does not exist.", + "File '/a/cache/node_modules/lib/index.tsx' does not exist.", + "File '/a/cache/node_modules/lib/index.d.ts' does not exist.", + "File '/a/cache/node_modules/lib/index.js' does not exist.", + "File '/a/cache/node_modules/lib/index.jsx' does not exist.", + "File '/a/cache/node_modules/@types/lib.ts' does not exist.", + "File '/a/cache/node_modules/@types/lib.tsx' does not exist.", + "File '/a/cache/node_modules/@types/lib.d.ts' does not exist.", + "File '/a/cache/node_modules/@types/lib.js' does not exist.", + "File '/a/cache/node_modules/@types/lib.jsx' does not exist.", + "File '/a/cache/node_modules/@types/lib/package.json' does not exist.", + "File '/a/cache/node_modules/@types/lib/index.ts' does not exist.", + "File '/a/cache/node_modules/@types/lib/index.tsx' does not exist.", + "File '/a/cache/node_modules/@types/lib/index.d.ts' exist - use it as a name resolution result.", + ]); + checkProjectActualFiles(proj, [ file1.path, lib.path ]); + }); + }); } \ No newline at end of file diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index dbaeb4b45c9..fe8c11d262e 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -183,17 +183,16 @@ namespace ts.server { public readonly logger: Logger, public readonly cancellationToken: HostCancellationToken, private readonly useSingleInferredProject: boolean, - private typingsInstaller: ITypingsInstaller, + readonly typingsInstaller: ITypingsInstaller = nullTypingsInstaller, private readonly eventHandler?: ProjectServiceEventHandler) { this.toCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames); this.directoryWatchers = new DirectoryWatchers(this); this.throttledOperations = new ThrottledOperations(host); - const installer = typingsInstaller || nullTypingsInstaller; - installer.attach(this); + this.typingsInstaller.attach(this); - this.typingsCache = new TypingsCache(installer); + this.typingsCache = new TypingsCache(this.typingsInstaller); // ts.disableIncrementalParsing = true; diff --git a/src/server/lsHost.ts b/src/server/lsHost.ts index 4e5017f2946..bf460d404b3 100644 --- a/src/server/lsHost.ts +++ b/src/server/lsHost.ts @@ -9,10 +9,37 @@ namespace ts.server { private readonly resolvedTypeReferenceDirectives: ts.FileMap>; private readonly getCanonicalFileName: (fileName: string) => string; + private readonly resolveModuleName: typeof resolveModuleName; + readonly trace: (s: string) => void; + constructor(private readonly host: ServerHost, private readonly project: Project, private readonly cancellationToken: HostCancellationToken) { this.getCanonicalFileName = ts.createGetCanonicalFileName(this.host.useCaseSensitiveFileNames); this.resolvedModuleNames = createFileMap>(); this.resolvedTypeReferenceDirectives = createFileMap>(); + + if (host.trace) { + this.trace = s => host.trace(s); + } + + this.resolveModuleName = (moduleName, containingFile, compilerOptions, host) => { + const primaryResult = resolveModuleName(moduleName, containingFile, compilerOptions, host); + if (primaryResult.resolvedModule) { + return primaryResult; + } + const globalCache = this.project.projectService.typingsInstaller.globalTypingsCacheLocation; + if (this.project.getTypingOptions().enableAutoDiscovery && globalCache) { + const traceEnabled = isTraceEnabled(compilerOptions, host); + if (traceEnabled) { + trace(host, Diagnostics.Auto_discovery_for_typings_is_enabled_in_project_0_Running_extra_resolution_pass_for_module_1_using_cache_location_2, this.project.getProjectName(), moduleName, globalCache); + } + const state: ModuleResolutionState = { compilerOptions, host, skipTsx: false, traceEnabled }; + const resolvedName = loadModuleFromNodeModules(moduleName, globalCache, primaryResult.failedLookupLocations, state, /*checkOneLevel*/ true); + if (resolvedName) { + return createResolvedModule(resolvedName, /*isExternalLibraryImport*/ true, primaryResult.failedLookupLocations); + } + } + return primaryResult; + }; } private resolveNamesWithLocalCache( @@ -89,7 +116,7 @@ namespace ts.server { } resolveModuleNames(moduleNames: string[], containingFile: string): ResolvedModule[] { - return this.resolveNamesWithLocalCache(moduleNames, containingFile, this.resolvedModuleNames, resolveModuleName, m => m.resolvedModule); + return this.resolveNamesWithLocalCache(moduleNames, containingFile, this.resolvedModuleNames, this.resolveModuleName, m => m.resolvedModule); } getDefaultLibFileName() { diff --git a/src/server/server.ts b/src/server/server.ts index 11cc4b94740..6fa348fdb1e 100644 --- a/src/server/server.ts +++ b/src/server/server.ts @@ -13,6 +13,29 @@ namespace ts.server { fork(modulePath: string, args: string[], options?: { execArgv: string[], env?: MapLike }): NodeChildProcess; } = require("child_process"); + const os: { + homedir(): string + } = require("os"); + + + function getGlobalTypingsCacheLocation() { + let basePath: string; + switch (process.platform) { + case "win32": + basePath = process.env.LOCALAPPDATA || process.env.APPDATA || os.homedir(); + break; + case "linux": + basePath = os.homedir(); + break; + case "darwin": + basePath = combinePaths(os.homedir(), "Library/Application Support/"); + break; + } + + Debug.assert(basePath !== undefined); + return combinePaths(normalizeSlashes(basePath), "Microsoft/TypeScript"); + } + interface NodeChildProcess { send(message: any, sendHandle?: any): void; on(message: "message", f: (m: any) => void): void; @@ -167,7 +190,11 @@ namespace ts.server { private socket: NodeSocket; private projectService: ProjectService; - constructor(private readonly logger: server.Logger, private readonly eventPort: number, private newLine: string) { + constructor( + private readonly logger: server.Logger, + private readonly eventPort: number, + readonly globalTypingsCacheLocation: string, + private newLine: string) { if (eventPort) { const s = net.connect({ port: eventPort }, () => { this.socket = s; @@ -181,7 +208,7 @@ namespace ts.server { this.logger.info("Binding..."); } - const args: string[] = []; + const args: string[] = ["--globalTypingsCacheLocation", this.globalTypingsCacheLocation]; if (this.logger.loggingEnabled() && this.logger.getLogFileName()) { args.push("--logFile", combinePaths(getDirectoryPath(normalizeSlashes(this.logger.getLogFileName())), `ti-${process.pid}.log`)); } @@ -238,12 +265,13 @@ namespace ts.server { installerEventPort: number, canUseEvents: boolean, useSingleInferredProject: boolean, + globalTypingsCacheLocation: string, logger: server.Logger) { super( host, cancellationToken, useSingleInferredProject, - new NodeTypingsInstaller(logger, installerEventPort, host.newLine), + new NodeTypingsInstaller(logger, installerEventPort, globalTypingsCacheLocation, host.newLine), Buffer.byteLength, process.hrtime, logger, @@ -442,12 +470,6 @@ namespace ts.server { } } - function writeCompressedData(prefix: string, compressed: CompressedData, suffix: string): void { - sys.write(prefix); - writeMessage(compressed.data); - sys.write(suffix); - } - const sys = ts.sys; // Override sys.write because fs.writeSync is not reliable on Node 4 @@ -463,7 +485,6 @@ namespace ts.server { sys.clearTimeout = clearTimeout; sys.setImmediate = setImmediate; sys.clearImmediate = clearImmediate; - sys.writeCompressedData = writeCompressedData; if (typeof global !== "undefined" && global.gc) { sys.gc = () => global.gc(); } @@ -491,7 +512,14 @@ namespace ts.server { } const useSingleInferredProject = sys.args.indexOf("--useSingleInferredProject") >= 0; - const ioSession = new IOSession(sys, cancellationToken, eventPort, /*canUseEvents*/ eventPort === undefined, useSingleInferredProject, logger); + const ioSession = new IOSession( + sys, + cancellationToken, + eventPort, + /*canUseEvents*/ eventPort === undefined, + useSingleInferredProject, + getGlobalTypingsCacheLocation(), + logger); process.on("uncaughtException", function (err: Error) { ioSession.logError(err, "unknown"); }); diff --git a/src/server/types.d.ts b/src/server/types.d.ts index b4694cc4017..14e3ead9b82 100644 --- a/src/server/types.d.ts +++ b/src/server/types.d.ts @@ -16,6 +16,7 @@ declare namespace ts.server { clearImmediate(timeoutId: any): void; writeCompressedData(prefix: string, data: CompressedData, suffix: string): void; gc?(): void; + trace?(s: string): void; } export interface TypingInstallerRequest { diff --git a/src/server/typingsCache.ts b/src/server/typingsCache.ts index 91c5aaef7cd..25efa02c349 100644 --- a/src/server/typingsCache.ts +++ b/src/server/typingsCache.ts @@ -5,12 +5,14 @@ namespace ts.server { enqueueInstallTypingsRequest(p: Project, typingOptions: TypingOptions): void; attach(projectService: ProjectService): void; onProjectClosed(p: Project): void; + readonly globalTypingsCacheLocation: string; } export const nullTypingsInstaller: ITypingsInstaller = { enqueueInstallTypingsRequest: () => {}, attach: (projectService: ProjectService) => {}, - onProjectClosed: (p: Project) => {} + onProjectClosed: (p: Project) => {}, + globalTypingsCacheLocation: undefined }; class TypingsCacheEntry { diff --git a/src/server/typingsInstaller/nodeTypingsInstaller.ts b/src/server/typingsInstaller/nodeTypingsInstaller.ts index e0a361f9411..6e726a75656 100644 --- a/src/server/typingsInstaller/nodeTypingsInstaller.ts +++ b/src/server/typingsInstaller/nodeTypingsInstaller.ts @@ -3,32 +3,10 @@ namespace ts.server.typingsInstaller { - const os: { - homedir(): string - } = require("os"); - const fs: { appendFileSync(file: string, content: string): void } = require("fs"); - function getGlobalCacheLocation() { - let basePath: string; - switch (process.platform) { - case "win32": - basePath = process.env.LOCALAPPDATA || process.env.APPDATA || os.homedir(); - break; - case "linux": - basePath = os.homedir(); - break; - case "darwin": - basePath = combinePaths(os.homedir(), "Library/Application Support/"); - break; - } - - Debug.assert(basePath !== undefined); - return combinePaths(normalizeSlashes(basePath), "Microsoft/TypeScript"); - } - class FileLog implements Log { constructor(private readonly logFile?: string) { } @@ -49,8 +27,8 @@ namespace ts.server.typingsInstaller { private tsdRunCount = 1; readonly installTypingHost: InstallTypingHost = sys; - constructor(log?: Log) { - super(getGlobalCacheLocation(), toPath("typingSafeList.json", __dirname, createGetCanonicalFileName(sys.useCaseSensitiveFileNames)), log); + constructor(globalTypingsCacheLocation: string, log: Log) { + super(globalTypingsCacheLocation, toPath("typingSafeList.json", __dirname, createGetCanonicalFileName(sys.useCaseSensitiveFileNames)), log); if (this.log.isEnabled()) { this.log.writeLine(`Process id: ${process.pid}`); } @@ -158,23 +136,27 @@ namespace ts.server.typingsInstaller { } } - let logFilePath: string; - { - const logFileIndex = sys.args.indexOf("--logFile"); - if (logFileIndex >= 0 && logFileIndex < sys.args.length - 1) { - logFilePath = sys.args[logFileIndex + 1]; - } + function findArgument(argumentName: string) { + const index = sys.args.indexOf(argumentName); + return index >= 0 && index < sys.args.length - 1 + ? sys.args[index] + : undefined; } + + const logFilePath = findArgument("--logFile"); + const globalTypingsCacheLocation = findArgument("--globalTypingsCacheLocation"); const log = new FileLog(logFilePath); if (log.isEnabled()) { process.on("uncaughtException", (e: Error) => { log.writeLine(`Unhandled exception: ${e} at ${e.stack}`); }); - process.on("disconnect", () => { - log.writeLine(`Parent process has exited, shutting down...`); - process.exit(0); - }); } - const installer = new NodeTypingsInstaller(log); + process.on("disconnect", () => { + if (log.isEnabled()) { + log.writeLine(`Parent process has exited, shutting down...`); + } + process.exit(0); + }); + const installer = new NodeTypingsInstaller(globalTypingsCacheLocation, log); installer.init(); } \ No newline at end of file