diff --git a/src/Ng/Arguments.cs b/src/Ng/Arguments.cs index af6ecb15e..472ea03b3 100644 --- a/src/Ng/Arguments.cs +++ b/src/Ng/Arguments.cs @@ -212,6 +212,7 @@ public static class Arguments public const string BatchSize = "batchSize"; public const string IndexFile = "indexFile"; public const string CursorFile = "cursorFile"; + public const string Driver = "driver"; #endregion } } \ No newline at end of file diff --git a/src/Ng/Jobs/LightningJob.cs b/src/Ng/Jobs/LightningJob.cs index b89d63fd3..a84405b56 100644 --- a/src/Ng/Jobs/LightningJob.cs +++ b/src/Ng/Jobs/LightningJob.cs @@ -2,14 +2,27 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Text; using System.Threading; using System.Threading.Tasks; +using Autofac; +using Autofac.Extensions.DependencyInjection; +using Microsoft.ApplicationInsights; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using NuGet.Jobs.Catalog2Registration; +using NuGet.Packaging.Core; +using NuGet.Protocol.Catalog; +using NuGet.Services; using NuGet.Services.Configuration; using NuGet.Services.Metadata.Catalog; +using NuGet.Services.Metadata.Catalog.Helpers; using NuGet.Services.Metadata.Catalog.Registration; using VDS.RDF; @@ -17,6 +30,9 @@ namespace Ng.Jobs { public class LightningJob : NgJob { + private const string GraphDriver = "graph"; + private const string JsonDriver = "json"; + public LightningJob(ITelemetryService telemetryService, ILoggerFactory loggerFactory) : base(telemetryService, loggerFactory) { @@ -44,18 +60,32 @@ public override string GetUsage() { var sw = new StringWriter(); - sw.WriteLine($"Usage: ng lightning -command prepare|strike"); + sw.WriteLine($"Usage: ng lightning -{Arguments.Command} prepare|strike"); sw.WriteLine(); sw.WriteLine($"The following arguments are supported on both lightning commands."); sw.WriteLine(); sw.WriteLine($" -{Arguments.ContentBaseAddress} "); sw.WriteLine($" The base address for package contents."); - sw.WriteLine(); sw.WriteLine($" -{Arguments.GalleryBaseAddress} "); sw.WriteLine($" The base address for gallery."); - sw.WriteLine(); + sw.WriteLine($" -{Arguments.ContentIsFlatContainer} true|false"); + sw.WriteLine($" Boolean to indicate if the registration blobs will have the content from the packages or flat container."); + sw.WriteLine($" The default value is false."); + sw.WriteLine($" -{Arguments.FlatContainerName} "); + sw.WriteLine($" It is required when the ContentIsFlatContainer flag is true."); + sw.WriteLine($" -{Arguments.StorageSuffix} "); + sw.WriteLine($" String to indicate the target storage suffix. If china for example core.chinacloudapi.cn needs to be used."); + sw.WriteLine($" The default value is 'core.windows.net'."); + sw.WriteLine($" -{Arguments.AllIconsInFlatContainer} true|false"); + sw.WriteLine($" Assume all icons (including external) are in flat container."); + sw.WriteLine($" The default value is false."); + sw.WriteLine($" -{Arguments.Driver} {GraphDriver}|{JsonDriver}"); + sw.WriteLine($" Specifies which implementation to use when building the hives."); + sw.WriteLine($" '{GraphDriver}' uses RDF and JSON-LD. '{JsonDriver}' uses simple JSON serialization."); + sw.WriteLine($" The default value is '{GraphDriver}'."); sw.WriteLine($" -{Arguments.Verbose} true|false"); sw.WriteLine($" Switch output verbosity on/off."); + sw.WriteLine($" The default value is false."); sw.WriteLine(); sw.WriteLine($" -{Arguments.StorageBaseAddress} "); sw.WriteLine($" Base address to write into registration blobs."); @@ -76,13 +106,6 @@ public override string GetUsage() sw.WriteLine($" Compressed only: Azure Storage account name."); sw.WriteLine($" -{Arguments.CompressedStorageContainer} "); sw.WriteLine($" Compressed only: Container to generate registrations in."); - sw.WriteLine($" -{Arguments.ContentIsFlatContainer} true|false"); - sw.WriteLine($" Boolean to indicate if the registration blobs will have the content from the packages or flat container."); - sw.WriteLine($" -{Arguments.FlatContainerName} "); - sw.WriteLine($" It is required when the ContentIsFlatContainer flag is true."); - sw.WriteLine($" -{Arguments.StorageSuffix} "); - sw.WriteLine($" String to indicate the target storage suffix. If china for example core.chinacloudapi.cn needs to be used."); - sw.WriteLine($" The default value is blob.core.windows.net."); sw.WriteLine(); sw.WriteLine($" -{Arguments.UseSemVer2Storage} true|false"); sw.WriteLine($" Enable or disable SemVer 2.0.0 registration blobs."); @@ -96,16 +119,16 @@ public override string GetUsage() sw.WriteLine($" SemVer 2.0.0 only: Container to generate registrations in."); sw.WriteLine(); sw.WriteLine($"The prepare command:"); - sw.WriteLine($" ng lightning -command prepare ..."); + sw.WriteLine($" ng lightning -{Arguments.Command} prepare ..."); sw.WriteLine(); - sw.WriteLine($" -outputFolder "); + sw.WriteLine($" -{Arguments.OutputFolder} "); sw.WriteLine($" The folder to generate files in."); - sw.WriteLine($" -templateFile "); + sw.WriteLine($" -{Arguments.TemplateFile} "); sw.WriteLine($" The lightning-template.txt that calls the strike command per batch."); sw.WriteLine($" This file can be found in Ng source directory."); sw.WriteLine($" -{Arguments.Source} "); sw.WriteLine($" The catalog index.json URL to work with."); - sw.WriteLine($" -batchSize 2000"); + sw.WriteLine($" -{Arguments.BatchSize} "); sw.WriteLine($" The batch size."); sw.WriteLine(); sw.WriteLine($"Traverses the given catalog and, using a template file and batch size,"); @@ -114,14 +137,12 @@ public override string GetUsage() sw.WriteLine($"in the catalog with their entries."); sw.WriteLine(); sw.WriteLine($"The strike command:"); - sw.WriteLine($" ng lightning -command strike ..."); + sw.WriteLine($" ng lightning -{Arguments.Command} strike ..."); sw.WriteLine(); - sw.WriteLine($" -indexFile "); + sw.WriteLine($" -{Arguments.IndexFile} "); sw.WriteLine($" Index file generated by the lightning prepare command."); - sw.WriteLine($" -cursorFile "); + sw.WriteLine($" -{Arguments.CursorFile} "); sw.WriteLine($" Cursor file containing range of the batch."); - sw.WriteLine($" [-{Arguments.AllIconsInFlatContainer} [true|false]]"); - sw.WriteLine($" Assume all icons (including external) are in flat container."); sw.WriteLine(); sw.WriteLine($"The lightning strike command is used by the batch files generated with"); sw.WriteLine($"the prepare command. It creates registrations for a given batch of catalog"); @@ -139,6 +160,8 @@ public override string GetUsage() private RegistrationStorageFactories _storageFactories; private ShouldIncludeRegistrationPackage _shouldIncludeSemVer2ForLegacyStorageFactory; private RegistrationMakerCatalogItem.PostProcessGraph _postProcessGraphForLegacyStorageFactory; + private bool _forceIconsFromFlatContainer; + private string _driver; private IDictionary _arguments; #endregion @@ -152,7 +175,6 @@ public override string GetUsage() #region Strike Arguments private string _indexFile; private string _cursorFile; - private bool _forceIconsFromFlatContainer; #endregion protected override void Init(IDictionary arguments, CancellationToken cancellationToken) @@ -182,10 +204,17 @@ protected override void Init(IDictionary arguments, Cancellation _storageFactories = CommandHelpers.CreateRegistrationStorageFactories(arguments, _verbose); _shouldIncludeSemVer2ForLegacyStorageFactory = RegistrationCollector.GetShouldIncludeRegistrationPackageForLegacyStorageFactory(_storageFactories.SemVer2StorageFactory); _postProcessGraphForLegacyStorageFactory = RegistrationCollector.GetPostProcessGraphForLegacyStorageFactory(_storageFactories.SemVer2StorageFactory); + _forceIconsFromFlatContainer = arguments.GetOrDefault(Arguments.AllIconsInFlatContainer); + _driver = arguments.GetOrDefault(Arguments.Driver, GraphDriver).ToLowerInvariant(); // We save the arguments because the "prepare" command generates "strike" commands. Some of the arguments // used by "prepare" should be used when executing "strike". _arguments = arguments; + if (_driver != GraphDriver && _driver != JsonDriver) + { + throw new NotSupportedException($"The lightning driver '{_driver}' is not supported."); + } + switch (_command.ToLowerInvariant()) { case "charge": @@ -212,7 +241,6 @@ private void InitStrike(IDictionary arguments) { _indexFile = arguments.GetOrThrow(Arguments.IndexFile); _cursorFile = arguments.GetOrThrow(Arguments.CursorFile); - _forceIconsFromFlatContainer = arguments.GetOrDefault(Arguments.AllIconsInFlatContainer); } protected override async Task RunInternalAsync(CancellationToken cancellationToken) @@ -303,6 +331,10 @@ private async Task PrepareAsync() await cursor.SaveAsync(CancellationToken.None); _log.WriteLine("Finished writing new cursor."); + // Ensure the SemVer 2.0.0 storage containers is created, if applicable. The gzipped storage account is + // created above when we write the cursor. + _storageFactories.SemVer2StorageFactory?.Create(); + // Write command files _log.WriteLine("Start preparing lightning reindex command files..."); @@ -326,48 +358,38 @@ private async Task PrepareAsync() var cursorTextFileName = "cursor" + batchNumber + ".txt"; using (var cursorCommandStreamWriter = new StreamWriter(Path.Combine(_outputFolder, cursorCommandFileName))) + using (var cursorTextStreamWriter = new StreamWriter(Path.Combine(_outputFolder, cursorTextFileName))) { - using (var cursorTextStreamWriter = new StreamWriter(Path.Combine(_outputFolder, cursorTextFileName))) - { - var commandStreamContents = templateFileContents; - - var replacements = _arguments - .Concat(new[] - { - new KeyValuePair("indexFile", indexFile), - new KeyValuePair("cursorFile", cursorTextFileName) - }); - - foreach (var replacement in replacements) - { - commandStreamContents = commandStreamContents - .Replace($"[{replacement.Key}]", replacement.Value); - } - - //the not required arguments need to be added only if they were passed in - //they cannot be hardcoded in the template - string optionalArguments = string.Empty; - if (_arguments.ContainsKey(Arguments.StorageSuffix)) - { - optionalArguments += $" -{Arguments.StorageSuffix} {_arguments[Arguments.StorageSuffix]}"; - } - - if (_arguments.ContainsKey(Arguments.ContentIsFlatContainer)) - { - optionalArguments += $" -{Arguments.ContentIsFlatContainer} {_arguments[Arguments.ContentIsFlatContainer]}"; - } + var commandStreamContents = templateFileContents; - if (_arguments.ContainsKey(Arguments.FlatContainerName)) + var replacements = _arguments + .Concat(new[] { - optionalArguments += $" -{Arguments.FlatContainerName} {_arguments[Arguments.FlatContainerName]}"; - } + new KeyValuePair("indexFile", indexFile), + new KeyValuePair("cursorFile", cursorTextFileName) + }); + foreach (var replacement in replacements) + { commandStreamContents = commandStreamContents - .Replace($"[{optionalArgumentsTemplate}]", optionalArguments); - - await cursorCommandStreamWriter.WriteLineAsync(commandStreamContents); - await cursorTextStreamWriter.WriteLineAsync(batchStart + "," + batchEnd); + .Replace($"[{replacement.Key}]", replacement.Value); } + + //the not required arguments need to be added only if they were passed in + //they cannot be hardcoded in the template + var optionalArguments = new StringBuilder(); + AppendOptionalArgument(optionalArguments, Arguments.ContentIsFlatContainer); + AppendOptionalArgument(optionalArguments, Arguments.FlatContainerName); + AppendOptionalArgument(optionalArguments, Arguments.StorageSuffix); + AppendOptionalArgument(optionalArguments, Arguments.AllIconsInFlatContainer); + AppendOptionalArgument(optionalArguments, Arguments.Driver); + AppendOptionalArgument(optionalArguments, Arguments.Verbose); + + commandStreamContents = commandStreamContents + .Replace($"[{optionalArgumentsTemplate}]", optionalArguments.ToString()); + + await cursorCommandStreamWriter.WriteLineAsync(commandStreamContents); + await cursorTextStreamWriter.WriteLineAsync(batchStart + "," + batchEnd); } batchNumber++; @@ -379,6 +401,20 @@ private async Task PrepareAsync() _log.WriteLine("to multiple machines and run the cursor*.cmd files in parallel."); } + private void AppendOptionalArgument(StringBuilder optionalArguments, string name) + { + if (_arguments.ContainsKey(name)) + { + if (optionalArguments.Length > 0) + { + optionalArguments.AppendLine(" ^"); + optionalArguments.Append(" "); + } + + optionalArguments.AppendFormat("-{0} {1}", name, _arguments[name]); + } + } + private async Task StrikeAsync() { _log.WriteLine("Start lightning strike for {0}...", _cursorFile); @@ -403,6 +439,9 @@ private async Task StrikeAsync() // Time to strike var httpMessageHandlerFactory = CommandHelpers.GetHttpMessageHandlerFactory(TelemetryService, _verbose); var collectorHttpClient = new CollectorHttpClient(httpMessageHandlerFactory()); + var serviceProvider = GetServiceProvider(); + var catalogClient = serviceProvider.GetRequiredService(); + var registrationUpdater = serviceProvider.GetRequiredService(); var startElement = string.Format("Element@{0}.", batchStart); var endElement = string.Format("Element@{0}.", batchEnd + 1); @@ -426,43 +465,28 @@ private async Task StrikeAsync() { var packageId = line.Split(new[] { ". " }, StringSplitOptions.None).Last().Trim(); - var sortedGraphs = new Dictionary(); + IStrike strike; + switch (_driver) + { + case JsonDriver: + strike = new JsonStrike(catalogClient, registrationUpdater, packageId); + break; + default: + strike = new GraphStrike(this, collectorHttpClient, packageId); + break; + } line = await indexStreamReader.ReadLineAsync(); while (!string.IsNullOrEmpty(line) && !line.Contains("Element@")) { - // Fetch graph for package version var url = line.TrimEnd(); - var graph = await collectorHttpClient.GetGraphAsync(new Uri(url)); - if (sortedGraphs.ContainsKey(url)) - { - sortedGraphs[url] = graph; - } - else - { - sortedGraphs.Add(url, graph); - } - - // To reduce memory footprint, we're flushing out large registrations - // in very small batches. - if (graph.Nodes.Count() > 3000 && sortedGraphs.Count >= 20) - { - // Process graphs - await ProcessGraphsAsync(packageId, sortedGraphs); - - // Destroy! - sortedGraphs = new Dictionary(); - } + await strike.ProcessCatalogLeafUrl(url); // Read next line line = await indexStreamReader.ReadLineAsync(); } - // Process graphs - if (sortedGraphs.Any()) - { - await ProcessGraphsAsync(packageId, sortedGraphs); - } + await strike.FinishAsync(); // Update cursor file so next time we have less work to do batchStart++; @@ -492,36 +516,257 @@ private static Task UpdateCursorFileAsync(string cursorFileName, int startIndex, return Task.FromResult(true); } - private async Task ProcessGraphsAsync(string packageId, IReadOnlyDictionary sortedGraphs) + private IServiceProvider GetServiceProvider() + { + var services = new ServiceCollection(); + services.AddSingleton(LoggerFactory); + services.AddLogging(); + services.Add(ServiceDescriptor.Scoped(typeof(IOptionsSnapshot<>), typeof(NonCachingOptionsSnapshot<>))); + services.AddSingleton(new TelemetryClient()); + + services.Configure(config => + { + config.LegacyBaseUrl = _arguments.GetOrDefault(Arguments.StorageBaseAddress); + config.LegacyStorageContainer = _arguments.GetOrDefault(Arguments.StorageContainer); + config.StorageConnectionString = GetConnectionString( + config.StorageConnectionString, + Arguments.StorageAccountName, + Arguments.StorageKeyValue, + Arguments.StorageSuffix); + + if (_arguments.GetOrDefault(Arguments.UseCompressedStorage)) + { + config.GzippedBaseUrl = _arguments.GetOrDefault(Arguments.CompressedStorageBaseAddress); + config.GzippedStorageContainer = _arguments.GetOrDefault(Arguments.CompressedStorageContainer); + config.StorageConnectionString = GetConnectionString( + config.StorageConnectionString, + Arguments.CompressedStorageAccountName, + Arguments.CompressedStorageKeyValue, + Arguments.StorageSuffix); + } + + if (_arguments.GetOrDefault(Arguments.UseSemVer2Storage)) + { + config.SemVer2BaseUrl = _arguments.GetOrDefault(Arguments.SemVer2StorageBaseAddress); + config.SemVer2StorageContainer = _arguments.GetOrDefault(Arguments.SemVer2StorageContainer); + config.StorageConnectionString = GetConnectionString( + config.StorageConnectionString, + Arguments.SemVer2StorageAccountName, + Arguments.SemVer2StorageKeyValue, + Arguments.StorageSuffix); + } + + config.GalleryBaseUrl = _arguments.GetOrThrow(Arguments.GalleryBaseAddress); + if (_arguments.GetOrThrow(Arguments.ContentIsFlatContainer)) + { + var contentBaseAddress = _arguments.GetOrThrow(Arguments.ContentBaseAddress); + var flatContainerName = _arguments.GetOrThrow(Arguments.FlatContainerName); + config.FlatContainerBaseUrl = contentBaseAddress.TrimEnd('/') + '/' + flatContainerName; + } + + config.EnsureSingleSnapshot = true; + }); + + services.AddCatalog2Registration(); + + var containerBuilder = new ContainerBuilder(); + containerBuilder.AddCatalog2Registration(); + containerBuilder.Populate(services); + + return new AutofacServiceProvider(containerBuilder.Build()); + } + + private string GetConnectionString( + string currentConnectionString, + string accountNameArgument, + string accountKeyArgument, + string endpointSuffixArgument) { - await RegistrationMaker.ProcessAsync( - new RegistrationKey(packageId), - sortedGraphs, - _shouldIncludeSemVer2ForLegacyStorageFactory, - _storageFactories.LegacyStorageFactory, - _postProcessGraphForLegacyStorageFactory, - new Uri(_contentBaseAddress), - new Uri(_galleryBaseAddress), - RegistrationCollector.PartitionSize, - RegistrationCollector.PackageCountThreshold, - _forceIconsFromFlatContainer, - TelemetryService, - CancellationToken.None); - - if (_storageFactories.SemVer2StorageFactory != null) + var builder = new StringBuilder(); + builder.Append("DefaultEndpointsProtocol=https;"); + builder.AppendFormat("AccountName={0};", _arguments.GetOrThrow(accountNameArgument)); + builder.AppendFormat("AccountKey={0};", _arguments.GetOrThrow(accountKeyArgument)); + builder.AppendFormat("EndpointSuffix={0}", _arguments.GetOrDefault(endpointSuffixArgument, "core.windows.net")); + + var connectionString = builder.ToString(); + if(currentConnectionString != null && currentConnectionString != connectionString) + { + throw new InvalidOperationException("The same connection string must be used for all hives."); + } + + return connectionString; + } + + private class GraphStrike : IStrike + { + private readonly CollectorHttpClient _collectorHttpClient; + private readonly string _packageId; + private readonly LightningJob _job; + private Dictionary _sortedGraphs; + + public GraphStrike(LightningJob job, CollectorHttpClient collectorHttpClient, string packageId) + { + _collectorHttpClient = collectorHttpClient; + _packageId = packageId; + _job = job; + _sortedGraphs = new Dictionary(); + } + + public async Task ProcessCatalogLeafUrl(string url) + { + // Fetch graph for package version + var graph = await _collectorHttpClient.GetGraphAsync(new Uri(url)); + if (_sortedGraphs.ContainsKey(url)) + { + _sortedGraphs[url] = graph; + } + else + { + _sortedGraphs.Add(url, graph); + } + + // To reduce memory footprint, we're flushing out large registrations + // in very small batches. + if (graph.Nodes.Count() > 3000 && _sortedGraphs.Count >= 20) + { + // Process graphs + await ProcessGraphsAsync(); + + // Destroy! + _sortedGraphs = new Dictionary(); + } + } + + public async Task FinishAsync() + { + // Process graphs + if (_sortedGraphs.Any()) + { + await ProcessGraphsAsync(); + } + } + + private async Task ProcessGraphsAsync() { await RegistrationMaker.ProcessAsync( - new RegistrationKey(packageId), - sortedGraphs, - _storageFactories.SemVer2StorageFactory, - new Uri(_contentBaseAddress), - new Uri(_galleryBaseAddress), + new RegistrationKey(_packageId), + _sortedGraphs, + _job._shouldIncludeSemVer2ForLegacyStorageFactory, + _job._storageFactories.LegacyStorageFactory, + _job._postProcessGraphForLegacyStorageFactory, + new Uri(_job._contentBaseAddress), + new Uri(_job._galleryBaseAddress), RegistrationCollector.PartitionSize, RegistrationCollector.PackageCountThreshold, - _forceIconsFromFlatContainer, - TelemetryService, + _job._forceIconsFromFlatContainer, + _job.TelemetryService, CancellationToken.None); + + if (_job._storageFactories.SemVer2StorageFactory != null) + { + await RegistrationMaker.ProcessAsync( + new RegistrationKey(_packageId), + _sortedGraphs, + _job._storageFactories.SemVer2StorageFactory, + new Uri(_job._contentBaseAddress), + new Uri(_job._galleryBaseAddress), + RegistrationCollector.PartitionSize, + RegistrationCollector.PackageCountThreshold, + _job._forceIconsFromFlatContainer, + _job.TelemetryService, + CancellationToken.None); + } + } + } + + private class JsonStrike : IStrike + { + private readonly ICatalogClient _catalogClient; + private readonly IRegistrationUpdater _updater; + private readonly string _packageId; + private readonly List _urls; + + public JsonStrike( + ICatalogClient catalogClient, + IRegistrationUpdater updater, + string packageId) + { + _catalogClient = catalogClient; + _updater = updater; + _packageId = packageId; + _urls = new List(); + } + + public async Task ProcessCatalogLeafUrl(string url) + { + _urls.Add(url); + if (_urls.Count >= 127) + { + await FinishAsync(); + } + } + + public async Task FinishAsync() + { + if (!_urls.Any()) + { + return; + } + + // Download all of the leaves. + var urlsToDownload = new ConcurrentBag(_urls); + var leaves = new ConcurrentBag(); + await ParallelAsync.Repeat( + async () => + { + await Task.Yield(); + while (urlsToDownload.TryTake(out var url)) + { + var leaf = await _catalogClient.GetLeafAsync(url); + leaves.Add(leaf); + } + }); + + // Build the input to hive updaters. + var entries = new List(); + var entryToLeaf = new Dictionary( + ReferenceEqualityComparer.Default); + foreach (var leaf in leaves) + { + if (!(leaf.IsPackageDelete() ^ leaf.IsPackageDetails())) + { + throw new InvalidOperationException("A catalog leaf must be either a package delete or a package details leaf."); + } + + var typeUri = leaf.IsPackageDetails() ? Schema.DataTypes.PackageDetails : Schema.DataTypes.PackageDelete; + + var catalogCommitItem = new CatalogCommitItem( + new Uri(leaf.Url), + leaf.CommitId, + leaf.CommitTimestamp.UtcDateTime, + types: null, + typeUris: new[] { typeUri }, + packageIdentity: new PackageIdentity(_packageId, leaf.ParsePackageVersion())); + + entries.Add(catalogCommitItem); + + if (leaf.IsPackageDetails()) + { + entryToLeaf.Add(catalogCommitItem, (PackageDetailsCatalogLeaf)leaf); + } + } + + // Update the hives. + await _updater.UpdateAsync(_packageId, entries, entryToLeaf); + + _urls.Clear(); } } + + private interface IStrike + { + Task ProcessCatalogLeafUrl(string url); + Task FinishAsync(); + } } } \ No newline at end of file diff --git a/src/Ng/Ng.csproj b/src/Ng/Ng.csproj index 4d9fd2b1b..18afd1ad2 100644 --- a/src/Ng/Ng.csproj +++ b/src/Ng/Ng.csproj @@ -123,6 +123,10 @@ {ddb34145-870f-42c3-9663-a9390cee1e35} NuGet.Indexing + + {5ABE8807-2209-4948-9FC5-1980A507C47A} + NuGet.Jobs.Catalog2Registration + {d44c2e89-2d98-44bd-8712-8ccbe4e67c9c} NuGet.Protocol.Catalog @@ -131,6 +135,10 @@ {1745a383-d0be-484b-81eb-27b20f6ac6c5} NuGet.Services.Metadata.Catalog.Monitoring + + {C3F9A738-9759-4B2B-A50D-6507B28A659B} + NuGet.Services.V3 + diff --git a/src/Ng/lightning-template.txt b/src/Ng/lightning-template.txt index 461431b0b..2cd5f6f48 100644 --- a/src/Ng/lightning-template.txt +++ b/src/Ng/lightning-template.txt @@ -11,7 +11,7 @@ start /w ng.exe lightning ^ -indexFile "[indexFile]" ^ -cursorFile "[cursorFile]" ^ -contentBaseAddress "[contentBaseAddress]" ^ - -verbose "[verbose]" ^ + -galleryBaseAddress "[galleryBaseAddress]" ^ -storageBaseAddress "[storageBaseAddress]" ^ -storageAccountName "[storageAccountName]" ^ -storageKeyValue "[storageKeyValue]" ^