diff --git a/.github/workflows/build-gate.yml b/.github/workflows/build-gate.yml
index c1035934b..b823511e3 100644
--- a/.github/workflows/build-gate.yml
+++ b/.github/workflows/build-gate.yml
@@ -61,6 +61,10 @@ jobs:
needs: gate
uses: ./.github/workflows/artifactoryTests.yml
secrets: inherit
+ choco:
+ needs: gate
+ uses: ./.github/workflows/chocoTests.yml
+ secrets: inherit
conan:
needs: gate
uses: ./.github/workflows/conanTests.yml
diff --git a/.github/workflows/chocoTests.yml b/.github/workflows/chocoTests.yml
new file mode 100644
index 000000000..93c5a58fc
--- /dev/null
+++ b/.github/workflows/chocoTests.yml
@@ -0,0 +1,75 @@
+name: Chocolatey Tests
+
+on:
+ workflow_call:
+ workflow_dispatch:
+
+jobs:
+ Choco-Tests:
+ name: Chocolatey tests (windows)
+ # Chocolatey is a Windows-only package manager, so unlike the other package
+ # manager suites this one does not run on a matrix.
+ runs-on: windows-2022
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v7
+ with:
+ ref: ${{ github.event.pull_request.head.sha || github.ref }}
+ # Safe: this workflow only runs after human approval via the build-gate environment.
+ allow-unsafe-pr-checkout: true
+
+ - name: Setup FastCI
+ uses: jfrog-fastci/fastci@v1
+ with:
+ github_token: ${{ secrets.GITHUB_TOKEN }}
+ fastci_otel_token: ${{ secrets.FASTCI_TOKEN }}
+
+ # Chocolatey is pre-installed on GitHub-hosted Windows runners (2.7.4 on windows-2022).
+ # 'jf setup choco' needs 2.0+ for the 'apikey add' verb - on 1.x, a bare 'choco apikey'
+ # only lists keys - so fail here with a clear message rather than letting every setup
+ # test fail later on an argument-parsing error.
+ - name: Verify Chocolatey
+ shell: pwsh
+ run: |
+ $version = (choco --version).Trim()
+ Write-Host "Chocolatey version: $version"
+ if ([int]($version -split '\.')[0] -lt 2) {
+ Write-Error "'jf setup choco' requires Chocolatey 2.0+ for the 'apikey add' verb; found $version"
+ exit 1
+ }
+
+ - name: Setup Go with cache
+ uses: jfrog/.github/actions/install-go-with-cache@main
+
+ - name: Install local Artifactory
+ uses: jfrog/.github/actions/install-local-artifactory@main
+ with:
+ RTLIC: ${{ secrets.RTLIC }}
+ RT_CONNECTION_TIMEOUT_SECONDS: ${{ env.RT_CONNECTION_TIMEOUT_SECONDS || '1200' }}
+
+ - name: Run Chocolatey tests
+ run: >-
+ go test -v github.com/jfrog/jfrog-cli --timeout 0 --test.choco
+ ${{ env.JFROG_TESTS_IS_EXTERNAL == 'true' && format('--jfrog.url={0} --jfrog.adminToken={1}', env.JFROG_TESTS_URL, env.JFROG_TESTS_LOCAL_ACCESS_TOKEN) || '' }}
+
+ # The OS gate is the one behaviour that can only be observed where Chocolatey cannot run, so it
+ # needs a non-Windows job. Deliberately cheap: no Artifactory and no Chocolatey, just the two
+ # tests asserting that 'jf choco' refuses to run and that 'jf choco --help' still works anyway.
+ Choco-OS-Gate:
+ name: Chocolatey OS gate (linux)
+ runs-on: ubuntu-24.04
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v7
+ with:
+ ref: ${{ github.event.pull_request.head.sha || github.ref }}
+ # Safe: this workflow only runs after human approval via the build-gate environment.
+ allow-unsafe-pr-checkout: true
+
+ - name: Setup Go with cache
+ uses: jfrog/.github/actions/install-go-with-cache@main
+
+ - name: Assert jf choco refuses to run on a non-Windows host
+ run: >-
+ go test -v github.com/jfrog/jfrog-cli --timeout 0 --test.choco
+ -run 'TestChocoNonWindowsGate|TestChocoHelpWorksOnAllPlatforms'
diff --git a/buildtools/cli.go b/buildtools/cli.go
index 9e60d7e8b..5b70f280d 100644
--- a/buildtools/cli.go
+++ b/buildtools/cli.go
@@ -16,6 +16,7 @@ import (
alpinecommand "github.com/jfrog/jfrog-cli-artifactory/artifactory/commands/alpine"
aptcommand "github.com/jfrog/jfrog-cli-artifactory/artifactory/commands/apt"
cargocommand "github.com/jfrog/jfrog-cli-artifactory/artifactory/commands/cargo"
+ chococommand "github.com/jfrog/jfrog-cli-artifactory/artifactory/commands/choco"
conancommand "github.com/jfrog/jfrog-cli-artifactory/artifactory/commands/conan"
nixcommand "github.com/jfrog/jfrog-cli-artifactory/artifactory/commands/nix"
nugetcommand "github.com/jfrog/jfrog-cli-artifactory/artifactory/commands/nuget"
@@ -64,6 +65,7 @@ import (
"github.com/jfrog/jfrog-cli/docs/buildtools/apkcommand"
aptdocs "github.com/jfrog/jfrog-cli/docs/buildtools/apt"
"github.com/jfrog/jfrog-cli/docs/buildtools/cargo"
+ chocodocs "github.com/jfrog/jfrog-cli/docs/buildtools/choco"
"github.com/jfrog/jfrog-cli/docs/buildtools/conan"
"github.com/jfrog/jfrog-cli/docs/buildtools/conanconfig"
"github.com/jfrog/jfrog-cli/docs/buildtools/docker"
@@ -248,6 +250,21 @@ func GetCommands() []cli.Command {
Category: buildToolsCategory,
Action: NugetCmd,
},
+ {
+ Name: "choco",
+ Flags: cliutils.GetCommandFlags(cliutils.Choco),
+ Usage: corecommon.ResolveDescription(chocodocs.GetDescription(), chocodocs.GetAIDescription()),
+ HelpName: corecommon.CreateUsage("choco", corecommon.ResolveDescription(chocodocs.GetDescription(), chocodocs.GetAIDescription()), chocodocs.Usage),
+ UsageText: chocodocs.GetArguments(),
+ ArgsUsage: common.CreateEnvVars(),
+ SkipFlagParsing: true,
+ BashComplete: corecommon.CreateBashCompletionFunc(),
+ Category: buildToolsCategory,
+ Action: func(c *cli.Context) error {
+ cmdName, _ := getCommandName(c.Args())
+ return securityCLI.WrapCmdWithCurationPostFailureRun(c, ChocoCmd, techutils.Nuget, cmdName)
+ },
+ },
{
Name: "dotnet-config",
Flags: cliutils.GetCommandFlags(cliutils.DotnetConfig),
@@ -1094,6 +1111,52 @@ func NugetCmd(c *cli.Context) error {
return commands.ExecWithPackageManager(nugetCmd, project.Nuget.String())
}
+func ChocoCmd(c *cli.Context) error {
+ if show, err := cliutils.ShowGenericCmdHelpIfNeeded(c, c.Args(), c.Command.Name); show || err != nil {
+ return err
+ }
+ if c.NArg() < 1 {
+ return cliutils.WrongNumberOfArgumentsHandler(c)
+ }
+ args := cliutils.ExtractCommand(c)
+ args, serverID, err := coreutils.ExtractServerIdFromCommand(args)
+ if err != nil {
+ return fmt.Errorf("extract server ID: %w", err)
+ }
+ filteredArgs, buildConfiguration, err := build.ExtractBuildDetailsFromArgs(args)
+ if err != nil {
+ return err
+ }
+ filteredArgs, repoResolve, err := coreutils.ExtractStringOptionFromArgs(filteredArgs, "repo-resolve")
+ if err != nil {
+ return fmt.Errorf("extract --repo-resolve: %w", err)
+ }
+ filteredArgs, repoDeploy, err := coreutils.ExtractStringOptionFromArgs(filteredArgs, "repo")
+ if err != nil {
+ return fmt.Errorf("extract --repo: %w", err)
+ }
+ commandName, commandArgs := getCommandName(filteredArgs)
+ workingDirectory, err := filepath.Abs(".")
+ if err != nil {
+ return err
+ }
+ command := chococommand.NewChocoFlexPackCommand().
+ SetSubCommand(commandName).
+ SetArgs(commandArgs).
+ SetRepoResolve(repoResolve).
+ SetRepoDeploy(repoDeploy).
+ SetBuildConfiguration(buildConfiguration).
+ SetWorkingDirectory(workingDirectory)
+ serverDetails, err := coreConfig.GetSpecificConfig(serverID, true, false)
+ if err != nil && serverID != "" {
+ return fmt.Errorf("server-id %q not found: %w", serverID, err)
+ }
+ if err == nil {
+ command.SetServerDetails(serverDetails)
+ }
+ return commands.ExecWithPackageManager(command, "choco")
+}
+
func DotnetCmd(c *cli.Context) error {
if show, err := cliutils.ShowCmdHelpIfNeeded(c, c.Args()); show || err != nil {
return err
@@ -1946,6 +2009,11 @@ func setupCmd(c *cli.Context) (err error) {
return
}
}
+ if packageManager == project.Choco {
+ if err = setup.ValidateChocoPlatform(); err != nil {
+ return err
+ }
+ }
setupCmd := setup.NewSetupCommand(packageManager)
artDetails, err := cliutils.CreateArtifactoryDetailsByFlags(c)
if err != nil {
diff --git a/choco_test.go b/choco_test.go
new file mode 100644
index 000000000..405e862f1
--- /dev/null
+++ b/choco_test.go
@@ -0,0 +1,781 @@
+package main
+
+import (
+ "fmt"
+ "net/http"
+ "net/url"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "runtime"
+ "strings"
+ "testing"
+ "time"
+
+ buildInfo "github.com/jfrog/build-info-go/entities"
+ "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils"
+ coreTests "github.com/jfrog/jfrog-cli-core/v2/utils/tests"
+ "github.com/jfrog/jfrog-cli/inttestutils"
+ "github.com/jfrog/jfrog-cli/utils/tests"
+ "github.com/jfrog/jfrog-client-go/http/httpclient"
+ clientTestUtils "github.com/jfrog/jfrog-client-go/utils/tests"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+// chocoCommandProperty is the single module property that 'jf choco' records, holding the
+// executed Chocolatey command line with any API key redacted.
+const chocoCommandProperty = buildInfo.BuildInfoEnvPrefix + "CHOCO_COMMAND"
+
+// initChocoTest gates every Chocolatey test. Chocolatey is a Windows-only package manager, and
+// both 'jf choco' and 'jf setup choco' fail fast on any other OS, so the tests are skipped
+// unless a real 'choco' executable is available.
+func initChocoTest(t *testing.T) {
+ if !*tests.TestChoco {
+ t.Skip("Skipping Chocolatey test. To run Choco test add the '-test.choco=true' option.")
+ }
+ if runtime.GOOS != "windows" {
+ t.Skipf("Skipping Chocolatey test. Chocolatey runs on Windows only. Detected OS: %s", runtime.GOOS)
+ }
+ if _, err := exec.LookPath("choco"); err != nil {
+ t.Skip("Skipping Chocolatey test. The 'choco' executable was not found in PATH.")
+ }
+ createJfrogHomeConfig(t, true)
+}
+
+// runChoco runs a 'jf choco' command. Unlike 'jf nuget', the Chocolatey command has no
+// JFROG_RUN_NATIVE gate - it always delegates to the native client - and its flag set has no
+// --allow-insecure-connections, so neither is set here.
+func runChoco(t *testing.T, args ...string) error {
+ t.Helper()
+ jfrogCli := coreTests.NewJfrogCli(execMain, "jfrog", "")
+ return jfrogCli.Exec(args...)
+}
+
+// createChocoPackageSource writes a minimal but valid Chocolatey package layout into a fresh
+// temp directory and returns that directory. A nuspec with neither dependencies nor content is
+// rejected (NU5017), so a tools script is packed as content - which is also the conventional
+// Chocolatey layout and makes the package safe to install.
+func createChocoPackageSource(t *testing.T, id, version string) (packageDir, nuspecName string) {
+ t.Helper()
+ packageDir = t.TempDir()
+ toolsDir := filepath.Join(packageDir, "tools")
+ require.NoError(t, os.MkdirAll(toolsDir, 0o700))
+ installScript := "Write-Host 'jfrog-cli-tests Chocolatey package installed'\n"
+ require.NoError(t, os.WriteFile(filepath.Join(toolsDir, "chocolateyinstall.ps1"), []byte(installScript), 0o600))
+ uninstallScript := "Write-Host 'jfrog-cli-tests Chocolatey package uninstalled'\n"
+ require.NoError(t, os.WriteFile(filepath.Join(toolsDir, "chocolateyuninstall.ps1"), []byte(uninstallScript), 0o600))
+
+ nuspecName = id + ".nuspec"
+ nuspecContent := fmt.Sprintf(`
+
+
+ %s
+ %s
+ jfrog-cli-tests
+ jfrog-cli-tests
+ Test package for jf choco integration tests.
+
+
+
+
+`, id, version)
+ require.NoError(t, os.WriteFile(filepath.Join(packageDir, nuspecName), []byte(nuspecContent), 0o600))
+ return packageDir, nuspecName
+}
+
+// packChocoPackage runs 'jf choco pack' from inside a generated package directory and returns the
+// absolute path of the produced .nupkg. 'choco pack' writes the package into the current working
+// directory, and the command snapshots that same directory to discover what it produced, so the
+// test must run from there.
+func packChocoPackage(t *testing.T, id, version string, extraArgs ...string) (nupkgPath string) {
+ t.Helper()
+ packageDir, nuspecName := createChocoPackageSource(t, id, version)
+ packChocoPackageIn(t, packageDir, nuspecName, extraArgs...)
+ nupkgPath = filepath.Join(packageDir, id+"."+version+".nupkg")
+ require.FileExists(t, nupkgPath)
+ return nupkgPath
+}
+
+// packChocoPackageIn runs 'jf choco pack' with packageDir as the working directory.
+func packChocoPackageIn(t *testing.T, packageDir, nuspecName string, extraArgs ...string) {
+ t.Helper()
+ workingDirectory, err := os.Getwd()
+ require.NoError(t, err)
+ defer clientTestUtils.ChangeDirWithCallback(t, workingDirectory, packageDir)()
+ args := append([]string{"choco", "pack", nuspecName}, extraArgs...)
+ require.NoError(t, runChoco(t, args...), "'jf choco pack' should succeed")
+}
+
+// pushChocoPackage runs 'jf choco push' for an already packed .nupkg.
+func pushChocoPackage(t *testing.T, nupkgPath, repo string, extraArgs ...string) error {
+ t.Helper()
+ args := append([]string{"choco", "push", nupkgPath, "--repo=" + repo}, extraArgs...)
+ return runChoco(t, args...)
+}
+
+// chocoArtifactPath is the repository-relative path a Chocolatey package lands on. The layout is
+// flat: the package sits directly under the repository root, with no version directories.
+func chocoArtifactPath(repo, id, version string) string {
+ return repo + "/" + id + "." + version + ".nupkg"
+}
+
+// assertChocoArtifactExists verifies that a pushed package is retrievable from Artifactory.
+func assertChocoArtifactExists(t *testing.T, repoRelativePath string) {
+ t.Helper()
+ client, err := httpclient.ClientBuilder().Build()
+ require.NoError(t, err)
+ _, res, err := client.GetRemoteFileDetails(serverDetails.ArtifactoryUrl+repoRelativePath, artHttpDetails)
+ require.NoError(t, err, "pushed Chocolatey package should exist at %s", repoRelativePath)
+ assert.Equal(t, http.StatusOK, res.StatusCode)
+}
+
+// chocoSourceName mirrors the source name that 'jf setup choco' derives from the Artifactory
+// hostname and repository key, so tests can clean the machine-wide source up afterwards.
+func chocoSourceName(t *testing.T, repo string) string {
+ t.Helper()
+ parsedURL, err := url.Parse(serverDetails.ArtifactoryUrl)
+ require.NoError(t, err)
+ return "jfrt-" + sanitizeChocoSourceComponent(parsedURL.Hostname()) + "-" + repo
+}
+
+func sanitizeChocoSourceComponent(value string) string {
+ var builder strings.Builder
+ for _, character := range strings.ToLower(value) {
+ if (character >= 'a' && character <= 'z') || (character >= '0' && character <= '9') || character == '.' || character == '_' || character == '-' {
+ builder.WriteRune(character)
+ } else {
+ builder.WriteByte('-')
+ }
+ }
+ return strings.Trim(builder.String(), "-")
+}
+
+// cleanupChocoSource removes a machine-wide Chocolatey source. Removal is best effort: the source
+// may never have been created if the test failed early.
+func cleanupChocoSource(t *testing.T, sourceName string) {
+ t.Helper()
+ t.Cleanup(func() {
+ if output, err := exec.Command("choco", "source", "remove", "-n="+sourceName).CombinedOutput(); err != nil {
+ t.Logf("cleanup: 'choco source remove -n=%s' returned %v: %s", sourceName, err, string(output))
+ }
+ })
+}
+
+// cleanupChocoInstalledPackage uninstalls a package that a test installed machine-wide.
+func cleanupChocoInstalledPackage(t *testing.T, packageID string) {
+ t.Helper()
+ t.Cleanup(func() {
+ if output, err := exec.Command("choco", "uninstall", packageID, "-y").CombinedOutput(); err != nil {
+ t.Logf("cleanup: 'choco uninstall %s -y' returned %v: %s", packageID, err, string(output))
+ }
+ })
+}
+
+// getPublishedChocoBuildInfo publishes and then reads back the build info of a Chocolatey build.
+func getPublishedChocoBuildInfo(t *testing.T, buildName, buildNumber string) buildInfo.BuildInfo {
+ t.Helper()
+ require.NoError(t, artifactoryCli.Exec("bp", buildName, buildNumber))
+ publishedBuildInfo, found, err := tests.GetBuildInfo(serverDetails, buildName, buildNumber)
+ require.NoError(t, err)
+ require.True(t, found, "build info %s/%s should have been published", buildName, buildNumber)
+ return publishedBuildInfo.BuildInfo
+}
+
+// getChocoCommandProperty reads the recorded Chocolatey command line out of a module. Module
+// properties are typed as interface{} in build-info, so after a publish/fetch round trip they
+// arrive as a generic map.
+func getChocoCommandProperty(t *testing.T, module buildInfo.Module) string {
+ t.Helper()
+ switch properties := module.Properties.(type) {
+ case nil:
+ return ""
+ case map[string]string:
+ return properties[chocoCommandProperty]
+ case map[string]interface{}:
+ value, ok := properties[chocoCommandProperty].(string)
+ if !ok {
+ return ""
+ }
+ return value
+ default:
+ t.Fatalf("unexpected module properties type %T", module.Properties)
+ return ""
+ }
+}
+
+// TestChocoPackCollectsArtifacts covers the 'jf choco pack' happy path: the packed .nupkg is
+// discovered in the working directory and recorded as a build-info artifact.
+func TestChocoPackCollectsArtifacts(t *testing.T) {
+ initChocoTest(t)
+ defer cleanTestsHomeEnv()
+
+ id, version := "ChocoPackPkg", "1.0.0"
+ buildName := tests.ChocoBuildName + "-pack"
+ buildNumber := "1"
+ defer inttestutils.DeleteBuild(serverDetails.ArtifactoryUrl, buildName, artHttpDetails)
+
+ packChocoPackage(t, id, version, "--build-name="+buildName, "--build-number="+buildNumber)
+
+ collectedBuildInfo := getPublishedChocoBuildInfo(t, buildName, buildNumber)
+ require.Len(t, collectedBuildInfo.Modules, 1)
+ module := collectedBuildInfo.Modules[0]
+ assert.Equal(t, id+":"+version, module.Id, "pack module id should be ':'")
+ assert.Equal(t, buildInfo.Nuget, module.Type, "Chocolatey packages are NuGet packages")
+ require.Len(t, module.Artifacts, 1)
+ artifact := module.Artifacts[0]
+ assert.Equal(t, id+"."+version+".nupkg", artifact.Name)
+ assert.Equal(t, "nupkg", artifact.Type)
+ assert.Equal(t, artifact.Name, artifact.Path, "the Chocolatey layout is flat, so path equals name")
+ assert.NotEmpty(t, artifact.Sha1)
+ assert.NotEmpty(t, artifact.Sha256)
+ assert.NotEmpty(t, artifact.Md5)
+ assert.Contains(t, getChocoCommandProperty(t, module), "pack", "the executed choco command should be recorded")
+}
+
+// TestChocoPushBuildInfoAndProperties covers the primary end-to-end flow - pack, then push to a
+// local repository - and asserts the artifact layout, the published build info and the build
+// properties stamped on the uploaded package.
+func TestChocoPushBuildInfoAndProperties(t *testing.T) {
+ initChocoTest(t)
+ defer cleanTestsHomeEnv()
+
+ id, version := "ChocoPushPkg", "1.0.0"
+ buildName := tests.ChocoBuildName + "-push"
+ buildNumber := "1"
+ defer inttestutils.DeleteBuild(serverDetails.ArtifactoryUrl, buildName, artHttpDetails)
+
+ nupkgPath := packChocoPackage(t, id, version)
+ require.NoError(t, pushChocoPackage(t, nupkgPath, tests.NugetLocalRepo,
+ "--build-name="+buildName, "--build-number="+buildNumber))
+
+ artifactPath := chocoArtifactPath(tests.NugetLocalRepo, id, version)
+ assertChocoArtifactExists(t, artifactPath)
+
+ pushedBuildInfo := getPublishedChocoBuildInfo(t, buildName, buildNumber)
+ require.Len(t, pushedBuildInfo.Modules, 1)
+ module := pushedBuildInfo.Modules[0]
+ assert.Equal(t, id+":"+version, module.Id)
+ assert.Equal(t, buildInfo.Nuget, module.Type)
+ require.Len(t, module.Artifacts, 1)
+ artifact := module.Artifacts[0]
+ assert.Equal(t, id+"."+version+".nupkg", artifact.Name)
+ assert.Equal(t, "nupkg", artifact.Type)
+ assert.Equal(t, tests.NugetLocalRepo, artifact.OriginalDeploymentRepo)
+ assert.NotEmpty(t, artifact.Sha1)
+ assert.NotEmpty(t, artifact.Sha256)
+ assert.NotEmpty(t, artifact.Md5)
+ assert.Contains(t, getChocoCommandProperty(t, module), "push")
+
+ // Build properties are stamped on the uploaded package so it can be traced back to its build.
+ properties := getFlexPackItemProps(t, artifactPath)
+ assert.Equal(t, []string{buildName}, properties["build.name"])
+ assert.Equal(t, []string{buildNumber}, properties["build.number"])
+ assert.NotEmpty(t, properties["build.timestamp"])
+}
+
+// TestChocoPushToRemoteRejected verifies that a remote repository is refused as a push target
+// before anything is uploaded.
+func TestChocoPushToRemoteRejected(t *testing.T) {
+ initChocoTest(t)
+ defer cleanTestsHomeEnv()
+
+ nupkgPath := packChocoPackage(t, "ChocoPushRemotePkg", "1.0.0")
+ err := pushChocoPackage(t, nupkgPath, tests.NugetRemoteRepo)
+ require.Error(t, err, "pushing to a remote repository must be rejected")
+ assert.Contains(t, err.Error(), "cannot be used as a Chocolatey push target")
+}
+
+// TestChocoPushSourceRepoMismatchRejected verifies that an explicit --source pointing at one
+// repository while --repo names another is rejected, instead of silently pushing to one and
+// recording build info against the other.
+func TestChocoPushSourceRepoMismatchRejected(t *testing.T) {
+ initChocoTest(t)
+ defer cleanTestsHomeEnv()
+
+ nupkgPath := packChocoPackage(t, "ChocoSourceMismatchPkg", "1.0.0")
+ mismatchedSource := serverDetails.ArtifactoryUrl + "api/nuget/" + tests.NugetVirtualRepo
+ err := pushChocoPackage(t, nupkgPath, tests.NugetLocalRepo, "-s="+mismatchedSource)
+ require.Error(t, err, "a --source that disagrees with --repo must be rejected")
+ assert.Contains(t, err.Error(), "use matching source and --repo values")
+}
+
+// TestChocoPushToVirtualRepoConvention verifies that pushing to a virtual repository forwards to
+// its default deployment repository, and that build info records the resolved local repository
+// rather than the virtual repository key.
+func TestChocoPushToVirtualRepoConvention(t *testing.T) {
+ initChocoTest(t)
+ defer cleanTestsHomeEnv()
+
+ id, version := "ChocoVirtualPushPkg", "1.0.0"
+ buildName := tests.ChocoBuildName + "-virtual-push"
+ buildNumber := "1"
+ defer inttestutils.DeleteBuild(serverDetails.ArtifactoryUrl, buildName, artHttpDetails)
+
+ nupkgPath := packChocoPackage(t, id, version)
+ require.NoError(t, pushChocoPackage(t, nupkgPath, tests.NugetVirtualRepo,
+ "--build-name="+buildName, "--build-number="+buildNumber))
+
+ // The package must land in the virtual repository's default deployment (local) repository.
+ assertChocoArtifactExists(t, chocoArtifactPath(tests.NugetLocalRepo, id, version))
+
+ virtualPushBuildInfo := getPublishedChocoBuildInfo(t, buildName, buildNumber)
+ require.Len(t, virtualPushBuildInfo.Modules, 1)
+ require.NotEmpty(t, virtualPushBuildInfo.Modules[0].Artifacts)
+ for _, artifact := range virtualPushBuildInfo.Modules[0].Artifacts {
+ assert.Equal(t, tests.NugetLocalRepo, artifact.OriginalDeploymentRepo,
+ "build info must record the resolved local repository, not the virtual repository key")
+ }
+}
+
+// TestChocoBuildFlagsValidation covers the two partial build-flag cases. They behave differently:
+// --build-name alone is accepted and simply collects no build info, while --build-number without
+// --build-name is rejected by jf's CLI-wide flag-pair validation.
+func TestChocoBuildFlagsValidation(t *testing.T) {
+ initChocoTest(t)
+ defer cleanTestsHomeEnv()
+
+ // Build-info is collected when, and only when, both --build-name and --build-number are given.
+ // Neither flag is a plain passthrough that must still succeed; half of the pair is a mistake
+ // worth reporting rather than silently ignoring, and it is reported before choco runs at all.
+ testCases := []struct {
+ name string
+ packageID string
+ buildName string
+ extraArgs []string
+ expectError bool
+ }{
+ {
+ name: "neither build flag pushes without collecting build info",
+ packageID: "ChocoNoBuildFlagsPkg",
+ },
+ {
+ name: "build name without build number is rejected",
+ packageID: "ChocoNameOnlyPkg",
+ buildName: tests.ChocoBuildName + "-name-only",
+ expectError: true,
+ },
+ {
+ name: "build number without build name is rejected",
+ packageID: "ChocoNumberOnlyPkg",
+ extraArgs: []string{"--build-number=1"},
+ expectError: true,
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(testCase.name, func(t *testing.T) {
+ extraArgs := testCase.extraArgs
+ if testCase.buildName != "" {
+ extraArgs = append(extraArgs, "--build-name="+testCase.buildName)
+ t.Cleanup(func() {
+ inttestutils.DeleteBuild(serverDetails.ArtifactoryUrl, testCase.buildName, artHttpDetails)
+ })
+ }
+ nupkgPath := packChocoPackage(t, testCase.packageID, "1.0.0")
+ err := pushChocoPackage(t, nupkgPath, tests.NugetLocalRepo, extraArgs...)
+ if testCase.expectError {
+ require.Error(t, err, "one build flag without the other must be rejected")
+ assert.Contains(t, err.Error(), "cannot be provided separately")
+ // The rejection happens before the native command, so nothing was published.
+ return
+ }
+ // A bare passthrough is legal: the package is pushed, there is just no build to publish.
+ require.NoError(t, err)
+ assertChocoArtifactExists(t, chocoArtifactPath(tests.NugetLocalRepo, testCase.packageID, "1.0.0"))
+ })
+ }
+}
+
+// TestChocoBuildInfoFromEnvVars verifies that the build name and number can come from the
+// standard JFrog CLI environment variables instead of command-line flags.
+func TestChocoBuildInfoFromEnvVars(t *testing.T) {
+ initChocoTest(t)
+ defer cleanTestsHomeEnv()
+
+ buildName := tests.ChocoBuildName + "-envvars"
+ buildNumber := "7"
+ clientTestUtils.SetEnvAndAssert(t, "JFROG_CLI_BUILD_NAME", buildName)
+ clientTestUtils.SetEnvAndAssert(t, "JFROG_CLI_BUILD_NUMBER", buildNumber)
+ defer clientTestUtils.UnSetEnvAndAssert(t, "JFROG_CLI_BUILD_NAME")
+ defer clientTestUtils.UnSetEnvAndAssert(t, "JFROG_CLI_BUILD_NUMBER")
+ defer inttestutils.DeleteBuild(serverDetails.ArtifactoryUrl, buildName, artHttpDetails)
+
+ nupkgPath := packChocoPackage(t, "ChocoEnvVarPkg", "1.0.0")
+ require.NoError(t, pushChocoPackage(t, nupkgPath, tests.NugetLocalRepo))
+
+ envVarBuildInfo := getPublishedChocoBuildInfo(t, buildName, buildNumber)
+ require.Len(t, envVarBuildInfo.Modules, 1)
+ assert.NotEmpty(t, envVarBuildInfo.Modules[0].Artifacts,
+ "build info must be collected from JFROG_CLI_BUILD_NAME/NUMBER alone")
+}
+
+// TestChocoModuleOverride verifies that --module replaces the default ':'
+// module id with a caller-chosen name.
+func TestChocoModuleOverride(t *testing.T) {
+ initChocoTest(t)
+ defer cleanTestsHomeEnv()
+
+ buildName := tests.ChocoBuildName + "-module-override"
+ buildNumber := "1"
+ defer inttestutils.DeleteBuild(serverDetails.ArtifactoryUrl, buildName, artHttpDetails)
+
+ nupkgPath := packChocoPackage(t, "ChocoModuleOverridePkg", "1.0.0")
+ require.NoError(t, pushChocoPackage(t, nupkgPath, tests.NugetLocalRepo,
+ "--build-name="+buildName, "--build-number="+buildNumber, "--module=my-service"))
+
+ moduleOverrideBuildInfo := getPublishedChocoBuildInfo(t, buildName, buildNumber)
+ require.Len(t, moduleOverrideBuildInfo.Modules, 1)
+ assert.Equal(t, "my-service", moduleOverrideBuildInfo.Modules[0].Id,
+ "--module must override the default ':' module id")
+}
+
+// TestChocoCommandPropertyRedactsApiKey verifies that a Chocolatey API key passed through to the
+// native client is never recorded in build info.
+func TestChocoCommandPropertyRedactsApiKey(t *testing.T) {
+ initChocoTest(t)
+ defer cleanTestsHomeEnv()
+
+ id, version := "ChocoRedactPkg", "1.0.0"
+ buildName := tests.ChocoBuildName + "-redact"
+ buildNumber := "1"
+ defer inttestutils.DeleteBuild(serverDetails.ArtifactoryUrl, buildName, artHttpDetails)
+
+ sourceURL := serverDetails.ArtifactoryUrl + "api/nuget/" + tests.NugetLocalRepo
+ apiKey := chocoPushApiKey(t)
+ nupkgPath := packChocoPackage(t, id, version)
+ // An explicit source and API key suppress the credentials the command would otherwise inject,
+ // so the native push is driven entirely by these arguments.
+ require.NoError(t, pushChocoPackage(t, nupkgPath, tests.NugetLocalRepo,
+ "--build-name="+buildName, "--build-number="+buildNumber,
+ "-s="+sourceURL, "-k="+apiKey))
+
+ redactedBuildInfo := getPublishedChocoBuildInfo(t, buildName, buildNumber)
+ require.Len(t, redactedBuildInfo.Modules, 1)
+ recordedCommand := getChocoCommandProperty(t, redactedBuildInfo.Modules[0])
+ require.NotEmpty(t, recordedCommand)
+ assert.Contains(t, recordedCommand, "-k=***", "the API key must be redacted in build info")
+ assert.NotContains(t, recordedCommand, apiKey, "the real credential must never be recorded")
+}
+
+// chocoPushApiKey builds the composite ':' key that Artifactory NuGet endpoints
+// expect for authenticated pushes.
+func chocoPushApiKey(t *testing.T) string {
+ t.Helper()
+ if serverDetails.AccessToken != "" {
+ return serverDetails.User + ":" + serverDetails.AccessToken
+ }
+ return serverDetails.User + ":" + serverDetails.Password
+}
+
+// TestSetupChocoConfiguresSource covers the 'jf setup choco' happy path: the machine-wide
+// Chocolatey source for the repository is created and authenticated.
+func TestSetupChocoConfiguresSource(t *testing.T) {
+ initChocoTest(t)
+ defer cleanTestsHomeEnv()
+
+ sourceName := chocoSourceName(t, tests.NugetVirtualRepo)
+ cleanupChocoSource(t, sourceName)
+
+ require.NoError(t, runChoco(t, "setup", "choco", "--repo="+tests.NugetVirtualRepo),
+ "'jf setup choco' should configure the Chocolatey source")
+
+ output, err := exec.Command("choco", "source", "list").CombinedOutput()
+ require.NoError(t, err, "'choco source list' failed: %s", string(output))
+ assert.Contains(t, string(output), sourceName, "the JFrog Chocolatey source should be configured")
+ // The V2 NuGet endpoint is what Chocolatey speaks; a V3 URL would not work.
+ assert.Contains(t, string(output), "api/nuget/"+tests.NugetVirtualRepo,
+ "the source should point at the Artifactory NuGet V2 endpoint")
+}
+
+// TestChocoInstallCollectsDependencies covers the 'jf choco install' happy path end to end: a
+// package is packed, pushed, resolved from Artifactory and recorded as a build-info dependency.
+func TestChocoInstallCollectsDependencies(t *testing.T) {
+ initChocoTest(t)
+ defer cleanTestsHomeEnv()
+
+ id, version := "ChocoInstallPkg", "1.0.0"
+ buildName := tests.ChocoBuildName + "-install"
+ buildNumber := "1"
+ defer inttestutils.DeleteBuild(serverDetails.ArtifactoryUrl, buildName, artHttpDetails)
+
+ nupkgPath := packChocoPackage(t, id, version)
+ require.NoError(t, pushChocoPackage(t, nupkgPath, tests.NugetLocalRepo))
+
+ sourceName := chocoSourceName(t, tests.NugetLocalRepo)
+ cleanupChocoSource(t, sourceName)
+ require.NoError(t, runChoco(t, "setup", "choco", "--repo="+tests.NugetLocalRepo))
+
+ cleanupChocoInstalledPackage(t, id)
+ requireChocoInstall(t, id, version, sourceName, buildName, buildNumber)
+
+ installBuildInfo := getPublishedChocoBuildInfo(t, buildName, buildNumber)
+ require.Len(t, installBuildInfo.Modules, 1)
+ module := installBuildInfo.Modules[0]
+ assert.Equal(t, buildInfo.Nuget, module.Type)
+ require.NotEmpty(t, module.Dependencies, "the installed package must be recorded as a dependency")
+ dependency := module.Dependencies[0]
+ assert.Equal(t, id+":"+version, dependency.Id)
+ assert.Equal(t, "nupkg", dependency.Type)
+ assert.Equal(t, tests.NugetLocalRepo, dependency.Repository,
+ "--repo-resolve should be recorded as the resolution repository")
+ assert.Contains(t, getChocoCommandProperty(t, module), "install")
+}
+
+// requireChocoInstall runs 'jf choco install' with a short retry, because a freshly pushed
+// package is not immediately searchable through the NuGet endpoint.
+func requireChocoInstall(t *testing.T, id, version, sourceName, buildName, buildNumber string) {
+ t.Helper()
+ args := []string{"choco", "install", id, "--version=" + version, "--repo-resolve=" + tests.NugetLocalRepo,
+ "--build-name=" + buildName, "--build-number=" + buildNumber, "-y", "-s=" + sourceName}
+ var lastErr error
+ for attempt := 0; attempt < 5; attempt++ {
+ if attempt > 0 {
+ time.Sleep(time.Duration(attempt) * 2 * time.Second)
+ }
+ if lastErr = runChoco(t, args...); lastErr == nil {
+ return
+ }
+ t.Logf("'jf choco install' attempt %d failed, retrying: %v", attempt+1, lastErr)
+ }
+ require.NoError(t, lastErr, "'jf choco install' should succeed once the pushed package is indexed")
+}
+
+// ---------------------------------------------------------------------------------------------
+// Regression tests for three behaviours Chocolatey has that the first implementation pass assumed
+// away. Each was verified against Chocolatey's own documentation or source, and each failed before
+// the accompanying build-info-go fix.
+// ---------------------------------------------------------------------------------------------
+
+// TestChocoPackRespectsOutputDirectory covers 'choco pack --output-directory'.
+//
+// The flag exists (--out / --outdir / --outputdirectory / --output-directory), so snapshotting only
+// the working directory finds nothing the pack produced. That failure is silent: the command
+// succeeds, the package is on disk, and the build info simply has no artifacts.
+func TestChocoPackRespectsOutputDirectory(t *testing.T) {
+ initChocoTest(t)
+
+ const id, version = "JfChocoOutDir", "1.0.0"
+ buildName := tests.ChocoBuildName + "-outdir"
+ const buildNumber = "1"
+ defer inttestutils.DeleteBuild(serverDetails.ArtifactoryUrl, buildName, artHttpDetails)
+
+ packageDir, nuspecName := createChocoPackageSource(t, id, version)
+ outputDir := filepath.Join(packageDir, "build-output")
+ require.NoError(t, os.MkdirAll(outputDir, 0o700))
+
+ packChocoPackageIn(t, packageDir, nuspecName,
+ "--output-directory="+outputDir,
+ "--build-name="+buildName, "--build-number="+buildNumber)
+
+ require.FileExists(t, filepath.Join(outputDir, id+"."+version+".nupkg"),
+ "'choco pack' should have written the package into --output-directory")
+
+ publishedBuildInfo := getPublishedChocoBuildInfo(t, buildName, buildNumber)
+ require.Len(t, publishedBuildInfo.Modules, 1)
+ require.Len(t, publishedBuildInfo.Modules[0].Artifacts, 1,
+ "a package written to --output-directory must still be collected; collecting zero artifacts "+
+ "here is the silent failure this test exists to catch")
+ assert.Equal(t, id+"."+version+".nupkg", publishedBuildInfo.Modules[0].Artifacts[0].Name)
+}
+
+// TestChocoPushWithoutPositionalPath covers 'choco push' with no path argument.
+//
+// The path is optional: with exactly one .nupkg in the folder Chocolatey pushes it. Requiring an
+// explicit positional means the package uploads but no artifact is recorded, so it is never
+// stamped and never appears in the build info.
+func TestChocoPushWithoutPositionalPath(t *testing.T) {
+ initChocoTest(t)
+
+ const id, version = "JfChocoBarePush", "1.0.0"
+ buildName := tests.ChocoBuildName + "-bare-push"
+ const buildNumber = "1"
+ defer inttestutils.DeleteBuild(serverDetails.ArtifactoryUrl, buildName, artHttpDetails)
+
+ nupkgPath := packChocoPackage(t, id, version)
+
+ workingDirectory, err := os.Getwd()
+ require.NoError(t, err)
+ defer clientTestUtils.ChangeDirWithCallback(t, workingDirectory, filepath.Dir(nupkgPath))()
+
+ require.NoError(t, runChoco(t, "choco", "push",
+ "--repo="+tests.NugetLocalRepo,
+ "--build-name="+buildName, "--build-number="+buildNumber),
+ "'choco push' with no positional path must succeed")
+
+ publishedBuildInfo := getPublishedChocoBuildInfo(t, buildName, buildNumber)
+ require.Len(t, publishedBuildInfo.Modules, 1)
+ require.Len(t, publishedBuildInfo.Modules[0].Artifacts, 1,
+ "a push with no positional path must still record the package Chocolatey found in the folder")
+ assert.Equal(t, id+"."+version+".nupkg", publishedBuildInfo.Modules[0].Artifacts[0].Name)
+ assertChocoArtifactExists(t, chocoArtifactPath(tests.NugetLocalRepo, id, version))
+}
+
+// TestChocoInstallRecordsVersionFromInstalledPackage is the end-to-end counterpart to
+// resolveInstalledPackage's unit tests: Chocolatey stores lib\\.nupkg with no version in
+// the file name, so a version read off that name is always empty and every dependency is dropped.
+// Asserting the resolved version here proves the version came from the installed .nuspec.
+func TestChocoInstallRecordsVersionFromInstalledPackage(t *testing.T) {
+ initChocoTest(t)
+
+ const id, version = "JfChocoInstalledVersion", "3.4.5"
+ buildName := tests.ChocoBuildName + "-installed-version"
+ const buildNumber = "1"
+ defer inttestutils.DeleteBuild(serverDetails.ArtifactoryUrl, buildName, artHttpDetails)
+ cleanupChocoInstalledPackage(t, id)
+
+ nupkgPath := packChocoPackage(t, id, version)
+ require.NoError(t, pushChocoPackage(t, nupkgPath, tests.NugetLocalRepo))
+ // Confirm the fixture really is published at 3.4.5 before asserting what the install recorded,
+ // so a wrong recorded version cannot be blamed on a bad fixture.
+ assertChocoArtifactExists(t, chocoArtifactPath(tests.NugetLocalRepo, id, version))
+
+ sourceName := chocoSourceName(t, tests.NugetLocalRepo)
+ cleanupChocoSource(t, sourceName)
+ require.NoError(t, runChoco(t, "setup", "choco", "--repo="+tests.NugetLocalRepo))
+
+ // Deliberately no --version: a bare install resolves latest, so the recorded version cannot
+ // have come from the command line either.
+ requireChocoInstall(t, id, "", sourceName, buildName, buildNumber)
+
+ publishedBuildInfo := getPublishedChocoBuildInfo(t, buildName, buildNumber)
+ require.Len(t, publishedBuildInfo.Modules, 1)
+ require.Len(t, publishedBuildInfo.Modules[0].Dependencies, 1)
+ assert.Equal(t, id+":"+version, publishedBuildInfo.Modules[0].Dependencies[0].Id,
+ "the installed version must be resolved from the package's .nuspec, not from its file name")
+}
+
+// ---------------------------------------------------------------------------------------------
+// Pass-through, statelessness and the platform gate.
+// ---------------------------------------------------------------------------------------------
+
+// initChocoTestAnyPlatform gates only on the feature flag, for the two scenarios that must be
+// observed where Chocolatey cannot run. It deliberately configures no JFrog server: the OS gate
+// fires before any server interaction, and requiring one here would hide a regression that moved
+// the gate behind server resolution.
+func initChocoTestAnyPlatform(t *testing.T) {
+ t.Helper()
+ if !*tests.TestChoco {
+ t.Skip("Skipping Chocolatey test. To run Choco test add the '-test.choco=true' option.")
+ }
+}
+
+// TestChocoNonWindowsGate asserts the command refuses to run off Windows, naming the detected OS
+// instead of surfacing a bare "choco: executable file not found".
+func TestChocoNonWindowsGate(t *testing.T) {
+ initChocoTestAnyPlatform(t)
+ if runtime.GOOS == "windows" {
+ t.Skip("The OS gate only rejects non-Windows hosts; nothing to assert on Windows.")
+ }
+
+ err := runChoco(t, "choco", "install", "some-package")
+ require.Error(t, err, "'jf choco' must refuse to run on a non-Windows host")
+ assert.Contains(t, err.Error(), "Windows only")
+ assert.Containsf(t, err.Error(), runtime.GOOS, "the error must name the detected OS (%s)", runtime.GOOS)
+}
+
+// TestChocoHelpWorksOnAllPlatforms asserts the gate lives in the command's Run(), not in its
+// registration, so help stays reachable on the machines that cannot run the tool.
+func TestChocoHelpWorksOnAllPlatforms(t *testing.T) {
+ initChocoTestAnyPlatform(t)
+ assert.NoError(t, runChoco(t, "choco", "--help"), "'jf choco --help' must work on every OS")
+}
+
+// TestChocoPassThroughCollectsNoBuildInfo covers the subcommands that are not build events. The
+// config-mutating ones matter most: 'source', 'apikey' and 'config' change machine state, and a
+// wrapper whose contract is that it writes no configuration must not treat them as builds.
+func TestChocoPassThroughCollectsNoBuildInfo(t *testing.T) {
+ initChocoTest(t)
+
+ buildName := tests.ChocoBuildName + "-passthrough"
+ const buildNumber = "1"
+ defer inttestutils.DeleteBuild(serverDetails.ArtifactoryUrl, buildName, artHttpDetails)
+
+ for _, testCase := range []struct {
+ name string
+ args []string
+ }{
+ {"list", []string{"choco", "list", "-r"}},
+ {"outdated", []string{"choco", "outdated", "-r"}},
+ {"source-list", []string{"choco", "source", "list", "-r"}},
+ {"config-list", []string{"choco", "config", "list"}},
+ {"feature-list", []string{"choco", "feature", "list"}},
+ } {
+ t.Run(testCase.name, func(t *testing.T) {
+ // Built as a fresh slice rather than appended onto testCase.args, which would be free
+ // to reuse that slice's backing array and leak the build flags into the next case.
+ args := make([]string, 0, len(testCase.args)+2)
+ args = append(args, testCase.args...)
+ args = append(args, "--build-name="+buildName, "--build-number="+buildNumber)
+ // The native exit code is passed through; a query returning non-zero is not this
+ // test's concern. What matters is that nothing was collected.
+ _ = runChoco(t, args...)
+
+ _, found, err := tests.GetBuildInfo(serverDetails, buildName, buildNumber)
+ assert.NoError(t, err)
+ assert.Falsef(t, found, "'choco %s' is not a build event and must not produce build info", testCase.name)
+ })
+ }
+}
+
+// TestChocoPassThroughWithoutServerConfigured asserts a pass-through command does not require a
+// configured JFrog server. Requiring one would make 'jf choco list' fail on any unconfigured
+// machine, leaving the wrapper strictly worse than the tool it wraps.
+func TestChocoPassThroughWithoutServerConfigured(t *testing.T) {
+ initChocoTest(t)
+
+ restoreHomeDir := clientTestUtils.SetEnvWithCallbackAndAssert(t, coreutils.HomeDir, t.TempDir())
+ defer restoreHomeDir()
+
+ assert.NoError(t, runChoco(t, "choco", "--version"),
+ "pass-through must work with no JFrog server configured")
+}
+
+// TestChocoDoesNotMutateChocolateyConfig covers the statelessness contract, and the clearest line
+// between 'jf choco' and 'jf setup choco'. chocolatey.config is machine-wide, so an accidental
+// write by the wrapper would change behaviour for every user on the machine.
+func TestChocoDoesNotMutateChocolateyConfig(t *testing.T) {
+ initChocoTest(t)
+
+ const id, version = "JfChocoStateless", "1.0.0"
+ buildName := tests.ChocoBuildName + "-stateless"
+ const buildNumber = "1"
+ defer inttestutils.DeleteBuild(serverDetails.ArtifactoryUrl, buildName, artHttpDetails)
+
+ sourcesBefore, err := exec.Command("choco", "source", "list", "-r").CombinedOutput()
+ require.NoError(t, err)
+
+ packageDir, nuspecName := createChocoPackageSource(t, id, version)
+ packChocoPackageIn(t, packageDir, nuspecName, "--build-name="+buildName, "--build-number="+buildNumber)
+
+ assert.NoDirExists(t, filepath.Join(packageDir, ".jfrog", "projects"),
+ "FlexPack is stateless: 'jf choco' must not create a .jfrog/projects directory")
+
+ sourcesAfter, err := exec.Command("choco", "source", "list", "-r").CombinedOutput()
+ require.NoError(t, err)
+ assert.Equal(t, string(sourcesBefore), string(sourcesAfter),
+ "'jf choco' must not modify Chocolatey's machine-wide source list; that is 'jf setup choco's job")
+}
+
+// TestChocoNoBuildFlagsCollectsNothing asserts that without both build coordinates the command is a
+// transparent native run: the package is produced, and nothing is published.
+func TestChocoNoBuildFlagsCollectsNothing(t *testing.T) {
+ initChocoTest(t)
+
+ const id, version = "JfChocoNoFlags", "1.0.0"
+ nupkgPath := packChocoPackage(t, id, version)
+ assert.FileExists(t, nupkgPath, "the native pack must still run and produce the package")
+
+ _, found, err := tests.GetBuildInfo(serverDetails, tests.ChocoBuildName+"-noflags", "1")
+ assert.NoError(t, err)
+ assert.False(t, found, "no build info may be published when the build flags are absent")
+}
+
+// TestChocoPushToNonexistentRepoRejected asserts an unknown repository fails clearly rather than
+// surfacing a raw Artifactory 404 from deep inside the push.
+func TestChocoPushToNonexistentRepoRejected(t *testing.T) {
+ initChocoTest(t)
+
+ nupkgPath := packChocoPackage(t, "JfChocoBadRepo", "1.0.0")
+ require.Error(t, pushChocoPackage(t, nupkgPath, "cli-choco-nonexistent-repo"))
+}
diff --git a/docs/buildtools/choco/help.go b/docs/buildtools/choco/help.go
new file mode 100644
index 000000000..26d7d3671
--- /dev/null
+++ b/docs/buildtools/choco/help.go
@@ -0,0 +1,32 @@
+package choco
+
+var Usage = []string{"choco [command options]"}
+
+func GetDescription() string {
+ return "Run Chocolatey with optional JFrog build-info collection."
+}
+
+func GetArguments() string {
+ return ` choco sub-command
+ Arguments and options for the native Chocolatey command.`
+}
+
+func GetAIDescription() string {
+ return `Run a native Chocolatey command through JFrog. The command forwards Chocolatey arguments unchanged and optionally records build-info for pack, push, install, and upgrade.
+
+Prerequisites:
+- Chocolatey installed on Windows.
+- Run 'jf setup choco' to add authenticated Artifactory sources, or pass the native Chocolatey source/authentication options yourself.
+
+Examples:
+ $ jf choco install git -s=jfrt-acme.jfrog.io-choco-virtual --build-name=image --build-number=1
+ $ jf choco push tool.1.0.0.nupkg -s=jfrt-acme.jfrog.io-choco-local --repo=choco-local --build-name=tool --build-number=1
+
+Gotchas:
+- Chocolatey runs on Windows only.
+- Build-info is collected only when both '--build-name' and '--build-number' are given; supplying just one is an error.
+- The native '-s' source and JFrog '--repo' must identify the same Artifactory endpoint for push build-info.
+- Install and upgrade record the requested packages and their transitive dependencies, read from Chocolatey's lib directory. A dependency served by a Chocolatey special source (ruby, cygwin, python, windowsfeatures) never lands there, so it is reported as not found and left out of the build-info.
+- In CI, set '--execution-timeout' explicitly. Chocolatey's default of 2700 seconds is mishandled by Chocolatey itself and becomes a five-hour timeout, so a hung install stalls the build instead of failing it. '--execution-timeout=2700' is enough to avoid this.
+- This command never writes Chocolatey configuration. Use 'jf setup choco' for that.`
+}
diff --git a/docs/buildtools/setup/help.go b/docs/buildtools/setup/help.go
index 0a8d7eaca..bc989c2f9 100644
--- a/docs/buildtools/setup/help.go
+++ b/docs/buildtools/setup/help.go
@@ -34,6 +34,7 @@ Not the same command as jf npm-config / jf mvn-config / jf pip-config, which loo
Prerequisites:
- A configured server (jf c add or jf login), or pass --url/--user/--password/--access-token directly.
- The Artifactory repository name for the package manager (a virtual repo where supported).
+- For Chocolatey, select a NuGet virtual, local, or remote repository when prompted. Setup creates a named native source ('jfrt--') for each repository, so use -s to choose a configured resolve or publish endpoint.
Common patterns:
$ jf setup npm
@@ -49,6 +50,7 @@ Gotchas:
- pnpm and npm can end up on different repositories without any warning. pnpm reads its own configuration first and ~/.npmrc only as a fallback, so a machine with no pnpm setup follows "jf setup npm", but once "jf setup pnpm" has run, a later "jf setup npm --repo b" moves npm alone and pnpm keeps resolving from the repository it was given. If both are in use, run "jf setup" for both.
- maven and gradle do not need their client installed: their setup writes settings.xml and a Gradle init script directly, so it works on a machine that only has ./mvnw or ./gradlew. Every other package manager's setup runs its client. helm additionally needs 3.8.0 or newer, because its login targets an OCI registry.
- docker/podman authenticate directly against the registry and skip the repository prompt entirely (no --repo needed); helm still goes through repository selection like the other package managers even though its login step doesn't end up using the repo name.
+- Chocolatey runs on Windows only and updates machine-wide chocolatey.config, so use an elevated shell. Re-running 'jf setup choco' for one repository refreshes only that repository's 'jfrt--' source; it preserves other Chocolatey sources.
Related: jf npm-config, jf go-config, jf pip-config, jf c add`
}
diff --git a/go.mod b/go.mod
index 8d2b4d8b0..f9a664c7f 100644
--- a/go.mod
+++ b/go.mod
@@ -19,11 +19,11 @@ require (
github.com/buger/jsonparser v1.3.0
github.com/gocarina/gocsv v0.0.0-20260607070740-0735908c6461
github.com/jfrog/archiver/v3 v3.6.5
- github.com/jfrog/build-info-go v1.13.1-0.20260902120316-b325d342b210
+ github.com/jfrog/build-info-go v1.13.1-0.20260910033343-40d564cbd202
github.com/jfrog/gofrog v1.7.7
github.com/jfrog/jfrog-cli-application v1.0.2-0.20260820134442-c8629258ff3a
- github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260908123315-10cfe381853a
- github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20260909093400-32a7208a18bd
+ github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260910035002-9e5eaa824782
+ github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20260910032555-f560dade8e04
github.com/jfrog/jfrog-cli-evidence v0.11.1-0.20260824063609-79b735ec565e
github.com/jfrog/jfrog-cli-platform-services v1.10.1-0.20260618062042-6053ab368cab
github.com/jfrog/jfrog-cli-security v1.37.0
@@ -221,14 +221,14 @@ require (
go.opentelemetry.io/otel/trace v1.45.0 // indirect
go.yaml.in/yaml/v2 v2.4.4 // indirect
go.yaml.in/yaml/v3 v3.0.5 // indirect
- golang.org/x/crypto v0.54.0 // indirect
- golang.org/x/mod v0.38.0 // indirect
+ golang.org/x/crypto v0.56.0 // indirect
+ golang.org/x/mod v0.40.0 // indirect
golang.org/x/net v0.57.0 // indirect
golang.org/x/oauth2 v0.36.0 // indirect
golang.org/x/sync v0.22.0 // indirect
golang.org/x/sys v0.47.0 // indirect
golang.org/x/term v0.45.0 // indirect
- golang.org/x/text v0.40.0 // indirect
+ golang.org/x/text v0.41.0 // indirect
golang.org/x/time v0.15.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20260803160001-6ac0973c030d // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260803160001-6ac0973c030d // indirect
diff --git a/go.sum b/go.sum
index 7320371b7..648984699 100644
--- a/go.sum
+++ b/go.sum
@@ -390,8 +390,8 @@ github.com/jellydator/ttlcache/v3 v3.4.0 h1:YS4P125qQS0tNhtL6aeYkheEaB/m8HCqdMMP
github.com/jellydator/ttlcache/v3 v3.4.0/go.mod h1:Hw9EgjymziQD3yGsQdf1FqFdpp7YjFMd4Srg5EJlgD4=
github.com/jfrog/archiver/v3 v3.6.5 h1:AiNXJoe8jYDOtyykfVuwh26aM4rk/ei+YzBpfBukdzU=
github.com/jfrog/archiver/v3 v3.6.5/go.mod h1:5V9l+Fte30Y4qe9dUOAd3yNTf8lmtVNuhKNrvI8PMhg=
-github.com/jfrog/build-info-go v1.13.1-0.20260902120316-b325d342b210 h1:u1Ijj6fOX9hCzz27L3IpqFqdSsjOlg6Td7URtmNFVR8=
-github.com/jfrog/build-info-go v1.13.1-0.20260902120316-b325d342b210/go.mod h1:CYRUCvLKfyARjoJXLWAxce1qNUxTEtbRKAARkV42vpE=
+github.com/jfrog/build-info-go v1.13.1-0.20260910033343-40d564cbd202 h1:XD2+J0bYh6ZaEJYV6uRvfrrDIFFzdCGSEnp4CKvrJbA=
+github.com/jfrog/build-info-go v1.13.1-0.20260910033343-40d564cbd202/go.mod h1:CYRUCvLKfyARjoJXLWAxce1qNUxTEtbRKAARkV42vpE=
github.com/jfrog/froggit-go v1.23.1 h1:4wmaHeuptxVINbovMaeITzVhi3+VQoc/FFIjF4axzu0=
github.com/jfrog/froggit-go v1.23.1/go.mod h1:wRDryqyp3oe+eHgME2mpnEQmO8XBECIPagFwj0nHmdI=
github.com/jfrog/go-mockhttp v0.3.1 h1:/wac8v4GMZx62viZmv4wazB5GNKs+GxawuS1u3maJH8=
@@ -402,10 +402,10 @@ github.com/jfrog/jfrog-apps-config v1.0.1 h1:mtv6k7g8A8BVhlHGlSveapqf4mJfonwvXYL
github.com/jfrog/jfrog-apps-config v1.0.1/go.mod h1:8AIIr1oY9JuH5dylz2S6f8Ym2MaadPLR6noCBO4C22w=
github.com/jfrog/jfrog-cli-application v1.0.2-0.20260820134442-c8629258ff3a h1:7GhcPfi+k9oOAJdCsKWjymnqH0e7DQZ1soVhbneYnGY=
github.com/jfrog/jfrog-cli-application v1.0.2-0.20260820134442-c8629258ff3a/go.mod h1:p8yLtbmCxxQucIbLZKnWu0F+EDtj6NLXbRQCEK/nb6o=
-github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260908123315-10cfe381853a h1:Bp/vFrvUHdy4NEKcE3cZ5Dkz4S3IJtSIOv8PtUPbrTQ=
-github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260908123315-10cfe381853a/go.mod h1:Oiq1Gc1RtmaDBmpVMQuG3XlmRAWXkA5bfRraHLwbZF0=
-github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20260909093400-32a7208a18bd h1:tfC6CtOpqWoU/1V4ymBL0GnhM2kbq5JTGSaAk9eOybg=
-github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20260909093400-32a7208a18bd/go.mod h1:SwV+DNLBnWLxBeNeZpJk+xxAbqJ8ywq1va56up+AGu4=
+github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260910035002-9e5eaa824782 h1:fgHYLhpVRQFYd8Uoo9Lv9lfMMwxr7flph8v7m6ulzZY=
+github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260910035002-9e5eaa824782/go.mod h1:rCQHDC+q2hak/Jerz5ptrxCK3P+ONqFnJRW6eX0yNXg=
+github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20260910032555-f560dade8e04 h1:2SZDAl1CRqNaXB6iecU1Z+aPiFLLXNS60seZgZPY0hI=
+github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20260910032555-f560dade8e04/go.mod h1:SwV+DNLBnWLxBeNeZpJk+xxAbqJ8ywq1va56up+AGu4=
github.com/jfrog/jfrog-cli-evidence v0.11.1-0.20260824063609-79b735ec565e h1:+QYbewvK+PZKbfPpxYmy0bewhqMFtJPk/tUbCICjf8U=
github.com/jfrog/jfrog-cli-evidence v0.11.1-0.20260824063609-79b735ec565e/go.mod h1:I83k7IH/cmMh00LyZOp13jctj8/pPKVHSAlu7GMJgwI=
github.com/jfrog/jfrog-cli-platform-services v1.10.1-0.20260618062042-6053ab368cab h1:Zn/qB8LYhSu82YDtbqXwErN1RPHTHe/a3gQY6Ti/OBE=
@@ -758,14 +758,14 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
-golang.org/x/crypto v0.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw=
-golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk=
+golang.org/x/crypto v0.56.0 h1:GUh5Ii4J5jtcseSMiRqr1jXCNHoxjeV9Fmekc2oLy6Y=
+golang.org/x/crypto v0.56.0/go.mod h1:OMW5y6CY9l38uPLmxU6l6pwcXp1obtLo3e6gT7gQR2I=
golang.org/x/exp v0.0.0-20260727155853-b88d891fe743 h1:ex206bKw+v3K0dm3andkrIF+ijyQKJG1pLgwQ2PYdQM=
golang.org/x/exp v0.0.0-20260727155853-b88d891fe743/go.mod h1:EdfpwwqSu+0Li0mzskwHU6FWDV3t9Q+RZDo3QMUtL3Q=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
-golang.org/x/mod v0.38.0 h1:MECBjubtXD7yj4HrhIUcywNaGeNVUdfVnxmPajOk4yk=
-golang.org/x/mod v0.38.0/go.mod h1:V6Xz0pq8TQ3dGqVQ1FVHuelZpAL0uNhSkk9ogYP3c40=
+golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs=
+golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -834,16 +834,16 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
-golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs=
-golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY=
+golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8=
+golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M=
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
-golang.org/x/tools v0.48.0 h1:3+hClM1aLL5mjMKm5ovokw9epgRXPuu2tILgismM6RE=
-golang.org/x/tools v0.48.0/go.mod h1:08xX0orndb/F7jJxGDicx061tyd5pcMto75YMAXr6lk=
+golang.org/x/tools v0.49.0 h1:3NI7VXzL9+1WZD52Dx2ttoPwD5DWrFGpl9mFZDlmisI=
+golang.org/x/tools v0.49.0/go.mod h1:SJNXV9DBKT0UbdttsQjbfJlAE/q+y36++zo3uL3N0Oo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
diff --git a/main_test.go b/main_test.go
index 640018f10..3d6c32e7a 100644
--- a/main_test.go
+++ b/main_test.go
@@ -77,7 +77,7 @@ func setupIntegrationTests() {
InitArtifactoryTests()
}
- if *tests.TestNpm || *tests.TestPnpm || *tests.TestCargo || *tests.TestGradle || *tests.TestMaven || *tests.TestGo || *tests.TestNuget || *tests.TestPip || *tests.TestPipenv || *tests.TestPoetry || *tests.TestConan || *tests.TestHelm || *tests.TestUv || *tests.TestNix || *tests.TestApt || *tests.TestAlpine || *tests.TestApm || *tests.TestRuby || (*tests.TestArtifactory && !*tests.TestArtifactoryProxy) || *tests.TestArtifactoryProject {
+ if *tests.TestNpm || *tests.TestPnpm || *tests.TestCargo || *tests.TestGradle || *tests.TestMaven || *tests.TestGo || *tests.TestNuget || *tests.TestChoco || *tests.TestPip || *tests.TestPipenv || *tests.TestPoetry || *tests.TestConan || *tests.TestHelm || *tests.TestUv || *tests.TestNix || *tests.TestApt || *tests.TestAlpine || *tests.TestApm || *tests.TestRuby || (*tests.TestArtifactory && !*tests.TestArtifactoryProxy) || *tests.TestArtifactoryProject {
InitBuildToolsTests()
}
if *tests.TestDocker || *tests.TestPodman || *tests.TestDockerScan {
@@ -125,7 +125,7 @@ func tearDownIntegrationTests() {
if (*tests.TestArtifactory && !*tests.TestArtifactoryProxy) || *tests.TestArtifactoryProject {
CleanArtifactoryTests()
}
- if *tests.TestNpm || *tests.TestPnpm || *tests.TestGradle || *tests.TestMaven || *tests.TestGo || *tests.TestNuget || *tests.TestPip || *tests.TestPipenv || *tests.TestPoetry || *tests.TestConan || *tests.TestHelm || *tests.TestNix || *tests.TestCargo || *tests.TestApt || *tests.TestAlpine || *tests.TestApm || *tests.TestRuby || *tests.TestDocker || *tests.TestPodman || *tests.TestDockerScan || (*tests.TestArtifactory && !*tests.TestArtifactoryProxy) || *tests.TestArtifactoryProject {
+ if *tests.TestNpm || *tests.TestPnpm || *tests.TestGradle || *tests.TestMaven || *tests.TestGo || *tests.TestNuget || *tests.TestChoco || *tests.TestPip || *tests.TestPipenv || *tests.TestPoetry || *tests.TestConan || *tests.TestHelm || *tests.TestNix || *tests.TestCargo || *tests.TestApt || *tests.TestAlpine || *tests.TestApm || *tests.TestRuby || *tests.TestDocker || *tests.TestPodman || *tests.TestDockerScan || (*tests.TestArtifactory && !*tests.TestArtifactoryProxy) || *tests.TestArtifactoryProject {
CleanBuildToolsTests()
}
if *tests.TestDistribution {
diff --git a/utils/cliutils/commandsflags.go b/utils/cliutils/commandsflags.go
index 131210556..2f498ca84 100644
--- a/utils/cliutils/commandsflags.go
+++ b/utils/cliutils/commandsflags.go
@@ -64,6 +64,7 @@ const (
Yarn = "yarn"
NugetConfig = "nuget-config"
Nuget = "nuget"
+ Choco = "choco"
Dotnet = "dotnet"
DotnetConfig = "dotnet-config"
Go = "go"
@@ -2259,6 +2260,9 @@ var commandFlags = map[string][]string{
Nuget: {
BuildName, BuildNumber, module, Project, allowInsecureConnections, serverId, repoResolve, repo, nugetV2,
},
+ Choco: {
+ BuildName, BuildNumber, module, Project, serverId, repoResolve, repo,
+ },
DotnetConfig: {
global, serverIdResolve, repoResolve, nugetV2,
},
diff --git a/utils/tests/consts.go b/utils/tests/consts.go
index 12f9928f2..a6875cedf 100644
--- a/utils/tests/consts.go
+++ b/utils/tests/consts.go
@@ -292,6 +292,7 @@ var (
PnpmBuildName = "cli-pnpm-build"
YarnBuildName = "cli-yarn-build"
NuGetBuildName = "cli-nuget-build"
+ ChocoBuildName = "cli-choco-build"
PipBuildName = "cli-pip-build"
PipenvBuildName = "cli-pipenv-build"
PoetryBuildName = "cli-poetry-build"
diff --git a/utils/tests/utils.go b/utils/tests/utils.go
index f4acd5408..915601f90 100644
--- a/utils/tests/utils.go
+++ b/utils/tests/utils.go
@@ -66,6 +66,7 @@ var (
TestGradle *bool
TestMaven *bool
TestNuget *bool
+ TestChoco *bool
TestPip *bool
TestPipenv *bool
TestPoetry *bool
@@ -141,6 +142,7 @@ func init() {
TestGradle = flag.Bool("test.gradle", false, "Test Gradle")
TestMaven = flag.Bool("test.maven", false, "Test Maven")
TestNuget = flag.Bool("test.nuget", false, "Test Nuget")
+ TestChoco = flag.Bool("test.choco", false, "Test Chocolatey")
TestPip = flag.Bool("test.pip", false, "Test Pip")
TestPipenv = flag.Bool("test.pipenv", false, "Test Pipenv")
TestPoetry = flag.Bool("test.poetry", false, "Test Poetry")
@@ -491,6 +493,7 @@ func GetNonVirtualRepositories() map[*string]string {
TestNpm: {&NpmRepo, &NpmScopedRepo, &NpmRemoteRepo},
TestPnpm: {&NpmRepo, &NpmScopedRepo, &NpmRemoteRepo},
TestNuget: {&NugetRemoteRepo, &NugetLocalRepo},
+ TestChoco: {&NugetRemoteRepo, &NugetLocalRepo},
TestPip: {&PypiLocalRepo, &PypiRemoteRepo},
TestPipenv: {&PipenvRemoteRepo},
TestPoetry: {&PoetryLocalRepo, &PoetryRemoteRepo},
@@ -530,6 +533,7 @@ func GetVirtualRepositories() map[*string]string {
TestNpm: {},
TestPnpm: {},
TestNuget: {&NugetVirtualRepo},
+ TestChoco: {&NugetVirtualRepo},
TestPip: {&PypiVirtualRepo},
TestPipenv: {&PipenvVirtualRepo},
TestPoetry: {&PoetryVirtualRepo},
@@ -578,6 +582,7 @@ func GetBuildNames() []string {
TestNpm: {&NpmBuildName, &YarnBuildName},
TestPnpm: {&PnpmBuildName},
TestNuget: {&NuGetBuildName},
+ TestChoco: {&ChocoBuildName},
TestPip: {&PipBuildName},
TestPipenv: {&PipenvBuildName},
TestPoetry: {&PoetryBuildName},
@@ -788,6 +793,7 @@ func AddTimestampToGlobalVars() {
YarnBuildName += uniqueSuffix
MvnBuildName += uniqueSuffix
NuGetBuildName += uniqueSuffix
+ ChocoBuildName += uniqueSuffix
PipBuildName += uniqueSuffix
PipenvBuildName += uniqueSuffix
PoetryBuildName += uniqueSuffix