diff --git a/.git-hooks/pre-commit b/.git-hooks/pre-commit
new file mode 100755
index 0000000..344f031
--- /dev/null
+++ b/.git-hooks/pre-commit
@@ -0,0 +1,8 @@
+#!/bin/bash
+
+FILES=$(git diff --cached --name-only --diff-filter=ACM "*.cs")
+if [ -n "$FILES" ]
+then
+ dotnet format ./pgutil.slnx --no-restore --include $FILES
+ echo "$FILES" | xargs git add
+fi
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 1df55ce..470d7a4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -360,3 +360,6 @@ MigrationBackup/
# Fody - auto-generated XML schema
FodyWeavers.xsd
/pgutil/Properties/launchSettings.json
+
+# Rider
+.idea/*
diff --git a/Directory.Build.props b/Directory.Build.props
new file mode 100644
index 0000000..131a516
--- /dev/null
+++ b/Directory.Build.props
@@ -0,0 +1,18 @@
+
+
+ net8.0;net10.0
+ enable
+ enable
+
+ Inedo
+ Inedo
+ ProGet
+ Copyright © Inedo 2026
+
+ https://github.com/Inedo/pgutil
+ https://github.com/Inedo/pgutil.git
+ git
+ MIT
+ proget
+
+
\ No newline at end of file
diff --git a/Directory.Packages.props b/Directory.Packages.props
new file mode 100644
index 0000000..bdb9ad6
--- /dev/null
+++ b/Directory.Packages.props
@@ -0,0 +1,13 @@
+
+
+ true
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Inedo.ProGet/BuildInfo.cs b/Inedo.ProGet/BuildInfo.cs
index 8943a9d..466f3ae 100644
--- a/Inedo.ProGet/BuildInfo.cs
+++ b/Inedo.ProGet/BuildInfo.cs
@@ -34,7 +34,7 @@ public sealed class BuildInfo
{
// Build version number (e.g. "1.0.0")
public required string Version { get; init; }
-
+
// Indicates whether the build is active
// * Default is "true"
// * Value is either "true" or "false"
diff --git a/Inedo.ProGet/BuildIssue.cs b/Inedo.ProGet/BuildIssue.cs
index 5fdbab1..bd7103b 100644
--- a/Inedo.ProGet/BuildIssue.cs
+++ b/Inedo.ProGet/BuildIssue.cs
@@ -43,7 +43,7 @@ public sealed class BuildIssue
// A list of reasons for the issue (e.g. "Vulnerability (PGV-2422512); No license detected")
public string? Detail { get; set; }
-
+
// The package URL of the package associated with the issue (e.g. "pkg:npm/express@4.18.2")
[JsonPropertyName("purl")]
public required string PUrl { get; set; }
diff --git a/Inedo.ProGet/FeedStorageType.cs b/Inedo.ProGet/FeedStorageType.cs
index 1018cb7..70a05d7 100644
--- a/Inedo.ProGet/FeedStorageType.cs
+++ b/Inedo.ProGet/FeedStorageType.cs
@@ -37,7 +37,7 @@ public sealed class FeedStorageType
// Name of the feed storage (e.g. "Local Disk", "Amazon S3")
public string? Name { get; init; }
-
+
// Description of the feed storage type (e.g. "Local file system path or network share.")
public string? Description { get; init; }
diff --git a/Inedo.ProGet/Inedo.ProGet.csproj b/Inedo.ProGet/Inedo.ProGet.csproj
index 7720b1b..5eeabc8 100644
--- a/Inedo.ProGet/Inedo.ProGet.csproj
+++ b/Inedo.ProGet/Inedo.ProGet.csproj
@@ -1,31 +1,19 @@
- net8.0;net10.0
- enable
- enable
true
true
CS1591
- MIT
- https://github.com/Inedo/pgutil
- https://github.com/Inedo/pgutil.git
- Inedo
- Inedo
- ProGet
- Copyright © Inedo 2026
- proget
- git
- A HTTP Client that wraps ProGet's HTTP Endpoints.
+ A HTTP Client that wraps ProGet's HTTP Endpoints.
-This is essentially the .NET-library version of pgutil, and is built from the pgutil GitHub code/repository.
+ This is essentially the .NET-library version of pgutil, and is built from the pgutil GitHub code/repository.
-To learn how to use the library, see [Getting Started with Inedo.NuGet](https://docs.inedo.com/docs/proget-reference-api#net-library-nuget-package).
+ To learn how to use the library, see [Getting Started with Inedo.NuGet](https://docs.inedo.com/docs/proget-reference-api#net-library-nuget-package).
-
-
-
+
+
+
-
-
+
+
-
+
\ No newline at end of file
diff --git a/Inedo.ProGet/PackageMetadata.cs b/Inedo.ProGet/PackageMetadata.cs
index b928ad0..977ef81 100644
--- a/Inedo.ProGet/PackageMetadata.cs
+++ b/Inedo.ProGet/PackageMetadata.cs
@@ -125,7 +125,7 @@ public sealed class PackageMetadataVulnerabilityAssessment
// Severity of the assessment (E.g. "W", "E")
public required string Severity { get; init; }
-
+
// Indicates whether the vulnerability is blocked
// * Values are either "true" or "false"
public bool Blocked { get; init; }
diff --git a/Inedo.ProGet/PackageVersionInfo.cs b/Inedo.ProGet/PackageVersionInfo.cs
index 52e342a..22c5d2d 100644
--- a/Inedo.ProGet/PackageVersionInfo.cs
+++ b/Inedo.ProGet/PackageVersionInfo.cs
@@ -38,34 +38,34 @@ public sealed class PackageVersionInfo
// PUrl of the package (see https://github.com/package-url/purl-spec/blob/master/PURL-TYPES.rst)
[JsonPropertyName("purl")]
public required string PUrl { get; init; }
-
+
// Group of the package identifier if the package type supports groups
public string? Group { get; init; }
-
+
// Unique name of the package
public required string Name { get; init; }
-
+
// Version of the package
public required string Version { get; init; }
-
+
// Additional fields specific to the type of package (see https://github.com/package-url/purl-spec/blob/master/PURL-TYPES.rst)
public string? Qualifier { get; init; }
-
+
// Total number of downloads of all versions of the package from the ProGet feed
public long TotalDownloads { get; init; }
-
+
// Number of downloads of the latest version of the package from the ProGet feed
public long Downloads { get; init; }
-
+
// Timestamp when the package was published to ProGet
public DateTime Published { get; init; }
-
+
// User which published the package to ProGet; this information may not always be available
public string? PublishedBy { get; init; }
-
+
// Size of the package file in bytes
public long Size { get; init; }
-
+
// Indicates whether the package is visible to searches (assume true when not specified)
public bool Listed { get; init; } = true;
diff --git a/Inedo.ProGet/ProGetAuthenticationType.cs b/Inedo.ProGet/ProGetAuthenticationType.cs
index 98a9ded..b5f70f9 100644
--- a/Inedo.ProGet/ProGetAuthenticationType.cs
+++ b/Inedo.ProGet/ProGetAuthenticationType.cs
@@ -1,8 +1,8 @@
namespace Inedo.ProGet;
-public enum ProGetAuthenticationType
-{
- None,
- ApiKey,
- UsernamePassword
+public enum ProGetAuthenticationType
+{
+ None,
+ ApiKey,
+ UsernamePassword
}
\ No newline at end of file
diff --git a/Inedo.ProGet/ProGetClient.cs b/Inedo.ProGet/ProGetClient.cs
index fb16742..973cd03 100644
--- a/Inedo.ProGet/ProGetClient.cs
+++ b/Inedo.ProGet/ProGetClient.cs
@@ -55,7 +55,7 @@ public async Task GetInstanceHealthAsync(CancellationToken can
{
using var response = await this.http.GetAsync("health", cancellationToken).ConfigureAwait(false);
using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
-
+
try
{
return (await JsonSerializer.DeserializeAsync(stream, ProGetApiJsonContext.Default.ProGetHealthInfo, cancellationToken).ConfigureAwait(false))!;
diff --git a/Inedo.ProGet/ProGetHealthInfo.cs b/Inedo.ProGet/ProGetHealthInfo.cs
index b01939d..b10ca0f 100644
--- a/Inedo.ProGet/ProGetHealthInfo.cs
+++ b/Inedo.ProGet/ProGetHealthInfo.cs
@@ -60,7 +60,7 @@ public sealed class ProGetHealthInfo
// Information on status of replication (if configured, else will be null).
public ReplicationStatusInfo? ReplicationStatus { get; init; }
-
+
// JSON Object used by ReplicationStatus property
// * Status property values will be either "OK" or "Error" if replication servers exist, else will be null
// * Error properties will return an error message if Status is "Error", else will be null
diff --git a/Inedo.ProGet/RetentionRule.cs b/Inedo.ProGet/RetentionRule.cs
index a4a826c..6307dbc 100644
--- a/Inedo.ProGet/RetentionRule.cs
+++ b/Inedo.ProGet/RetentionRule.cs
@@ -70,7 +70,7 @@ public sealed class RetentionRule
// * When "false" and "sizeTriggerKb" is set to non-null "n", retention is run when the entire feed size is greater than `n` kilobytes
// * This value is ignored when "sizeTriggerKb" is "null"
public bool SizeExclusive { get; set; }
-
+
// When set to "n", the retention rule always keeps versions that have been downloaded more than "n" times
public int? TriggerDownloadCount { get; set; }
diff --git a/Inedo.ProGet/ScaPermissionInfo.cs b/Inedo.ProGet/ScaPermissionInfo.cs
index c8cf2d5..7edacf9 100644
--- a/Inedo.ProGet/ScaPermissionInfo.cs
+++ b/Inedo.ProGet/ScaPermissionInfo.cs
@@ -1,4 +1,5 @@
namespace Inedo.ProGet;
+
public sealed class ScaPermissionInfo
{
public bool CanView { get; init; }
diff --git a/Inedo.ProGet/Scan/BomWriter.cs b/Inedo.ProGet/Scan/BomWriter.cs
index abd34f9..e993367 100644
--- a/Inedo.ProGet/Scan/BomWriter.cs
+++ b/Inedo.ProGet/Scan/BomWriter.cs
@@ -74,7 +74,7 @@ public void AddPackage(string? group, string name, string version, string type,
var fullName = string.IsNullOrEmpty(group) ? Uri.EscapeDataString(name) : $"{Uri.EscapeDataString(group)}/{Uri.EscapeDataString(name)}";
- this.writer.WriteElementString("purl", ns, $"pkg:{type}/{fullName}@{version}{(string.IsNullOrWhiteSpace(qualifier) ? string.Empty : ("?"+qualifier))}");
+ this.writer.WriteElementString("purl", ns, $"pkg:{type}/{fullName}@{version}{(string.IsNullOrWhiteSpace(qualifier) ? string.Empty : ("?" + qualifier))}");
this.writer.WriteEndElement(); // component
}
diff --git a/Inedo.ProGet/Scan/ComposerDependencyScanner.cs b/Inedo.ProGet/Scan/ComposerDependencyScanner.cs
index 3955967..0113417 100644
--- a/Inedo.ProGet/Scan/ComposerDependencyScanner.cs
+++ b/Inedo.ProGet/Scan/ComposerDependencyScanner.cs
@@ -2,6 +2,7 @@
using Inedo.DependencyScan;
namespace Inedo.ProGet.Scan;
+
internal class ComposerDependencyScanner(CreateDependencyScannerArgs args) : DependencyScanner(args)
{
public override DependencyScannerType Type => DependencyScannerType.Composer;
@@ -24,13 +25,13 @@ public override async Task> ResolveDependenc
using var lockFileStream = await this.FileSystem.OpenReadAsync(composerLockFile.FullName, cancellationToken).ConfigureAwait(false);
using var doc = await JsonDocument.ParseAsync(lockFileStream, cancellationToken: cancellationToken).ConfigureAwait(false);
- if(!doc.RootElement.TryGetProperty("packages", out var packages) && packages.ValueKind != JsonValueKind.Array)
+ if (!doc.RootElement.TryGetProperty("packages", out var packages) && packages.ValueKind != JsonValueKind.Array)
throw new DependencyScannerException($"composer.lock at {searchDirectory} does not contain any packages");
var dependencies = new List();
dependencies.AddRange(readDependencies(packages));
-
- if(this.CreateArgs.IncludeDevDependencies && doc.RootElement.TryGetProperty("packages-dev", out var devPackages) && devPackages.ValueKind == JsonValueKind.Array)
+
+ if (this.CreateArgs.IncludeDevDependencies && doc.RootElement.TryGetProperty("packages-dev", out var devPackages) && devPackages.ValueKind == JsonValueKind.Array)
dependencies.AddRange(readDependencies(devPackages));
projects.Add(new ScannedProject(projectName.GetString()!, dependencies.Distinct()));
diff --git a/Inedo.ProGet/Scan/DependencyScanner.cs b/Inedo.ProGet/Scan/DependencyScanner.cs
index 9dfba36..92becf4 100644
--- a/Inedo.ProGet/Scan/DependencyScanner.cs
+++ b/Inedo.ProGet/Scan/DependencyScanner.cs
@@ -113,7 +113,7 @@ private static async Task GetImplicitFileAsync(DependencyScannerType sca
{
var files = await SourceFileSystem.Default.FindFilesAsync(folder, "requirements.txt", true, cancellationToken).ToListAsync(cancellationToken);
if (files.Count == 1)
- return files[0].FullName;
+ return files[0].FullName;
if (files.Count > 1)
throw new DependencyScannerException("Multiple requirements.txt files found in directory. Specify which requirements.txt file you would like to scan be using \"--input\" argument.");
@@ -124,22 +124,22 @@ private static async Task GetImplicitFileAsync(DependencyScannerType sca
private static async Task<(DependencyScannerType scannerType, string filePath)> GetImplicitTypeAsync(ISourceFileSystem fileSystem, string fileName, CancellationToken cancellationToken = default)
{
- if(fileSystem.IsDirectoryAsync(fileName))
+ if (fileSystem.IsDirectoryAsync(fileName))
{
var files = await fileSystem.FindFilesAsync(fileName, "*.slnx", true, cancellationToken).ToListAsync(cancellationToken);
- if(files.Count == 1)
+ if (files.Count == 1)
return (scannerType: DependencyScannerType.NuGet, filePath: files[0].FullName);
else if (files.Count > 1)
throw new DependencyScannerException("Multiple solution files found in directory. Specify which solution file you would like to scan be using \"--input\" argument.");
files = await fileSystem.FindFilesAsync(fileName, "*.sln", true, cancellationToken).ToListAsync(cancellationToken);
- if(files.Count == 1)
+ if (files.Count == 1)
return (scannerType: DependencyScannerType.NuGet, filePath: files[0].FullName);
else if (files.Count > 1)
throw new DependencyScannerException("Multiple solution files found in directory. Specify which solution file you would like to scan be using \"--input\" argument.");
files = await fileSystem.FindFilesAsync(fileName, "*.csproj", true, cancellationToken).ToListAsync(cancellationToken);
- if(files.Count == 1)
+ if (files.Count == 1)
return (scannerType: DependencyScannerType.NuGet, filePath: files[0].FullName);
else if (files.Count > 1)
throw new DependencyScannerException("Multiple project files found in directory. Specify which project file you would like to scan be using \"--input\" argument.");
@@ -149,15 +149,15 @@ private static async Task GetImplicitFileAsync(DependencyScannerType sca
return (scannerType: DependencyScannerType.Npm, filePath: files[0].FullName);
files = await fileSystem.FindFilesAsync(fileName, "package-lock.json", true, cancellationToken).ToListAsync(cancellationToken);
- if(files.Count > 0)
+ if (files.Count > 0)
return (scannerType: DependencyScannerType.Npm, filePath: fileName);
files = await fileSystem.FindFilesAsync(fileName, "Cargo.lock", true, cancellationToken).ToListAsync(cancellationToken);
- if(files.Count > 0)
+ if (files.Count > 0)
return (scannerType: DependencyScannerType.Cargo, filePath: fileName);
-
+
files = await fileSystem.FindFilesAsync(fileName, "composer.lock", true, cancellationToken).ToListAsync(cancellationToken);
- if(files.Count > 0)
+ if (files.Count > 0)
return (scannerType: DependencyScannerType.Composer, filePath: fileName);
files = await fileSystem.FindFilesAsync(fileName, "requirements.txt", true, cancellationToken).ToListAsync(cancellationToken);
diff --git a/Inedo.ProGet/Scan/NpmDependencyScanner.cs b/Inedo.ProGet/Scan/NpmDependencyScanner.cs
index c0558ce..59421c6 100644
--- a/Inedo.ProGet/Scan/NpmDependencyScanner.cs
+++ b/Inedo.ProGet/Scan/NpmDependencyScanner.cs
@@ -17,7 +17,7 @@ public override async Task> ResolveDependenc
// Handle pnpm lock file
if (this.SourcePath.EndsWith("pnpm-lock.yaml"))
{
- if(!await this.FileSystem.FileExistsAsync(this.SourcePath, cancellationToken))
+ if (!await this.FileSystem.FileExistsAsync(this.SourcePath, cancellationToken))
throw new FileNotFoundException("The specified pnpm lock file was not found.", this.SourcePath);
using var stream = await this.FileSystem.OpenReadAsync(this.SourcePath, cancellationToken).ConfigureAwait(false);
using var reader = new StreamReader(stream);
@@ -32,7 +32,7 @@ public override async Task> ResolveDependenc
if (string.IsNullOrEmpty(projectName))
throw new InvalidOperationException($"Unable to determine project name from package.json in directory: {this.FileSystem.GetDirectoryName(this.SourcePath)}");
-
+
var dependencies = ReadPnpmLockFile(rootNode).ToList();
projects.Add(new ScannedProject(projectName, dependencies));
}
@@ -46,7 +46,7 @@ public override async Task> ResolveDependenc
? this.FileSystem.GetDirectoryName(this.SourcePath)
: this.SourcePath;
- if(await this.FileSystem.FindFilesAsync(searchDirectory, "pnpm-lock.yaml", true, cancellationToken).AnyAsync(cancellationToken: cancellationToken))
+ if (await this.FileSystem.FindFilesAsync(searchDirectory, "pnpm-lock.yaml", true, cancellationToken).AnyAsync(cancellationToken: cancellationToken))
Console.WriteLine("Warning: pnpm-lock.yaml file detected in the scan directory. To parse pNPM lock files, specify the the pnpm-lock.yaml file in the Source Path argument.");
await foreach (var packageLockFile in this.FileSystem.FindFilesAsync(searchDirectory, "package-lock.json", !this.SourcePath.EndsWith("package-lock.json"), cancellationToken))
@@ -64,7 +64,7 @@ public override async Task> ResolveDependenc
return projects;
}
-
+
}
private IEnumerable ReadPackageLockFile(JsonDocument doc)
diff --git a/Inedo.ProGet/SecurityTask.cs b/Inedo.ProGet/SecurityTask.cs
index a2a03c0..c187f58 100644
--- a/Inedo.ProGet/SecurityTask.cs
+++ b/Inedo.ProGet/SecurityTask.cs
@@ -36,10 +36,10 @@ public sealed class SecurityTask
public required string Name { get; init; }
// Description of the task (e.g. "Allows basic feed access")
- public string? Description { get; init; }
+ public string? Description { get; init; }
// Indicates if the task is feed-scoped ("true") or global ("false")
- public bool FeedScoped { get; init; }
+ public bool FeedScoped { get; init; }
// The task attributes that the task enables (e.g. ["Feeds_ViewFeed", "Feeds_DownloadPackage"])
public required string[] Attributes { get; init; }
diff --git a/README.md b/README.md
index 5b50bbb..87ca0b5 100644
--- a/README.md
+++ b/README.md
@@ -19,6 +19,10 @@ It's built from the pgutil GitHub code/repository and is mostly a HTTP Client th
To learn how to use the library, see [Getting Started with Inedo.NuGet](https://docs.inedo.com/docs/proget-reference-api#net-library-nuget-package).
+# Samples
+
+Sample projects and examples are available in the [`samples`](samples) directory.
+
## Pull Requests & Issues
We're very open to your feedback and ideas for improving `pgutil` and `Inedo.ProGet`!
diff --git a/global.json b/global.json
new file mode 100644
index 0000000..5949319
--- /dev/null
+++ b/global.json
@@ -0,0 +1,6 @@
+{
+ "sdk": {
+ "version": "10.0.103",
+ "rollForward": "latestFeature"
+ }
+}
diff --git a/pgutil/ApiKeys/Create/SystemCommand.cs b/pgutil/ApiKeys/Create/SystemCommand.cs
index aadff05..ef96c3c 100644
--- a/pgutil/ApiKeys/Create/SystemCommand.cs
+++ b/pgutil/ApiKeys/Create/SystemCommand.cs
@@ -39,7 +39,7 @@ public static async Task ExecuteAsync(CommandContext context, CancellationT
var apis = apiValue.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
- if (apis.Length == 0
+ if (apis.Length == 0
|| apis.Contains("full-control") && apis.Length > 1
|| apis.Any(a => a != "feeds" && a != "sca" && a != "sbom-upload" && a != "full-control"))
{
@@ -66,7 +66,7 @@ private sealed class ApisOption : IConsoleOption
{
public static bool Required => false;
public static string Name => "--apis";
- public static string Description =>
+ public static string Description =>
$"Specifies the individual APIs to give access to when creating a system API key. " +
$"Value is either full-control or a comma-separated list of any combination of: feeds, sca, sbom-upload";
}
diff --git a/pgutil/ApiKeys/ListCommand.cs b/pgutil/ApiKeys/ListCommand.cs
index 9975a49..efec1a8 100644
--- a/pgutil/ApiKeys/ListCommand.cs
+++ b/pgutil/ApiKeys/ListCommand.cs
@@ -25,7 +25,7 @@ public static async Task ExecuteAsync(CommandContext context, CancellationT
{
count++;
CM.WriteLine(key.DisplayName ?? "(unnamed key)");
-
+
var data = new List<(string, string)>
{
(" Id:", key.Id.ToString()!),
diff --git a/pgutil/Assets/Metadata/MetadataCommand.cs b/pgutil/Assets/Metadata/MetadataCommand.cs
index a696170..cc96a67 100644
--- a/pgutil/Assets/Metadata/MetadataCommand.cs
+++ b/pgutil/Assets/Metadata/MetadataCommand.cs
@@ -24,6 +24,6 @@ private sealed class PathOption : IConsoleOption
public static string Name => "--path";
public static string Description => "Path of item to inspect";
}
- }
+ }
}
}
diff --git a/pgutil/Builds/BuildsCommand.cs b/pgutil/Builds/BuildsCommand.cs
index d91bc07..78beb9a 100644
--- a/pgutil/Builds/BuildsCommand.cs
+++ b/pgutil/Builds/BuildsCommand.cs
@@ -92,7 +92,7 @@ private sealed class ScannerTypeOption : IConsoleOption
public static string Description => "Type of project scanner to use; auto, npm, NuGet, PyPI, Conda, Composer, or Cargo (default=auto)";
public static string DefaultValue => "auto";
}
-
+
private sealed class DoNotAuditFlag : IConsoleFlagOption
{
public static string Name => "--noaudit";
diff --git a/pgutil/Builds/CreateCommand.cs b/pgutil/Builds/CreateCommand.cs
index 3e8ab23..8a871a5 100644
--- a/pgutil/Builds/CreateCommand.cs
+++ b/pgutil/Builds/CreateCommand.cs
@@ -35,16 +35,16 @@ public static async Task ExecuteAsync(CommandContext context, CancellationT
else if (context.HasFlag())
active = false;
- _ = await client.CreateOrUpdateBuildAsync(
- new Inedo.ProGet.CreateOrUpdateBuildOptions
- {
- Project = context.GetOption(),
- Version = context.GetOption(),
- Active = active,
- Stage = context.GetOptionOrDefault()
- },
- cancellationToken
- );
+ _ = await client.CreateOrUpdateBuildAsync(
+ new Inedo.ProGet.CreateOrUpdateBuildOptions
+ {
+ Project = context.GetOption(),
+ Version = context.GetOption(),
+ Active = active,
+ Stage = context.GetOptionOrDefault()
+ },
+ cancellationToken
+ );
Console.WriteLine("Build created.");
return 0;
diff --git a/pgutil/Builds/ScanCommand.cs b/pgutil/Builds/ScanCommand.cs
index dac27d8..c8f9fca 100644
--- a/pgutil/Builds/ScanCommand.cs
+++ b/pgutil/Builds/ScanCommand.cs
@@ -36,14 +36,14 @@ public static void Configure(ICommandBuilder builder)
public static async Task ExecuteAsync(CommandContext context, CancellationToken cancellationToken)
{
- context.TryGetOption(out var input);
+ context.TryGetOption(out var input);
CM.WriteLine("Scanning for dependencies in ", new TextSpan(input ?? Environment.CurrentDirectory, ConsoleColor.White), "...");
var scannerType = Enum.TryParse(context.GetOption(), out var _type) ? _type : DependencyScannerType.Auto;
var scanner = await DependencyScanner.GetScannerAsync(new CreateDependencyScannerArgs(
- input ?? string.Empty,
- SourceFileSystem.Default,
- IncludeProjectReferences: context.HasFlag(),
+ input ?? string.Empty,
+ SourceFileSystem.Default,
+ IncludeProjectReferences: context.HasFlag(),
DoNotScanNodeModules: context.HasFlag(),
IncludeDevDependencies: context.HasFlag()
), scannerType);
@@ -66,7 +66,7 @@ public static async Task ExecuteAsync(CommandContext context, CancellationT
await client.PublishSbomAsync(projects, consumer, context.GetOption(), scanner.Type.ToString().ToLowerInvariant(), cancellationToken);
CM.WriteLine("SBOM published.");
- if(context.HasFlag())
+ if (context.HasFlag())
return 0;
return await ExecuteAudit(client, consumer, cancellationToken);
@@ -75,8 +75,8 @@ public static async Task ExecuteAsync(CommandContext context, CancellationT
//Copy paste from AuditCommand.cs
private static async Task ExecuteAudit(ProGetClient client, PackageConsumer consumer, CancellationToken cancellationToken)
{
- try
- {
+ try
+ {
var project = consumer.Name;
var build = consumer.Version;
@@ -149,7 +149,7 @@ private static async Task ExecuteAudit(ProGetClient client, PackageConsumer
CM.WriteLine(" ", $"{v.Id} {v.Title}");
if (v.Category.HasValue)
CM.Write($" Category {v.Category} ({v.Assessment})");
- if(v.Score.HasValue)
+ if (v.Score.HasValue)
CM.Write($" Score {v.Score}");
}
}
@@ -176,7 +176,7 @@ private static async Task ExecuteAudit(ProGetClient client, PackageConsumer
return -1;
}
}
- catch (ProGetApiException ex) when(ex.StatusCode == HttpStatusCode.TooManyRequests)
+ catch (ProGetApiException ex) when (ex.StatusCode == HttpStatusCode.TooManyRequests)
{
CM.WriteError("ProGet Basic rate limit exceeded.");
return 429;
diff --git a/pgutil/Config/PgUtilSource.cs b/pgutil/Config/PgUtilSource.cs
index bb279e3..4fe32b9 100644
--- a/pgutil/Config/PgUtilSource.cs
+++ b/pgutil/Config/PgUtilSource.cs
@@ -15,7 +15,7 @@ public ProGetClient GetProGetClient()
return new(this.Url, this.Token);
else if (!string.IsNullOrEmpty(this.Username))
return new(this.Url, this.Username, this.Password ?? string.Empty);
- else
+ else
return new ProGetClient(this.Url);
}
public PgUtilSource Obfuscate()
diff --git a/pgutil/Feeds/CreateCommand.cs b/pgutil/Feeds/CreateCommand.cs
index 40c2c9e..c0431a4 100644
--- a/pgutil/Feeds/CreateCommand.cs
+++ b/pgutil/Feeds/CreateCommand.cs
@@ -46,7 +46,7 @@ private sealed class TypeOption : IConsoleOption
public static bool Required => true;
public static string Name => "--type";
public static string Description => "Type of the feed to create";
- public static string[] ValidValues => ["NuGet", "Chocolatey", "npm", "Bower", "Maven", "Universal", "PowerShell", "Docker", "RubyGems", "VSIX", "Debian", "PyPI", "Helm", "RPM", "Conda", "APK", "CRAN", "Asset" ];
+ public static string[] ValidValues => ["NuGet", "Chocolatey", "npm", "Bower", "Maven", "Universal", "PowerShell", "Docker", "RubyGems", "VSIX", "Debian", "PyPI", "Helm", "RPM", "Conda", "APK", "CRAN", "Asset"];
public static bool WarnWhenInvalidValue => true;
}
}
diff --git a/pgutil/Licenses/InfoCommand.cs b/pgutil/Licenses/InfoCommand.cs
index 9b3b1d0..e3396ec 100644
--- a/pgutil/Licenses/InfoCommand.cs
+++ b/pgutil/Licenses/InfoCommand.cs
@@ -38,7 +38,7 @@ public static async Task ExecuteAsync(CommandContext context, CancellationT
Console.WriteLine("Detection:");
if (license.Spdx is not null)
Console.WriteLine($" SPDX: {string.Join(", ", license.Spdx)}");
-
+
if (license.Urls is not null)
{
Console.WriteLine(" Url:");
diff --git a/pgutil/Packages/UploadCommand.cs b/pgutil/Packages/UploadCommand.cs
index f845f7d..4ee2dc4 100644
--- a/pgutil/Packages/UploadCommand.cs
+++ b/pgutil/Packages/UploadCommand.cs
@@ -131,7 +131,7 @@ Stream getSource(out string? fileName)
if (!inputFileName.Contains('*'))
return [inputFileName];
- var fullPath = Path.GetFullPath(inputFileName);
+ var fullPath = Path.GetFullPath(inputFileName);
int firstWildcardIndex = FirstWildcardRegex().Match(fullPath).Index;
var rootPath = fullPath[..firstWildcardIndex];
var wildcardPart = fullPath[(firstWildcardIndex)..];
diff --git a/pgutil/Security/Users/DeleteCommand.cs b/pgutil/Security/Users/DeleteCommand.cs
index 157a703..92c79de 100644
--- a/pgutil/Security/Users/DeleteCommand.cs
+++ b/pgutil/Security/Users/DeleteCommand.cs
@@ -16,7 +16,7 @@ private sealed class DeleteCommand : IConsoleCommand
$> pgutil security users delete --username="John Smith"
For more information, see: https://docs.inedo.com/docs/proget/api/security/users/delete
- """;
+ """;
public static void Configure(ICommandBuilder builder)
{
diff --git a/pgutil/Security/Users/EditCommand.cs b/pgutil/Security/Users/EditCommand.cs
index 685f49b..805d77d 100644
--- a/pgutil/Security/Users/EditCommand.cs
+++ b/pgutil/Security/Users/EditCommand.cs
@@ -20,7 +20,7 @@ private sealed class EditCommand : IConsoleCommand
For more information, see: https://docs.inedo.com/docs/proget/api/security/users/edit
""";
-
+
public static void Configure(ICommandBuilder builder)
{
builder.WithOption()
diff --git a/pgutil/Sources/ListCommand.cs b/pgutil/Sources/ListCommand.cs
index d2a7271..84ffa6e 100644
--- a/pgutil/Sources/ListCommand.cs
+++ b/pgutil/Sources/ListCommand.cs
@@ -38,7 +38,7 @@ 2. MyPackages (unapproved-nuget)
{
var s = sources[i];
- CM.Write(ConsoleColor.White, $"{i+1}. {s.Name}");
+ CM.Write(ConsoleColor.White, $"{i + 1}. {s.Name}");
if (!string.IsNullOrEmpty(s.DefaultFeed))
CM.Write(new TextSpan(" ("), new TextSpan(s.DefaultFeed, ConsoleColor.White), new TextSpan(" feed)"));
diff --git a/pgutil/Upack/UpackCommand.cs b/pgutil/Upack/UpackCommand.cs
index f32ba5a..ba791e9 100644
--- a/pgutil/Upack/UpackCommand.cs
+++ b/pgutil/Upack/UpackCommand.cs
@@ -78,7 +78,7 @@ static void ExtractItems(Stream packgeStream, string targetPath, bool overwrite)
try
{
- if(targetEntryPath.EndsWith("\\") || targetEntryPath.EndsWith("/"))
+ if (targetEntryPath.EndsWith("\\") || targetEntryPath.EndsWith("/"))
Directory.CreateDirectory(targetEntryPath);
else
entry.ExtractToFile(targetEntryPath, overwrite);
diff --git a/pgutil/pgutil.csproj b/pgutil/pgutil.csproj
index 3eaf572..102aa3c 100644
--- a/pgutil/pgutil.csproj
+++ b/pgutil/pgutil.csproj
@@ -1,31 +1,20 @@
-
- Exe
- net8.0;net10.0
- enable
- enable
- PgUtil
- Inedo
- Inedo
- ProGet
- Copyright © Inedo 2026
- CLI for ProGet's APIs.
- https://github.com/Inedo/pgutil
- https://github.com/Inedo/pgutil.git
- MIT
- proget
- README.md
- true
- true
- 0.0.0
-
-
-
-
-
-
-
-
-
-
-
+
+ Exe
+ PgUtil
+ CLI for ProGet's APIs.
+ README.md
+ true
+ true
+ 0.0.0
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/ProGetBootstrap.cs b/samples/ProGetBootstrap.cs
new file mode 100644
index 0000000..a44e148
--- /dev/null
+++ b/samples/ProGetBootstrap.cs
@@ -0,0 +1,131 @@
+#!/usr/bin/dotnet run
+
+#:sdk Microsoft.NET.Sdk
+
+#:package Inedo.ProGet@2.1.3
+
+#:property JsonSerializerIsReflectionEnabledByDefault=true
+
+using Inedo.ProGet;
+
+Console.WriteLine($"Starting ProGet bootstrap");
+
+var client = new ProGetClient("http://localhost:8080", "Admin", "admin");
+
+var groups = client.ListUserGroups();
+foreach (var group in DesiredState.Groups)
+{
+ if (await groups.FirstOrDefaultAsync(g => g.Name.Equals(group.Name)) != null)
+ {
+ await client.UpdateUserGroupAsync(group);
+ }
+ else
+ {
+ await client.CreateUserGroupAsync(group);
+ }
+}
+
+var users = client.ListUsersAsync();
+foreach (var user in DesiredState.Users)
+{
+ if (await users.FirstOrDefaultAsync(u => u.Name.Equals(user.Name)) != null)
+ {
+ await client.UpdateUserAsync(user);
+ }
+ else
+ {
+ await client.CreateUserAsync(user);
+ }
+}
+
+var connectors = client.ListConnectorsAsync();
+foreach (var connector in DesiredState.Connectors)
+{
+ if (await connectors.FirstOrDefaultAsync(c => c.Name.Equals(connector.Name)) != null)
+ {
+ await client.UpdateConnectorAsync(connector.Name, connector);
+ }
+ else
+ {
+ await client.CreateConnectorAsync(connector);
+ }
+}
+
+var feeds = client.ListFeedsAsync();
+foreach (var feed in DesiredState.Feeds)
+{
+ if (await feeds.FirstOrDefaultAsync(f => f.Name!.Equals(feed.Name)) != null)
+ {
+ await client.UpdateFeedAsync(feed.Name!, feed);
+ }
+ else
+ {
+ await client.CreateFeedAsync(feed.Name!, feed.FeedType!);
+ await client.UpdateFeedAsync(feed.Name!, feed);
+ }
+}
+
+var settings = client.ListSettingsAsync();
+foreach (var setting in DesiredState.Settings)
+{
+ await client.SetSettingAsync(setting.Name, setting.Value);
+}
+
+Console.WriteLine("ProGet bootstrap completed successfully.");
+
+public static class DesiredState
+{
+ public static IReadOnlyList Users { get; } =
+ [
+ new SecurityUser {
+ Name = "svc-ci",
+ DisplayName = "CI Service Account",
+ Email = "svc-ci@example.com",
+ Password = "svc-ci",
+ Groups = ["proget-admins"],
+ },
+ ];
+
+ public static IReadOnlyList Groups { get; } =
+ [
+ new SecurityGroup {
+ Name = "proget-admins",
+ },
+ ];
+
+ public static IReadOnlyList Connectors { get; } =
+ [
+ new ProGetConnector {
+ Name = "nuget-org",
+ FeedType = "nuget",
+ Url = "https://api.nuget.org/v3/index.json",
+ Timeout = 30,
+ MetadataCacheEnabled = true,
+ },
+ ];
+
+ public static IReadOnlyList Feeds { get; } =
+ [
+ new ProGetFeed {
+ Name = "nuget-internal",
+ FeedType = "NuGet",
+ Description = "Internal NuGet packages with nuget.org connector",
+ Active = true,
+ UseApiV3 = true,
+ Connectors = ["nuget-org"],
+ RetentionRulesEnabled = true,
+ VulnerabilitiesEnabled = true,
+ PackageStatisticsEnabled = true,
+ },
+ ];
+
+ public static IReadOnlyList Settings { get; } =
+ [
+ new SettingsInfo {
+ Name = "Web.BaseUrl",
+ Value = "http://localhost:8080",
+ Description = "Full root URL for this installation of ProGet. This should start with http:// or https://",
+ ValueType = SettingsInfoValueType.Text,
+ },
+ ];
+}
\ No newline at end of file
diff --git a/samples/README.md b/samples/README.md
new file mode 100644
index 0000000..302a1d6
--- /dev/null
+++ b/samples/README.md
@@ -0,0 +1,22 @@
+# Samples
+
+## Start ProGet
+
+Before running the samples, start a local ProGet instance with your preferred container engine.
+
+For example:
+
+```bash
+podman compose up
+```
+
+## Run a sample
+
+> [!IMPORTANT]
+> The samples require a running ProGet instance with a valid license key configured.
+
+After ProGet is running and initial setup is complete, run the sample with:
+
+```bash
+dotnet run ProGetBootstrap.cs
+```
\ No newline at end of file
diff --git a/samples/compose.yml b/samples/compose.yml
new file mode 100644
index 0000000..63bdae3
--- /dev/null
+++ b/samples/compose.yml
@@ -0,0 +1,16 @@
+services:
+ proget:
+ image: proget.inedo.com/productimages/inedo/proget:latest
+ container_name: proget
+ restart: unless-stopped
+ ports:
+ - "8080:80"
+ volumes:
+ - proget-packages:/var/proget/packages
+ - proget-database:/var/proget/database
+ - proget-backups:/var/proget/backups
+
+volumes:
+ proget-packages:
+ proget-database:
+ proget-backups:
\ No newline at end of file