From b9e277962a1c9ec544f960693393c6b2ae94b519 Mon Sep 17 00:00:00 2001 From: Michael Sverdlov Date: Wed, 29 Jul 2026 11:05:05 +0300 Subject: [PATCH] Say which configuration jf setup changed and how widely it applies jf setup writes user-level package manager configuration, so a run in one project silently changes resolution for every other project the user builds. The only output was "Successfully configured to use JFrog repository ''", which says nothing about the file that changed or its scope, so unrelated builds could start resolving elsewhere with nothing to trace it back to. Add a note naming the configuration and stating that it applies beyond the current directory. Container logins get a credentials-only wording, since docker/podman/helm authenticate rather than redirect resolution and an unqualified 'docker pull alpine' still reaches Docker Hub. Co-Authored-By: Claude Opus 5 --- artifactory/commands/setup/setup.go | 51 ++++++++++++++++++++++++ artifactory/commands/setup/setup_test.go | 36 +++++++++++++++++ 2 files changed, 87 insertions(+) diff --git a/artifactory/commands/setup/setup.go b/artifactory/commands/setup/setup.go index 85651aa6..41caf145 100644 --- a/artifactory/commands/setup/setup.go +++ b/artifactory/commands/setup/setup.go @@ -33,6 +33,54 @@ import ( "golang.org/x/exp/maps" ) +// packageManagerToConfigLocation names the configuration each package manager is +// written to, so the command can say what it changed. Every entry is user-level: +// the settings live in the user's home directory, not in the current project, so +// they take effect for every project this user builds. Saying so matters because +// nothing else in the output reveals that the change is not scoped to the +// directory the command was run in. +var packageManagerToConfigLocation = map[project.ProjectType]string{ + project.Npm: "your user-level npm configuration (.npmrc)", + project.Pnpm: "your user-level pnpm configuration (.npmrc)", + project.Yarn: "your user-level Yarn configuration (.yarnrc)", + project.Pip: "your user-level pip configuration (pip.conf)", + project.Pipenv: "your user-level pip configuration (pip.conf)", + project.Poetry: "your user-level Poetry configuration", + project.Twine: "your user-level Twine configuration (.pypirc)", + project.UV: "your user-level uv configuration (uv.toml)", + project.Nuget: "your user-level NuGet configuration (NuGet.Config)", + project.Dotnet: "your user-level NuGet configuration (NuGet.Config)", + project.Go: "your Go environment (GOPROXY)", + project.Gradle: "a Gradle init script in your Gradle user home", + project.Maven: "your user-level Maven settings (settings.xml)", + project.Docker: "your Docker credential store", + project.Podman: "your Podman credential store", + project.Helm: "your Helm registry credential store", +} + +// containerLoginPackageManagers authenticate to the platform instead of redirecting +// resolution, so the note must not claim that their projects now resolve through +// Artifactory: an unqualified `docker pull alpine` still goes to Docker Hub. +var containerLoginPackageManagers = map[project.ProjectType]bool{ + project.Docker: true, + project.Podman: true, + project.Helm: true, +} + +// configScopeNote describes what the command changed and how widely it applies, or +// an empty string for a package manager we have nothing accurate to say about. +func configScopeNote(packageManager project.ProjectType) string { + location, ok := packageManagerToConfigLocation[packageManager] + if !ok { + return "" + } + if containerLoginPackageManagers[packageManager] { + return fmt.Sprintf("Credentials were saved to %s for your user account.", location) + } + return fmt.Sprintf("This updated %s, so it applies to every %s project for this user, not only the current directory.", + location, packageManager.String()) +} + // packageManagerToRepositoryPackageType maps project types to corresponding Artifactory repository package types. var packageManagerToRepositoryPackageType = map[project.ProjectType]string{ // Npm package managers @@ -195,6 +243,9 @@ func (sc *SetupCommand) Run() (err error) { repoPrefix = coreutils.PrintBoldTitle(fmt.Sprintf(" repository '%s'", sc.repoName)) } log.Output(fmt.Sprintf("Successfully configured %s to use JFrog%s.", coreutils.PrintBoldTitle(sc.packageManager.String()), repoPrefix)) + if note := configScopeNote(sc.packageManager); note != "" { + log.Output(note) + } return nil } diff --git a/artifactory/commands/setup/setup_test.go b/artifactory/commands/setup/setup_test.go index c02ec8d2..dda8c273 100644 --- a/artifactory/commands/setup/setup_test.go +++ b/artifactory/commands/setup/setup_test.go @@ -923,3 +923,39 @@ func TestSetupCommand_MavenCorrupted(t *testing.T) { assert.NotContains(t, content, testCredential(), "Old token should be replaced") }) } + +// Every supported package manager must describe what it changed: the output is the +// only place a user learns the configuration is user-level rather than scoped to the +// directory they ran the command in. +func TestConfigScopeNote_CoversEverySupportedPackageManager(t *testing.T) { + for _, name := range GetSupportedPackageManagersList() { + packageManager := project.FromString(name) + note := configScopeNote(packageManager) + assert.NotEmptyf(t, note, "no scope note for supported package manager %q", name) + assert.Containsf(t, note, "your", "scope note for %q should name the configuration it changed: %s", name, note) + } +} + +// Container logins authenticate rather than redirect resolution, so their note must +// not promise that projects now resolve through Artifactory — an unqualified +// `docker pull alpine` still reaches Docker Hub after `jf setup docker`. +func TestConfigScopeNote_ContainerLoginsDoNotClaimResolution(t *testing.T) { + for _, packageManager := range []project.ProjectType{project.Docker, project.Podman, project.Helm} { + note := configScopeNote(packageManager) + assert.Contains(t, note, "Credentials were saved", packageManager.String()) + assert.NotContains(t, note, "applies to every", packageManager.String()) + } + + // Resolution-changing package managers must state the scope explicitly. + for _, packageManager := range []project.ProjectType{project.Npm, project.Maven, project.Go, project.Pip} { + note := configScopeNote(packageManager) + assert.Contains(t, note, "applies to every", packageManager.String()) + assert.Contains(t, note, "not only the current directory", packageManager.String()) + } +} + +// An unsupported package manager has nothing accurate to say, so it must stay silent +// rather than print a misleading note. +func TestConfigScopeNote_UnknownPackageManagerIsSilent(t *testing.T) { + assert.Empty(t, configScopeNote(project.Cocoapods)) +}