From 81ab8ba6cdf38c77ed24a24b7b665b2f83d8094d Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Thu, 31 Mar 2016 14:59:55 -0700 Subject: [PATCH 1/8] Allows NTVS repl window to work for any project type We currently only support running `.npm` commands in node projects for the Repl window. This change allows us to run repl window commands in other project types as well. Since these projects may not have a `package.json` file for npm to use, also added a command to create it. --- Nodejs/Product/Nodejs/Repl/NpmReplCommand.cs | 33 +++++++++++--------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/Nodejs/Product/Nodejs/Repl/NpmReplCommand.cs b/Nodejs/Product/Nodejs/Repl/NpmReplCommand.cs index 7fb7c2b2d..abb4ac02c 100644 --- a/Nodejs/Product/Nodejs/Repl/NpmReplCommand.cs +++ b/Nodejs/Product/Nodejs/Repl/NpmReplCommand.cs @@ -59,7 +59,7 @@ public async Task Execute(IReplWindow window, string arguments) npmArguments = string.Format(" {0} ", npmArguments); var solution = Package.GetGlobalService(typeof(SVsSolution)) as IVsSolution; - IEnumerable loadedProjects = solution.EnumerateLoadedProjects(onlyNodeProjects: true); + var loadedProjects = solution.EnumerateLoadedProjects(onlyNodeProjects: false); var projectNameToDirectoryDictionary = new Dictionary>(StringComparer.OrdinalIgnoreCase); foreach (IVsProject project in loadedProjects) { @@ -76,19 +76,9 @@ public async Task Execute(IReplWindow window, string arguments) continue; } - EnvDTE.Properties properties = dteProject.Properties; - if (dteProject.Properties == null) { - continue; - } - string projectName = dteProject.Name; - EnvDTE.Property projectHome = properties.Item("ProjectHome"); - if (projectHome == null || projectName == null) { - continue; - } - - var projectDirectory = projectHome.Value as string; - if (projectDirectory != null) { + string projectDirectory = Path.GetDirectoryName(dteProject.FullName); + if (!string.IsNullOrEmpty(projectDirectory)) { projectNameToDirectoryDictionary.Add(projectName, Tuple.Create(projectDirectory, hierarchy)); } } @@ -118,7 +108,7 @@ public async Task Execute(IReplWindow window, string arguments) // In case someone copies filename string projectDirectoryPath = File.Exists(projectPath) ? Path.GetDirectoryName(projectPath) : projectPath; - if (!isGlobalCommand && !(Directory.Exists(projectDirectoryPath) && File.Exists(Path.Combine(projectDirectoryPath, "package.json")))) { + if (!isGlobalCommand && !Directory.Exists(projectDirectoryPath)) { window.WriteError("Please specify a valid Node.js project or project directory. If your solution contains multiple projects, specify a target project using .npm [ProjectName or ProjectDir] For example: .npm [MyApp] list"); return ExecutionResult.Failure; } @@ -137,7 +127,20 @@ public async Task Execute(IReplWindow window, string arguments) } var npmReplRedirector = new NpmReplRedirector(window); - + + if (!isGlobalCommand && !File.Exists(Path.Combine(projectDirectoryPath, "package.json"))) { + await ExecuteNpmCommandAsync( + npmReplRedirector, + npmPath, + projectDirectoryPath, + new[] { "init", "--yes" }, + null); + + if (npmReplRedirector.HasErrors) { + return ExecutionResult.Failure; + } + } + await ExecuteNpmCommandAsync( npmReplRedirector, npmPath, From 310551503b00bcf60cfd6d46e1a972cd484d9535 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Thu, 31 Mar 2016 15:10:50 -0700 Subject: [PATCH 2/8] Support mixed node + other projects better. --- Nodejs/Product/Nodejs/Repl/NpmReplCommand.cs | 49 ++++++++++++-------- 1 file changed, 29 insertions(+), 20 deletions(-) diff --git a/Nodejs/Product/Nodejs/Repl/NpmReplCommand.cs b/Nodejs/Product/Nodejs/Repl/NpmReplCommand.cs index abb4ac02c..276198efb 100644 --- a/Nodejs/Product/Nodejs/Repl/NpmReplCommand.cs +++ b/Nodejs/Product/Nodejs/Repl/NpmReplCommand.cs @@ -61,26 +61,10 @@ public async Task Execute(IReplWindow window, string arguments) var solution = Package.GetGlobalService(typeof(SVsSolution)) as IVsSolution; var loadedProjects = solution.EnumerateLoadedProjects(onlyNodeProjects: false); - var projectNameToDirectoryDictionary = new Dictionary>(StringComparer.OrdinalIgnoreCase); - foreach (IVsProject project in loadedProjects) { - var hierarchy = (IVsHierarchy)project; - object extObject; - - var projectResult = hierarchy.GetProperty(VSConstants.VSITEMID_ROOT, (int)__VSHPROPID.VSHPROPID_ExtObject, out extObject); - if (!ErrorHandler.Succeeded(projectResult)) { - continue; - } - - EnvDTE.Project dteProject = extObject as EnvDTE.Project; - if (dteProject == null) { - continue; - } - - string projectName = dteProject.Name; - string projectDirectory = Path.GetDirectoryName(dteProject.FullName); - if (!string.IsNullOrEmpty(projectDirectory)) { - projectNameToDirectoryDictionary.Add(projectName, Tuple.Create(projectDirectory, hierarchy)); - } + // Prefer Node projects, but fall back to a non-node project if no Node projects are loaded. + var projectNameToDirectoryDictionary = GetProjectInfo(solution.EnumerateLoadedProjects(onlyNodeProjects: true)); + if (!projectNameToDirectoryDictionary.Any()) { + projectNameToDirectoryDictionary = GetProjectInfo(solution.EnumerateLoadedProjects(onlyNodeProjects: false)); } Tuple projectInfo; @@ -231,6 +215,31 @@ internal static async Task> ExecuteNpmCommandAsync( #endregion + private static Dictionary> GetProjectInfo(IEnumerable loadedProjects) { + var projectNameToDirectoryDictionary = new Dictionary>(); + foreach (IVsProject project in loadedProjects) { + var hierarchy = (IVsHierarchy)project; + object extObject; + + var projectResult = hierarchy.GetProperty(VSConstants.VSITEMID_ROOT, (int)__VSHPROPID.VSHPROPID_ExtObject, out extObject); + if (!ErrorHandler.Succeeded(projectResult)) { + continue; + } + + EnvDTE.Project dteProject = extObject as EnvDTE.Project; + if (dteProject == null) { + continue; + } + + string projectName = dteProject.Name; + string projectDirectory = Path.GetDirectoryName(dteProject.FullName); + if (!string.IsNullOrEmpty(projectDirectory)) { + projectNameToDirectoryDictionary.Add(projectName, Tuple.Create(projectDirectory, hierarchy)); + } + } + return projectNameToDirectoryDictionary; + } + internal class NpmReplRedirector : Redirector { internal const string ErrorAnsiColor = "\x1b[31;1m"; From bac5f1fa5fed11e43ed0d32a896626c94268f824 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Thu, 31 Mar 2016 15:21:25 -0700 Subject: [PATCH 3/8] Remove npm init logic --- Nodejs/Product/Nodejs/Repl/NpmReplCommand.cs | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/Nodejs/Product/Nodejs/Repl/NpmReplCommand.cs b/Nodejs/Product/Nodejs/Repl/NpmReplCommand.cs index 276198efb..a6efd7607 100644 --- a/Nodejs/Product/Nodejs/Repl/NpmReplCommand.cs +++ b/Nodejs/Product/Nodejs/Repl/NpmReplCommand.cs @@ -111,20 +111,6 @@ public async Task Execute(IReplWindow window, string arguments) } var npmReplRedirector = new NpmReplRedirector(window); - - if (!isGlobalCommand && !File.Exists(Path.Combine(projectDirectoryPath, "package.json"))) { - await ExecuteNpmCommandAsync( - npmReplRedirector, - npmPath, - projectDirectoryPath, - new[] { "init", "--yes" }, - null); - - if (npmReplRedirector.HasErrors) { - return ExecutionResult.Failure; - } - } - await ExecuteNpmCommandAsync( npmReplRedirector, npmPath, From 602aa9c58592b79b911d739161727bf8844b5e16 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Thu, 31 Mar 2016 15:24:22 -0700 Subject: [PATCH 4/8] Remove prefer node project logic --- Nodejs/Product/Nodejs/Repl/NpmReplCommand.cs | 49 ++++++++------------ 1 file changed, 20 insertions(+), 29 deletions(-) diff --git a/Nodejs/Product/Nodejs/Repl/NpmReplCommand.cs b/Nodejs/Product/Nodejs/Repl/NpmReplCommand.cs index a6efd7607..379136172 100644 --- a/Nodejs/Product/Nodejs/Repl/NpmReplCommand.cs +++ b/Nodejs/Product/Nodejs/Repl/NpmReplCommand.cs @@ -61,10 +61,26 @@ public async Task Execute(IReplWindow window, string arguments) var solution = Package.GetGlobalService(typeof(SVsSolution)) as IVsSolution; var loadedProjects = solution.EnumerateLoadedProjects(onlyNodeProjects: false); - // Prefer Node projects, but fall back to a non-node project if no Node projects are loaded. - var projectNameToDirectoryDictionary = GetProjectInfo(solution.EnumerateLoadedProjects(onlyNodeProjects: true)); - if (!projectNameToDirectoryDictionary.Any()) { - projectNameToDirectoryDictionary = GetProjectInfo(solution.EnumerateLoadedProjects(onlyNodeProjects: false)); + var projectNameToDirectoryDictionary = new Dictionary>(); + foreach (IVsProject project in loadedProjects) { + var hierarchy = (IVsHierarchy)project; + object extObject; + + var projectResult = hierarchy.GetProperty(VSConstants.VSITEMID_ROOT, (int)__VSHPROPID.VSHPROPID_ExtObject, out extObject); + if (!ErrorHandler.Succeeded(projectResult)) { + continue; + } + + EnvDTE.Project dteProject = extObject as EnvDTE.Project; + if (dteProject == null) { + continue; + } + + string projectName = dteProject.Name; + string projectDirectory = Path.GetDirectoryName(dteProject.FullName); + if (!string.IsNullOrEmpty(projectDirectory)) { + projectNameToDirectoryDictionary.Add(projectName, Tuple.Create(projectDirectory, hierarchy)); + } } Tuple projectInfo; @@ -201,31 +217,6 @@ internal static async Task> ExecuteNpmCommandAsync( #endregion - private static Dictionary> GetProjectInfo(IEnumerable loadedProjects) { - var projectNameToDirectoryDictionary = new Dictionary>(); - foreach (IVsProject project in loadedProjects) { - var hierarchy = (IVsHierarchy)project; - object extObject; - - var projectResult = hierarchy.GetProperty(VSConstants.VSITEMID_ROOT, (int)__VSHPROPID.VSHPROPID_ExtObject, out extObject); - if (!ErrorHandler.Succeeded(projectResult)) { - continue; - } - - EnvDTE.Project dteProject = extObject as EnvDTE.Project; - if (dteProject == null) { - continue; - } - - string projectName = dteProject.Name; - string projectDirectory = Path.GetDirectoryName(dteProject.FullName); - if (!string.IsNullOrEmpty(projectDirectory)) { - projectNameToDirectoryDictionary.Add(projectName, Tuple.Create(projectDirectory, hierarchy)); - } - } - return projectNameToDirectoryDictionary; - } - internal class NpmReplRedirector : Redirector { internal const string ErrorAnsiColor = "\x1b[31;1m"; From b19622a929a2e85c7d9e77f0f7625946db9282a9 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Thu, 31 Mar 2016 15:25:41 -0700 Subject: [PATCH 5/8] Replace IgnoreCase --- Nodejs/Product/Nodejs/Repl/NpmReplCommand.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Nodejs/Product/Nodejs/Repl/NpmReplCommand.cs b/Nodejs/Product/Nodejs/Repl/NpmReplCommand.cs index 379136172..ff5765345 100644 --- a/Nodejs/Product/Nodejs/Repl/NpmReplCommand.cs +++ b/Nodejs/Product/Nodejs/Repl/NpmReplCommand.cs @@ -61,7 +61,7 @@ public async Task Execute(IReplWindow window, string arguments) var solution = Package.GetGlobalService(typeof(SVsSolution)) as IVsSolution; var loadedProjects = solution.EnumerateLoadedProjects(onlyNodeProjects: false); - var projectNameToDirectoryDictionary = new Dictionary>(); + var projectNameToDirectoryDictionary = new Dictionary>(StringComparer.OrdinalIgnoreCase); foreach (IVsProject project in loadedProjects) { var hierarchy = (IVsHierarchy)project; object extObject; From 1d41401d46bc6882834815f05c746425dd9629ea Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Tue, 5 Apr 2016 12:27:40 -0700 Subject: [PATCH 6/8] Address small cr feedbacks --- Nodejs/Product/Nodejs/Repl/NpmReplCommand.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Nodejs/Product/Nodejs/Repl/NpmReplCommand.cs b/Nodejs/Product/Nodejs/Repl/NpmReplCommand.cs index ff5765345..0e17e4122 100644 --- a/Nodejs/Product/Nodejs/Repl/NpmReplCommand.cs +++ b/Nodejs/Product/Nodejs/Repl/NpmReplCommand.cs @@ -59,7 +59,7 @@ public async Task Execute(IReplWindow window, string arguments) npmArguments = string.Format(" {0} ", npmArguments); var solution = Package.GetGlobalService(typeof(SVsSolution)) as IVsSolution; - var loadedProjects = solution.EnumerateLoadedProjects(onlyNodeProjects: false); + IEnumerable loadedProjects = solution.EnumerateLoadedProjects(onlyNodeProjects: false); var projectNameToDirectoryDictionary = new Dictionary>(StringComparer.OrdinalIgnoreCase); foreach (IVsProject project in loadedProjects) { @@ -77,6 +77,10 @@ public async Task Execute(IReplWindow window, string arguments) } string projectName = dteProject.Name; + if (string.IsNullOrEmpty(projectName)) { + continue; + } + string projectDirectory = Path.GetDirectoryName(dteProject.FullName); if (!string.IsNullOrEmpty(projectDirectory)) { projectNameToDirectoryDictionary.Add(projectName, Tuple.Create(projectDirectory, hierarchy)); From dced2e72d3d52fd510f5499cf2d73269270dd634 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Tue, 5 Apr 2016 12:35:56 -0700 Subject: [PATCH 7/8] Prevent running `.npm init` without the yes flag directly in the Repl window --- Nodejs/Product/Nodejs/Project/ProjectResources.cs | 1 + Nodejs/Product/Nodejs/Repl/NpmReplCommand.cs | 7 +++++++ Nodejs/Product/Nodejs/Resources.resx | 3 +++ 3 files changed, 11 insertions(+) diff --git a/Nodejs/Product/Nodejs/Project/ProjectResources.cs b/Nodejs/Product/Nodejs/Project/ProjectResources.cs index 3f90b1c98..25cbc92d8 100644 --- a/Nodejs/Product/Nodejs/Project/ProjectResources.cs +++ b/Nodejs/Product/Nodejs/Project/ProjectResources.cs @@ -163,6 +163,7 @@ internal class SR : CommonSR { internal const string PropertiesClassLocalSubPackage = "PropertiesClassLocalSubPackage"; internal const string PropertiesClassNpm = "PropertiesClassNpm"; internal const string ReplInitializationMessage = "ReplInitializationMessage"; + internal const string ReplWindowNpmInitNoYesFlagWarning = "ReplWindowNpmInitNoYesFlagWarning"; internal const string RequestedVersionRangeNone = "RequestedVersionRangeNone"; internal const string ScriptArgumentsToolTip = "ScriptArgumentsToolTip"; internal const string ScriptFileToolTip = "ScriptFileTooltip"; diff --git a/Nodejs/Product/Nodejs/Repl/NpmReplCommand.cs b/Nodejs/Product/Nodejs/Repl/NpmReplCommand.cs index 0e17e4122..30417d300 100644 --- a/Nodejs/Product/Nodejs/Repl/NpmReplCommand.cs +++ b/Nodejs/Product/Nodejs/Repl/NpmReplCommand.cs @@ -58,6 +58,13 @@ public async Task Execute(IReplWindow window, string arguments) // at beginning and end of string (e.g. '--global') npmArguments = string.Format(" {0} ", npmArguments); + // Prevent running `npm init` without the `-y` flag since it will freeze the repl window, + // waiting for user input that will never come. + if (npmArguments.Contains(" init ") && !(npmArguments.Contains(" -y ") || npmArguments.Contains(" --yes "))) { + window.WriteError(SR.GetString(SR.ReplWindowNpmInitNoYesFlagWarning)); + return ExecutionResult.Failure; + } + var solution = Package.GetGlobalService(typeof(SVsSolution)) as IVsSolution; IEnumerable loadedProjects = solution.EnumerateLoadedProjects(onlyNodeProjects: false); diff --git a/Nodejs/Product/Nodejs/Resources.resx b/Nodejs/Product/Nodejs/Resources.resx index 7fdf07e1f..c9daeaf7a 100644 --- a/Nodejs/Product/Nodejs/Resources.resx +++ b/Nodejs/Product/Nodejs/Resources.resx @@ -473,6 +473,9 @@ NAME2=value2 Node.js interactive window. Type .help for a list of commands. + + Please run '.npm init -y' to create a new package.json file. + Specifies the path to the node.exe executable. From 5df5d69f13939705000ea40fcee0e91d72f9adb6 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Tue, 5 Apr 2016 13:32:19 -0700 Subject: [PATCH 8/8] Restore original ProjectHomeLogic, plus fallback for other projecs types --- Nodejs/Product/Nodejs/Repl/NpmReplCommand.cs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/Nodejs/Product/Nodejs/Repl/NpmReplCommand.cs b/Nodejs/Product/Nodejs/Repl/NpmReplCommand.cs index 30417d300..6cc8e3fa1 100644 --- a/Nodejs/Product/Nodejs/Repl/NpmReplCommand.cs +++ b/Nodejs/Product/Nodejs/Repl/NpmReplCommand.cs @@ -88,6 +88,26 @@ public async Task Execute(IReplWindow window, string arguments) continue; } + // Try checking the `ProjectHome` property first + EnvDTE.Properties properties = dteProject.Properties; + if (dteProject.Properties != null) { + EnvDTE.Property projectHome = null; + try { + projectHome = properties.Item("ProjectHome"); + } catch (ArgumentException) { + // noop + } + + if (projectHome != null) { + var projectHomeDirectory = projectHome.Value as string; + if (!string.IsNullOrEmpty(projectHomeDirectory)) { + projectNameToDirectoryDictionary.Add(projectName, Tuple.Create(projectHomeDirectory, hierarchy)); + continue; + } + } + } + + // Otherwise, fall back to using fullname string projectDirectory = Path.GetDirectoryName(dteProject.FullName); if (!string.IsNullOrEmpty(projectDirectory)) { projectNameToDirectoryDictionary.Add(projectName, Tuple.Create(projectDirectory, hierarchy));