diff --git a/ModernRonin.ProjectRenamer/Application.cs b/ModernRonin.ProjectRenamer/Application.cs new file mode 100644 index 0000000..34bd40f --- /dev/null +++ b/ModernRonin.ProjectRenamer/Application.cs @@ -0,0 +1,180 @@ +using System; +using System.IO; +using System.Linq; +using System.Text; +using Microsoft.Build.Construction; +using static ModernRonin.ProjectRenamer.Executor; +using static ModernRonin.ProjectRenamer.Runtime; + +namespace ModernRonin.ProjectRenamer +{ + public class Application + { + readonly Configuration _configuration; + readonly Encoding _projectFileEncoding = Encoding.UTF8; + readonly string _solutionPath; + + public Application(Configuration configuration, string solutionPath) + { + _configuration = configuration; + _solutionPath = solutionPath; + } + + void EnsureGitIsClean() + { + run("update-index -q --refresh"); + run("diff-index --quiet --cached HEAD --"); + run("diff-files --quiet"); + run("ls-files --exclude-standard --others"); + + void run(string arguments) => + Tool("git", arguments, + () => Error("git does not seem to be clean, check git status")); + } + + public void Run() + { + EnsureGitIsClean(); + + var (wasFound, oldProjectPath, solutionFolderPath) = findProject(); + if (!wasFound) Error($"{_configuration.OldProjectName} cannot be found in the solution"); + + var oldDir = Path.GetDirectoryName(oldProjectPath); + var newDir = Path.Combine(Path.GetDirectoryName(oldDir), _configuration.NewProjectName); + var newProjectPath = + Path.Combine(newDir, $"{_configuration.NewProjectName}{Constants.ProjectFileExtension}"); + var isPaketUsed = Directory.Exists(".paket"); + + if (!_configuration.DontReviewSettings) + { + var lines = new[] + { + "Please review the following settings:", + $"Project: {_configuration.OldProjectName}", + $"found at: {oldProjectPath}", + $"Rename to: {_configuration.NewProjectName}", + $"at: {newProjectPath})", + $"Paket in use: {isPaketUsed.AsText()}", + $"Run paket install: {(!_configuration.DontRunPaketInstall).AsText()}", + $"Run build after rename: {_configuration.DoRunBuild.AsText()}", + $"Create automatic commit: {(!_configuration.DontCreateCommit).AsText()}", + "-----------------------------------------------", + "Do you want to continue with the rename operation?" + }; + if (!DoesUserAgree(string.Join(Environment.NewLine, lines))) Abort(); + } + + removeFromSolution(); + gitMove(); + replaceReferences(); + addToSolution(); + updatePaket(); + stageAllChanges(); + build(); + commit(); + + void commit() + { + if (!_configuration.DontCreateCommit) + { + var arguments = + $"commit -m \"Renamed {_configuration.OldProjectName} to {_configuration.NewProjectName}\""; + Tool("git", arguments, + () => { Console.Error.WriteLine($"'git {arguments}' failed"); }); + } + } + + void build() + { + if (_configuration.DoRunBuild) + { + Tool("dotnet", "build", () => + { + if (DoesUserAgree( + "dotnet build returned an error or warning - do you want to rollback all changes?") + ) + { + RollbackGit(); + Abort(); + } + }); + } + } + + void stageAllChanges() => Git("add ."); + + void updatePaket() + { + if (isPaketUsed && !_configuration.DontRunPaketInstall) DotNet("paket install"); + } + + void replaceReferences() + { + var projectFiles = Directory + .EnumerateFiles(".", $"*{Constants.ProjectFileExtension}", SearchOption.AllDirectories) + .ToList(); + var (oldReference, newReference) = + (searchPattern(_configuration.OldProjectName), + searchPattern(_configuration.NewProjectName)); + projectFiles.ForEach(replaceIn); + + void replaceIn(string projectFile) + { + var contents = File.ReadAllText(projectFile, _projectFileEncoding); + contents = contents.Replace(oldReference, newReference); + File.WriteAllText(projectFile, contents, _projectFileEncoding); + } + + string searchPattern(string name) => + Path.Combine(name, name) + Constants.ProjectFileExtension; + } + + void gitMove() + { + Git($"mv {oldDir} {newDir}"); + var oldPath = Path.Combine(newDir, Path.GetFileName(oldProjectPath)); + Git($"mv {oldPath} {newProjectPath}"); + } + + void addToSolution() + { + var solutionFolderArgument = string.IsNullOrWhiteSpace(solutionFolderPath) + ? string.Empty + : $"-s {solutionFolderPath}"; + DotNet($"sln add {solutionFolderArgument} {newProjectPath}"); + } + + void removeFromSolution() => DotNet($"sln remove {oldProjectPath}"); + + (bool wasFound, string projectPath, string solutionFolder) findProject() + { + var solution = SolutionFile.Parse(_solutionPath); + var project = solution.ProjectsInOrder.FirstOrDefault(p => + p.ProjectName.EndsWith(_configuration.OldProjectName, + StringComparison.InvariantCultureIgnoreCase)); + return project switch + { + null => (false, null, null), + _ when project.ParentProjectGuid == null => (true, project.AbsolutePath, null), + _ => (true, project.AbsolutePath, + path(solution.ProjectsByGuid[project.ParentProjectGuid])) + }; + + string path(ProjectInSolution p) + { + if (p.ParentProjectGuid == null) return p.ProjectName; + var parent = solution.ProjectsByGuid[p.ParentProjectGuid]; + var parentPath = path(parent); + return $"{parentPath}/{p.ProjectName}"; + } + } + } + + bool DoesUserAgree(string question) + { + Console.WriteLine($"{question} [Enter=Yes, any other key=No]"); + var key = Console.ReadKey(); + return key.Key == ConsoleKey.Enter; + } + } +} \ No newline at end of file diff --git a/ModernRonin.ProjectRenamer/BooleanExtensions.cs b/ModernRonin.ProjectRenamer/BooleanExtensions.cs new file mode 100644 index 0000000..e6851ef --- /dev/null +++ b/ModernRonin.ProjectRenamer/BooleanExtensions.cs @@ -0,0 +1,7 @@ +namespace ModernRonin.ProjectRenamer +{ + public static class BooleanExtensions + { + public static string AsText(this bool self) => self ? "yes" : "no"; + } +} \ No newline at end of file diff --git a/ModernRonin.ProjectRenamer/Configuration.cs b/ModernRonin.ProjectRenamer/Configuration.cs new file mode 100644 index 0000000..c4fb7da --- /dev/null +++ b/ModernRonin.ProjectRenamer/Configuration.cs @@ -0,0 +1,19 @@ +namespace ModernRonin.ProjectRenamer +{ + public class Configuration + { + public bool DontRunPaketInstall { get; set; } + public bool DoRunBuild { get; set; } + public bool DontCreateCommit { get; set; } + public bool DontReviewSettings { get; set; } + public string OldProjectName { get; set; } + public string NewProjectName { get; set; } + + public string[] ProjectNames => + new[] + { + OldProjectName, + NewProjectName + }; + } +} \ No newline at end of file diff --git a/ModernRonin.ProjectRenamer/Constants.cs b/ModernRonin.ProjectRenamer/Constants.cs new file mode 100644 index 0000000..eed3b95 --- /dev/null +++ b/ModernRonin.ProjectRenamer/Constants.cs @@ -0,0 +1,7 @@ +namespace ModernRonin.ProjectRenamer +{ + public static class Constants + { + public const string ProjectFileExtension = ".csproj"; + } +} \ No newline at end of file diff --git a/ModernRonin.ProjectRenamer/Executor.cs b/ModernRonin.ProjectRenamer/Executor.cs new file mode 100644 index 0000000..4333a52 --- /dev/null +++ b/ModernRonin.ProjectRenamer/Executor.cs @@ -0,0 +1,49 @@ +using System; +using System.ComponentModel; +using System.Diagnostics; +using System.IO; + +namespace ModernRonin.ProjectRenamer +{ + public static class Executor + { + public static void Git(string arguments) => Tool("git", arguments); + + public static void Tool(string tool, string arguments) => + Tool(tool, arguments, () => Runtime.Error($"call '{tool} {arguments}' failed - aborting", true)); + + public static void Tool(string tool, string arguments, Action onNonZeroExitCode) + { + var psi = new ProcessStartInfo + { + FileName = tool, + Arguments = arguments, + UseShellExecute = false, + CreateNoWindow = false, + RedirectStandardOutput = false + }; + try + { + var process = Process.Start(psi); + process.WaitForExit(); + if (process.ExitCode != 0) onNonZeroExitCode(); + } + catch (Win32Exception) + { + onProcessStartProblem(); + } + catch (FileNotFoundException) + { + onProcessStartProblem(); + } + + void onProcessStartProblem() + { + Console.Error.WriteLine($"{tool} could not be found - make sure it's on your PATH."); + onNonZeroExitCode(); + } + } + + public static void DotNet(string arguments) => Tool("dotnet", arguments); + } +} \ No newline at end of file diff --git a/ModernRonin.ProjectRenamer/ModernRonin.ProjectRenamer.csproj b/ModernRonin.ProjectRenamer/ModernRonin.ProjectRenamer.csproj index 68db9af..9da4c80 100644 --- a/ModernRonin.ProjectRenamer/ModernRonin.ProjectRenamer.csproj +++ b/ModernRonin.ProjectRenamer/ModernRonin.ProjectRenamer.csproj @@ -18,7 +18,8 @@ + - + diff --git a/ModernRonin.ProjectRenamer/Program.cs b/ModernRonin.ProjectRenamer/Program.cs index 0e184f8..3dfdd84 100644 --- a/ModernRonin.ProjectRenamer/Program.cs +++ b/ModernRonin.ProjectRenamer/Program.cs @@ -1,215 +1,77 @@ using System; -using System.ComponentModel; -using System.Diagnostics; using System.IO; using System.Linq; -using System.Text; -using Microsoft.Build.Construction; +using ModernRonin.FluentArgumentParser; +using ModernRonin.FluentArgumentParser.Help; +using ModernRonin.FluentArgumentParser.Parsing; +using static ModernRonin.ProjectRenamer.Runtime; namespace ModernRonin.ProjectRenamer { class Program { - const string ProjectFileExtension = ".csproj"; - static readonly Encoding _projectFileEncoding = Encoding.UTF8; - static void Main(string[] args) { - if (args.Length != 2 || args.Any(a => - a.Contains('\\') || a.Contains('/') || - a.Contains(ProjectFileExtension, StringComparison.InvariantCultureIgnoreCase))) - { - Error( - "Usage: where project names contain neither path nor extension. Example: not ./utilities/My.Wonderful.Utilities.csproj, but My.Wonderful.Utilities"); - } - var solutionFiles = Directory.EnumerateFiles(".", "*.sln", SearchOption.TopDirectoryOnly).ToArray(); if (1 != solutionFiles.Length) Error("Needs to be run from a directory with exactly one *.sln file in it."); - - EnsureGitIsClean(); - var solutionPath = Path.GetFullPath(solutionFiles.First()); - Run(solutionPath, args.First(), args.Last()); - } - - static void EnsureGitIsClean() - { - run("update-index -q --refresh"); - run("diff-index --quiet --cached HEAD --"); - run("diff-files --quiet"); - run("ls-files --exclude-standard --others"); - - void run(string arguments) => - RunTool("git", arguments, () => Error("git does not seem to be clean, check git status")); - } - - static void Error(string msg, bool doResetGit = false) - { - Console.Error.WriteLine(msg); - if (doResetGit) - { - Console.Error.WriteLine("...running git reset to undo any changes..."); - RollbackGit(); - } - - Abort(); - } - - static void Abort() => Environment.Exit(-1); - - static void RollbackGit() => RunTool("git", "reset --hard HEAD", () => { }); - - static void Run(string solutionPath, string oldProjectName, string newProjectName) - { - var (wasFound, oldProjectPath, solutionFolderPath) = findProject(); - if (!wasFound) Error($"{oldProjectName} cannot be found in the solution"); - - var oldDir = Path.GetDirectoryName(oldProjectPath); - var newDir = Path.Combine(Path.GetDirectoryName(oldDir), newProjectName); - var newProjectPath = Path.Combine(newDir, $"{newProjectName}{ProjectFileExtension}"); - - removeFromSolution(); - gitMove(); - replaceReferences(); - addToSolution(); - updatePaket(); - stageAllChanges(); - - if (doesUserAgree("Finished - do you want to run a dotnet build to see whether all went well?")) - { - RunTool("dotnet", "build", () => - { - if (doesUserAgree( - "dotnet build returned an error or warning - do you want to rollback all changes?")) - { - RollbackGit(); - Abort(); - } - }); - } - - if (doesUserAgree("Do you want me to create a commit for you?")) - { - var arguments = $"commit -m \"Renamed {oldProjectName} to {newProjectName}\""; - RunTool("git", arguments, () => { Console.Error.WriteLine($"'git {arguments}' failed"); }); - } - - bool doesUserAgree(string question) - { - Console.WriteLine($"{question} [Enter=Yes, any other key=No]"); - var key = Console.ReadKey(); - return key.Key == ConsoleKey.Enter; - } - - void stageAllChanges() => RunGit("add ."); - - void updatePaket() - { - if (Directory.Exists(".paket") && - doesUserAgree("This solution uses paket - do you want to run paket install?")) - runDotNet("paket install"); - } - - void replaceReferences() - { - var projectFiles = Directory - .EnumerateFiles(".", $"*{ProjectFileExtension}", SearchOption.AllDirectories) - .ToList(); - var (oldReference, newReference) = - (searchPattern(oldProjectName), searchPattern(newProjectName)); - projectFiles.ForEach(replaceIn); - - void replaceIn(string projectFile) - { - var contents = File.ReadAllText(projectFile, _projectFileEncoding); - contents = contents.Replace(oldReference, newReference); - File.WriteAllText(projectFile, contents, _projectFileEncoding); - } - - string searchPattern(string name) => Path.Combine(name, name) + ProjectFileExtension; - } - - void gitMove() - { - RunGit($"mv {oldDir} {newDir}"); - var oldPath = Path.Combine(newDir, Path.GetFileName(oldProjectPath)); - RunGit($"mv {oldPath} {newProjectPath}"); - } - - void addToSolution() - { - var solutionFolderArgument = string.IsNullOrWhiteSpace(solutionFolderPath) - ? string.Empty - : $"-s {solutionFolderPath}"; - runDotNet($"sln add {solutionFolderArgument} {newProjectPath}"); - } - - void removeFromSolution() => runDotNet($"sln remove {oldProjectPath}"); - - void runDotNet(string arguments) => RunTool("dotnet", arguments); - - (bool wasFound, string projectPath, string solutionFolder) findProject() + switch (parseCommandLine()) { - var solution = SolutionFile.Parse(solutionPath); - var project = solution.ProjectsInOrder.FirstOrDefault(p => - p.ProjectName.EndsWith(oldProjectName, StringComparison.InvariantCultureIgnoreCase)); - return project switch - { - null => (false, null, null), - _ when project.ParentProjectGuid == null => (true, project.AbsolutePath, null), - _ => (true, project.AbsolutePath, - path(solution.ProjectsByGuid[project.ParentProjectGuid])) - }; - - string path(ProjectInSolution p) - { - if (p.ParentProjectGuid == null) return p.ProjectName; - var parent = solution.ProjectsByGuid[p.ParentProjectGuid]; - var parentPath = path(parent); - return $"{parentPath}/{p.ProjectName}"; - } + case (_, HelpResult help): + Console.WriteLine(help.Text); + Environment.Exit(help.IsResultOfInvalidInput ? -1 : 0); + break; + case (var helpOverview, Configuration configuration): + if (configuration.ProjectNames.Any(isInvalidProjectName)) Error(helpOverview); + + new Application(configuration, solutionPath).Run(); + break; + default: + Error( + "Something went seriously wrong. Please create an issue at https://github.com/ModernRonin/ProjectRenamer with as much detail as possible."); + break; } - } - - static void RunGit(string arguments) => RunTool("git", arguments); - - static void RunTool(string tool, string arguments) - { - RunTool(tool, arguments, () => Error($"call '{tool} {arguments}' failed - aborting", true)); - } - static void RunTool(string tool, string arguments, Action onNonZeroExitCode) - { - var psi = new ProcessStartInfo - { - FileName = tool, - Arguments = arguments, - UseShellExecute = false, - CreateNoWindow = false, - RedirectStandardOutput = false - }; - try - { - var process = Process.Start(psi); - process.WaitForExit(); - if (process.ExitCode != 0) onNonZeroExitCode(); - } - catch (Win32Exception) + (string, object) parseCommandLine() { - onProcessStartProblem(); - } - catch (FileNotFoundException) - { - onProcessStartProblem(); + var parser = ParserFactory.Create("renameproject", + "Rename C# projects comfortably, including renaming directories, updating references, keeping your git history intact, creating a git commit and updating paket, if needed."); + var cfg = parser.DefaultVerb(); + cfg.Parameter(c => c.DontCreateCommit) + .WithLongName("no-commit") + .WithShortName("nc") + .WithHelp("don't create a commit after renaming has finished"); + cfg.Parameter(c => c.DoRunBuild) + .WithLongName("build") + .WithShortName("b") + .WithHelp( + "run a build after renaming (but before committing) to make sure everything worked fine."); + cfg.Parameter(c => c.DontRunPaketInstall) + .WithLongName("no-paket") + .WithShortName("np") + .WithHelp("don't run paket install (if your solution uses paket as a local tool)"); + cfg.Parameter(c => c.DontReviewSettings) + .WithLongName("no-review") + .WithShortName("nr") + .WithHelp("don't review all settings before starting work"); + cfg.Parameter(c => c.OldProjectName) + .WithLongName("old-name") + .WithShortName("o") + .WithHelp( + "the name of the existing project - don't provide path or extension, just the name as you see it in VS."); + cfg.Parameter(c => c.NewProjectName) + .WithLongName("new-name") + .WithShortName("n") + .WithHelp("the new desired project name, again without path or extension"); + return (parser.HelpOverview, parser.Parse(args)); } - void onProcessStartProblem() - { - Console.Error.WriteLine($"{tool} could not be found - make sure it's on your PATH."); - onNonZeroExitCode(); - } + bool isInvalidProjectName(string projectName) => + projectName.Contains('\\') || projectName.Contains('/') || + projectName.Contains(Constants.ProjectFileExtension, + StringComparison.InvariantCultureIgnoreCase); } } } \ No newline at end of file diff --git a/ModernRonin.ProjectRenamer/Properties/launchSettings.json b/ModernRonin.ProjectRenamer/Properties/launchSettings.json index f3f314c..4df5bf9 100644 --- a/ModernRonin.ProjectRenamer/Properties/launchSettings.json +++ b/ModernRonin.ProjectRenamer/Properties/launchSettings.json @@ -1,7 +1,24 @@ { "profiles": { - "ModernRonin.ProjectRenamer": { + "rename library to project, in solution folder, no flags": { "commandName": "Project", + "commandLineArgs": "LibraryInSolutionFolder ProjectInSolutionFolder", + "workingDirectory": "C:\\Projects\\Github\\ProjectRenamerTestBed" + }, + "rename project to library, in solution folder, no review": { + "commandName": "Project", + "commandLineArgs": "ProjectInSolutionFolder LibraryInSolutionFolder --no-review", + "workingDirectory": "C:\\Projects\\Github\\ProjectRenamerTestBed" + }, + "rename library to project, in subfolder, no commit, build": { + "commandName": "Project", + "commandLineArgs": "LibraryInSubFolder ProjectInSubFolder --no-commit --build", + "workingDirectory": "C:\\Projects\\Github\\ProjectRenamerTestBed" + }, + "rename library to project, in subfolder, no commit, no paket": { + "commandName": "Project", + "commandLineArgs": "LibraryInSubFolder ProjectInSubFolder --no-commit --no-paket", + "workingDirectory": "C:\\Projects\\Github\\ProjectRenamerTestBed" } } } \ No newline at end of file diff --git a/ModernRonin.ProjectRenamer/Runtime.cs b/ModernRonin.ProjectRenamer/Runtime.cs new file mode 100644 index 0000000..326f81b --- /dev/null +++ b/ModernRonin.ProjectRenamer/Runtime.cs @@ -0,0 +1,23 @@ +using System; + +namespace ModernRonin.ProjectRenamer +{ + public static class Runtime + { + public static void Error(string msg, bool doResetGit = false) + { + Console.Error.WriteLine(msg); + if (doResetGit) + { + Console.Error.WriteLine("...running git reset to undo any changes..."); + RollbackGit(); + } + + Abort(); + } + + public static void Abort() => Environment.Exit(-1); + + public static void RollbackGit() => Executor.Tool("git", "reset --hard HEAD", () => { }); + } +} \ No newline at end of file diff --git a/ModernRonin.ProjectRenamer/Version.targets b/ModernRonin.ProjectRenamer/Version.targets deleted file mode 100644 index 5beea20..0000000 --- a/ModernRonin.ProjectRenamer/Version.targets +++ /dev/null @@ -1,10 +0,0 @@ - - - 1.0.1 - 1.0.1: bugfix: if a required tool like git cannot be found, give a proper error message - bugfix: if a project is not in a solution folder, the tools works now, too - 1.0.0: initial release - - $(MSBuildAllProjects);$(MSBuildThisFileFullPath) - - \ No newline at end of file diff --git a/ModernRonin.ProjectRenamer/release.history b/ModernRonin.ProjectRenamer/release.history new file mode 100644 index 0000000..854c858 --- /dev/null +++ b/ModernRonin.ProjectRenamer/release.history @@ -0,0 +1,18 @@ + + + 2.0.0 + +2.0.0: +* breaking changes: instead of asking the user interactively, behavior is now controlled via commandline switches +1.0.1: +* bugfix: if a required tool like git cannot be found, give a proper error message +* bugfix: if a project is not in a solution folder, the tools works now, too + +1.0.0: +initial release + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + + \ No newline at end of file diff --git a/README.md b/README.md index 315a497..d2310fd 100644 --- a/README.md +++ b/README.md @@ -29,14 +29,13 @@ dotnet tool update --global ModernRonin.ProjectRenamer ``` ### Release History +2.0.0: all behavior where the tool prompted you before interactively is now controlled via commandline arguments 1.0.1: * fixed problem when target project was not linked into a solution folder * when required tools like `git` or `dotnet` are not found, a properly informative error message is displayed 1.0.0: initial release - - ## Use it You use it from the command line, in the directory of your solution: @@ -57,13 +56,19 @@ What will happen: * the folder of the project file will be renamed * renaming is done with `git mv` so it keeps your history intact * all `` tags in other projects in your solution referencing the project will be adjusted -* if you use [paket](https://github.com/fsprojects/Paket) **as a local dotnet tool** (see [Soft Limitations](#soft-limitations)) and you agree to a prompt, `paket install` will be run +* if you use [paket](https://github.com/fsprojects/Paket) **as a local dotnet tool** (see [Soft Limitations](#soft-limitations)), `paket install` will be run, unless you specified the flag `--no-paket` * all changes will be staged in git -* if you agree to the corresponding prompt, a `dotnet build` will be run just to be totally safe that everything worked well, for very cautious/diligent people :-) -* if you agree to another prompt, a commit of the form `Renamed to ` will be created automatically +* if you specified a flag `--build`, a `dotnet build` will be run just to be totally safe that everything worked well, for very cautious/diligent people :-) +* a commit of the form `Renamed to ` will be created automatically, unless you specified a flag `--no-commit` If anything goes wrong, all changes will be discarded. +You can also use +```shell +renamedproject help +``` +to get help about the available flags. + ## Limitations *renameproject* has a few limitations. Some of them are *hard limitations*, meaning they are unlikely to go away, others are *soft limitations*, meaning they exist only because I simply have not gotten round to fix them yet. I do not really have a lot of free time to spend on this, but am **totally open to PRs (hint hint)**. @@ -74,7 +79,6 @@ If *renameproject* detects uncommitted changes, added files or the like, it will * the tool won't adjust your namespaces - just use R# for this. ### Soft Limitations -* the prompts (build, paket and commit) cannot be avoided using command-line flags * you cannot have more than one solution file or the solution file in another location than the current directory - could be turned into an optional command-line argument in the future * you cannot use this without git - the git-aspects could be made optional via a command-line flag in the future * you cannot use this with projects of other types than `csproj`, for example `fsproj` diff --git a/push-package.ps1 b/push-package.ps1 index 784bb0b..f351baa 100644 --- a/push-package.ps1 +++ b/push-package.ps1 @@ -2,4 +2,5 @@ param ([parameter(mandatory)]$version) if (-not $nugetApiKey) {Write-Host "You need to set the variable nugetApiKey"} +dotnet pack --configuration Release dotnet nuget push .\ModernRonin.ProjectRenamer\nupkg\ModernRonin.ProjectRenamer.$version.nupkg -s https://api.nuget.org/v3/index.json --api-key $nugetApiKey \ No newline at end of file