diff --git a/src/jsTyping/shared.ts b/src/jsTyping/shared.ts index 8dc4c02e9c444..c1624527cf3fb 100644 --- a/src/jsTyping/shared.ts +++ b/src/jsTyping/shared.ts @@ -21,6 +21,11 @@ namespace ts.server { * typingsInstaller will run the command with `${npmLocation} install ...`. */ export const NpmLocation = "--npmLocation"; + /** + * Flag indicating that the typings installer should try to validate the default npm location. + * If the default npm is not found when this flag is enabled, fallback to `npm install` + */ + export const ValidateDefaultNpmLocation = "--validateDefaultNpmLocation"; } export function hasArgument(argumentName: string) { diff --git a/src/tsserver/server.ts b/src/tsserver/server.ts index 80a9422e24a03..8747659606152 100644 --- a/src/tsserver/server.ts +++ b/src/tsserver/server.ts @@ -242,6 +242,7 @@ namespace ts.server { readonly typingSafeListLocation: string, readonly typesMapLocation: string, private readonly npmLocation: string | undefined, + private readonly validateDefaultNpmLocation: boolean, private event: Event) { } @@ -297,6 +298,9 @@ namespace ts.server { if (this.npmLocation) { args.push(Arguments.NpmLocation, this.npmLocation); } + if (this.validateDefaultNpmLocation) { + args.push(Arguments.ValidateDefaultNpmLocation); + } const execArgv: string[] = []; for (const arg of process.execArgv) { @@ -497,7 +501,7 @@ namespace ts.server { const typingsInstaller = disableAutomaticTypingAcquisition ? undefined - : new NodeTypingsInstaller(telemetryEnabled, logger, host, getGlobalTypingsCacheLocation(), typingSafeListLocation, typesMapLocation, npmLocation, event); + : new NodeTypingsInstaller(telemetryEnabled, logger, host, getGlobalTypingsCacheLocation(), typingSafeListLocation, typesMapLocation, npmLocation, validateDefaultNpmLocation, event); super({ host, @@ -933,6 +937,7 @@ namespace ts.server { const typingSafeListLocation = findArgument(Arguments.TypingSafeListLocation)!; // TODO: GH#18217 const typesMapLocation = findArgument(Arguments.TypesMapLocation) || combinePaths(getDirectoryPath(sys.getExecutingFilePath()), "typesMap.json"); const npmLocation = findArgument(Arguments.NpmLocation); + const validateDefaultNpmLocation = hasArgument(Arguments.ValidateDefaultNpmLocation); function parseStringArray(argName: string): ReadonlyArray { const arg = findArgument(argName); diff --git a/src/typingsInstaller/nodeTypingsInstaller.ts b/src/typingsInstaller/nodeTypingsInstaller.ts index 62bdcfce2603b..3e44a0d7d7b84 100644 --- a/src/typingsInstaller/nodeTypingsInstaller.ts +++ b/src/typingsInstaller/nodeTypingsInstaller.ts @@ -29,13 +29,17 @@ namespace ts.server.typingsInstaller { } /** Used if `--npmLocation` is not passed. */ - function getDefaultNPMLocation(processName: string) { + function getDefaultNPMLocation(processName: string, validateDefaultNpmLocation: boolean, host: InstallTypingHost): string { if (path.basename(processName).indexOf("node") === 0) { - return `"${path.join(path.dirname(process.argv[0]), "npm")}"`; - } - else { - return "npm"; + const npmPath = path.join(path.dirname(process.argv[0]), "npm"); + if (!validateDefaultNpmLocation) { + return npmPath; + } + if (host.fileExists(npmPath)) { + return `"${npmPath}"`; + } } + return "npm"; } interface TypesRegistryFile { @@ -79,7 +83,7 @@ namespace ts.server.typingsInstaller { private delayedInitializationError: InitializationFailedResponse | undefined; - constructor(globalTypingsCacheLocation: string, typingSafeListLocation: string, typesMapLocation: string, npmLocation: string | undefined, throttleLimit: number, log: Log) { + constructor(globalTypingsCacheLocation: string, typingSafeListLocation: string, typesMapLocation: string, npmLocation: string | undefined, validateDefaultNpmLocation: boolean, throttleLimit: number, log: Log) { super( sys, globalTypingsCacheLocation, @@ -87,7 +91,7 @@ namespace ts.server.typingsInstaller { typesMapLocation ? toPath(typesMapLocation, "", createGetCanonicalFileName(sys.useCaseSensitiveFileNames)) : toPath("typesMap.json", __dirname, createGetCanonicalFileName(sys.useCaseSensitiveFileNames)), throttleLimit, log); - this.npmPath = npmLocation !== undefined ? npmLocation : getDefaultNPMLocation(process.argv[0]); + this.npmPath = npmLocation !== undefined ? npmLocation : getDefaultNPMLocation(process.argv[0], validateDefaultNpmLocation, this.installTypingHost); // If the NPM path contains spaces and isn't wrapped in quotes, do so. if (stringContains(this.npmPath, " ") && this.npmPath[0] !== `"`) { @@ -96,6 +100,7 @@ namespace ts.server.typingsInstaller { if (this.log.isEnabled()) { this.log.writeLine(`Process id: ${process.pid}`); this.log.writeLine(`NPM location: ${this.npmPath} (explicit '${Arguments.NpmLocation}' ${npmLocation === undefined ? "not " : ""} provided)`); + this.log.writeLine(`validateDefaultNpmLocation: ${validateDefaultNpmLocation}`); } ({ execSync: this.nodeExecSync } = require("child_process")); @@ -229,6 +234,7 @@ namespace ts.server.typingsInstaller { const typingSafeListLocation = findArgument(Arguments.TypingSafeListLocation); const typesMapLocation = findArgument(Arguments.TypesMapLocation); const npmLocation = findArgument(Arguments.NpmLocation); + const validateDefaultNpmLocation = hasArgument(Arguments.ValidateDefaultNpmLocation); const log = new FileLog(logFilePath); if (log.isEnabled()) { @@ -242,7 +248,7 @@ namespace ts.server.typingsInstaller { } process.exit(0); }); - const installer = new NodeTypingsInstaller(globalTypingsCacheLocation!, typingSafeListLocation!, typesMapLocation!, npmLocation, /*throttleLimit*/5, log); // TODO: GH#18217 + const installer = new NodeTypingsInstaller(globalTypingsCacheLocation!, typingSafeListLocation!, typesMapLocation!, npmLocation, validateDefaultNpmLocation, /*throttleLimit*/5, log); // TODO: GH#18217 installer.listen(); function indent(newline: string, str: string): string {