Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion buildtools/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -805,7 +805,11 @@ func YarnCmd(c *cli.Context) error {

configFilePath, err := getProjectConfigPathOrThrow(project.Yarn, "yarn", "yarn-config")
if err != nil {
return err
// In native/FlexPack mode, a missing config file is expected —
// the yarn command resolves server details internally.
if !artutils.ShouldRunNative("") {
return err
}
}

yarnCmd := yarn.NewYarnCommand().SetConfigFilePath(configFilePath).SetArgs(c.Args())
Expand Down
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -248,13 +248,13 @@ require (

//replace github.com/ktrysmt/go-bitbucket => github.com/ktrysmt/go-bitbucket v0.9.80

// replace github.com/jfrog/jfrog-cli-artifactory => github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260331093138-48a54e89a292
replace github.com/jfrog/jfrog-cli-artifactory => github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260416054314-f941180fccc3

//replace github.com/jfrog/build-info-go => github.com/fluxxBot/build-info-go v1.10.10-0.20260105070825-d3f36f619ba5

// replace github.com/jfrog/build-info-go => github.com/reshmifrog/build-info-go v1.10.11-0.20260303032831-71878c7210bf

//replace github.com/jfrog/jfrog-cli-core/v2 => github.com/fluxxBot/jfrog-cli-core/v2 v2.58.1-0.20260105065921-c6488910f44c
replace github.com/jfrog/jfrog-cli-core/v2 => github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20260416053049-61d23df74bd3

//replace github.com/jfrog/jfrog-client-go => github.com/jfrog/jfrog-client-go v1.54.2-0.20251007084958-5eeaa42c31a6

Expand Down
8 changes: 4 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -418,10 +418,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.20260405065840-c930d515ef34 h1:qD53oDmaw7+5HjaU7FupqbB55saabNzMoMtu3kJfmg4=
github.com/jfrog/jfrog-cli-application v1.0.2-0.20260405065840-c930d515ef34/go.mod h1:xum2HquWO5uExa/A7MQs3TgJJVEeoqTR+6Z4mfBr1Xw=
github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260414085427-0d891fde6d05 h1:BNLs4VBRU3UrQTwEQSJw/5CaCgGMc1YDftpUgtb1GUI=
github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260414085427-0d891fde6d05/go.mod h1:u2yb7nO6VLxXwHKoICnxd8NEY4OclCk9YAi2n/Sbpn8=
github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20260414083544-243b4d55328b h1:PCvNCdTYojr9u5X5TVTj/3oSnHEe8kJ9I50X0Wb5iJg=
github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20260414083544-243b4d55328b/go.mod h1:RLLUO+oGDq88e5DPtP/KK2sVgMF32OuoRdVMxSFfb30=
github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260416054314-f941180fccc3 h1:zkF06kQHuL9tYGlQ260xizAvd0iuU3rIbUfIiHr0hCc=
github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260416054314-f941180fccc3/go.mod h1:OpSaxi/jI3aSCl56KQx3mqeJ2a2/LD4E1g7Tl5pE7Ac=
github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20260416053049-61d23df74bd3 h1:dzjGrmTOL2FlUV7WCM25w5C11FJv/4hssiOJ0P7/HFA=
github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20260416053049-61d23df74bd3/go.mod h1:nYC5zoLtokfPvNfc/04XS+1F/SmOQebCo17Tq2R9KgQ=
github.com/jfrog/jfrog-cli-evidence v0.9.2 h1:huiBzQSI9z3OF3l2RphthdXl1aH9zBsvAt+zLsApORI=
github.com/jfrog/jfrog-cli-evidence v0.9.2/go.mod h1:R9faPfyQESBmKrdZCmHvlpmYSHmffswjNnFeT3RMq8I=
github.com/jfrog/jfrog-cli-platform-services v1.10.1-0.20260306102152-984d60a80cec h1:d8CJ/LUGjNwPDPfYLJkAQJNmu+GCWxFsjZrmTcuV5wY=
Expand Down
173 changes: 157 additions & 16 deletions npm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -588,6 +588,14 @@ func initNpmTest(t *testing.T) {
createJfrogHomeConfig(t, true)
}

// enableNativeMode sets JFROG_RUN_NATIVE=true and returns a cleanup function that unsets it.
func enableNativeMode(t *testing.T) func() {
clientTestUtils.SetEnvAndAssert(t, "JFROG_RUN_NATIVE", "true")
return func() {
clientTestUtils.UnSetEnvAndAssert(t, "JFROG_RUN_NATIVE")
}
}

func TestNpmPublishDetailedSummary(t *testing.T) {
initNpmTest(t)
defer cleanNpmTest(t)
Expand Down Expand Up @@ -1058,11 +1066,10 @@ func TestYarnSetVersion(t *testing.T) {
modifyExistingYarnRc(t, "3.2.1")
}

func TestYarnUpgradeToV4(t *testing.T) {
// TestYarnUpgradeToV5 verifies that upgrading to an unsupported yarn major version (v5+) is blocked.
func TestYarnUpgradeToV5(t *testing.T) {
initNpmTest(t)
defer cleanNpmTest(t)

// Temporarily change the cache folder to a temporary folder - to make sure the cache is clean and dependencies will be downloaded from Artifactory
tempDirPath, createTempDirCallback := coretests.CreateTempDirWithCallbackAndAssert(t)
defer createTempDirCallback()

Expand All @@ -1080,7 +1087,6 @@ func TestYarnUpgradeToV4(t *testing.T) {
cleanUpYarnGlobalFolder := clientTestUtils.SetEnvWithCallbackAndAssert(t, "YARN_GLOBAL_FOLDER", tempDirPath)
defer cleanUpYarnGlobalFolder()

// Add "localhost" to http whitelist
yarnExecPath, err := exec.LookPath("yarn")
assert.NoError(t, err)
// Get original http white list config
Expand All @@ -1093,7 +1099,8 @@ func TestYarnUpgradeToV4(t *testing.T) {
}()

jfrogCli := coretests.NewJfrogCli(execMain, "jfrog", "")
err = jfrogCli.Exec("yarn", "set", "version", "4.0.1")
// Yarn v5 is not yet supported — should fail
err = jfrogCli.Exec("yarn", "set", "version", "5.0.0")
assert.Error(t, err)
}

Expand All @@ -1119,12 +1126,40 @@ func TestYarnInV4(t *testing.T) {
cleanUpYarnGlobalFolder := clientTestUtils.SetEnvWithCallbackAndAssert(t, "YARN_GLOBAL_FOLDER", tempDirPath)
defer cleanUpYarnGlobalFolder()

// Add "localhost" to http whitelist (CI Artifactory uses HTTP)
yarnExecPath, err := exec.LookPath("yarn")
assert.NoError(t, err)
origWhitelist, err := yarn.ConfigGet("unsafeHttpWhitelist", yarnExecPath, true)
assert.NoError(t, err)
assert.NoError(t, yarn.ConfigSet("unsafeHttpWhitelist", "[\"localhost\"]", yarnExecPath, true))
defer func() {
assert.NoError(t, yarn.ConfigSet("unsafeHttpWhitelist", origWhitelist, yarnExecPath, true))
}()

jfrogCli := coretests.NewJfrogCli(execMain, "jfrog", "")
err = jfrogCli.Exec("yarn", "install")
assert.Error(t, err)
// Yarn v4 is now supported — install should succeed and collect build-info
assert.NoError(t, jfrogCli.Exec("yarn", "--build-name="+tests.YarnBuildName, "--build-number=2", "--module=yarnV4Module"))

validateNpmLocalBuildInfo(t, tests.YarnBuildName, "2", "yarnV4Module")

assert.NoError(t, artifactoryCli.WithoutCredentials().Exec("bp", tests.YarnBuildName, "2"))
publishedBuildInfo, found, err := tests.GetBuildInfo(serverDetails, tests.YarnBuildName, "2")
assert.NoError(t, err)
assert.True(t, found)
if assert.NotNil(t, publishedBuildInfo) && assert.NotNil(t, publishedBuildInfo.BuildInfo) {
assert.Equal(t, 1, len(publishedBuildInfo.BuildInfo.Modules))
if len(publishedBuildInfo.BuildInfo.Modules) > 0 {
assert.Equal(t, buildinfo.Npm, publishedBuildInfo.BuildInfo.Modules[0].Type)
expectedDependencies := []expectedDependency{{id: "xml:1.0.1"}, {id: "json:9.0.6"}}
equalDependenciesSlices(t, expectedDependencies, publishedBuildInfo.BuildInfo.Modules[0].Dependencies)
}
}
inttestutils.DeleteBuild(serverDetails.ArtifactoryUrl, tests.YarnBuildName, artHttpDetails)
}

func TestYarnChangeVersionInV4(t *testing.T) {
// TestYarnChangeVersionInV4ToV5 verifies that upgrading from v4 to unsupported v5 is blocked,
// while downgrading to v3 is allowed.
func TestYarnChangeVersionInV4ToV5(t *testing.T) {
initNpmTest(t)
defer cleanNpmTest(t)

Expand All @@ -1146,33 +1181,31 @@ func TestYarnChangeVersionInV4(t *testing.T) {
cleanUpYarnGlobalFolder := clientTestUtils.SetEnvWithCallbackAndAssert(t, "YARN_GLOBAL_FOLDER", tempDirPath)
defer cleanUpYarnGlobalFolder()

// Add "localhost" to http whitelist
yarnExecPath, err := exec.LookPath("yarn")
assert.NoError(t, err)

yarnrcPath := ".yarnrc.yml"
data, err := os.ReadFile(yarnrcPath)
assert.NoError(t, err)
// Parse YAML
var config = make(map[string]any)
err = yaml.Unmarshal(data, &config)
if err != nil {
assert.NoError(t, err)
}
assert.NoError(t, err)
config["unsafeHttpWhitelist"] = []string{"localhost"}
updatedYamlData, err := yaml.Marshal(&config)
assert.NoError(t, err)
err = os.WriteFile(yarnrcPath, updatedYamlData, 0644)
assert.NoError(t, err)

assert.NoError(t, err)
defer func() {
// Restore original whitelist config
assert.NoError(t, yarn.ConfigSet("unsafeHttpWhitelist", "[]", yarnExecPath, true))
}()

jfrogCli := coretests.NewJfrogCli(execMain, "jfrog", "")

// Upgrading to v5 should fail
err = jfrogCli.Exec("yarn", "set", "version", "5.0.0")
assert.Error(t, err)

// Downgrading to v3 should succeed
err = jfrogCli.Exec("yarn", "set", "version", "3.2.1")
assert.NoError(t, err)
modifyExistingYarnRc(t, "3.2.1")
Expand All @@ -1181,6 +1214,114 @@ func TestYarnChangeVersionInV4(t *testing.T) {
assert.NoError(t, err)
}

// TestYarnV4NativeMode verifies that yarn v4 works in native mode (JFROG_RUN_NATIVE=true)
// without a jfrog config file, using the default server for build-info collection.
func TestYarnV4NativeMode(t *testing.T) {
initNpmTest(t)
defer cleanNpmTest(t)
defer enableNativeMode(t)()

tempDirPath, createTempDirCallback := coretests.CreateTempDirWithCallbackAndAssert(t)
defer createTempDirCallback()

testDataSource := filepath.Join(filepath.FromSlash(tests.GetTestResourcesPath()), "yarn")
testDataTarget := filepath.Join(tempDirPath, tests.Out, "yarn")
assert.NoError(t, biutils.CopyDir(testDataSource, testDataTarget, true, nil))

wd, err := os.Getwd()
assert.NoError(t, err, "Failed to get current dir")

yarnProjectPath := filepath.Join(testDataTarget, "yarnprojectV4")
// No config file created — native mode should work without it
chdirCallback := clientTestUtils.ChangeDirWithCallback(t, wd, yarnProjectPath)
defer chdirCallback()
cleanUpYarnGlobalFolder := clientTestUtils.SetEnvWithCallbackAndAssert(t, "YARN_GLOBAL_FOLDER", tempDirPath)
defer cleanUpYarnGlobalFolder()

// Add "localhost" to http whitelist (CI Artifactory uses HTTP)
yarnExecPath, err := exec.LookPath("yarn")
assert.NoError(t, err)
origWhitelist, err := yarn.ConfigGet("unsafeHttpWhitelist", yarnExecPath, true)
assert.NoError(t, err)
assert.NoError(t, yarn.ConfigSet("unsafeHttpWhitelist", "[\"localhost\"]", yarnExecPath, true))
defer func() {
assert.NoError(t, yarn.ConfigSet("unsafeHttpWhitelist", origWhitelist, yarnExecPath, true))
}()

jfrogCli := coretests.NewJfrogCli(execMain, "jfrog", "")
assert.NoError(t, jfrogCli.Exec("yarn", "--build-name="+tests.YarnBuildName, "--build-number=3", "--module=yarnV4NativeModule"))

validateNpmLocalBuildInfo(t, tests.YarnBuildName, "3", "yarnV4NativeModule")

assert.NoError(t, artifactoryCli.WithoutCredentials().Exec("bp", tests.YarnBuildName, "3"))
publishedBuildInfo, found, err := tests.GetBuildInfo(serverDetails, tests.YarnBuildName, "3")
assert.NoError(t, err)
assert.True(t, found)
if assert.NotNil(t, publishedBuildInfo) && assert.NotNil(t, publishedBuildInfo.BuildInfo) {
assert.Equal(t, 1, len(publishedBuildInfo.BuildInfo.Modules))
if len(publishedBuildInfo.BuildInfo.Modules) > 0 {
assert.Equal(t, buildinfo.Npm, publishedBuildInfo.BuildInfo.Modules[0].Type)
expectedDependencies := []expectedDependency{{id: "xml:1.0.1"}, {id: "json:9.0.6"}}
equalDependenciesSlices(t, expectedDependencies, publishedBuildInfo.BuildInfo.Modules[0].Dependencies)
}
}
inttestutils.DeleteBuild(serverDetails.ArtifactoryUrl, tests.YarnBuildName, artHttpDetails)
}

// TestYarnV2NativeModeWithServerId verifies that yarn v2+ works in native mode
// with an explicit --server-id flag for build-info collection.
func TestYarnV2NativeModeWithServerId(t *testing.T) {
initNpmTest(t)
defer cleanNpmTest(t)
defer enableNativeMode(t)()

tempDirPath, createTempDirCallback := coretests.CreateTempDirWithCallbackAndAssert(t)
defer createTempDirCallback()

testDataSource := filepath.Join(filepath.FromSlash(tests.GetTestResourcesPath()), "yarn")
testDataTarget := filepath.Join(tempDirPath, tests.Out, "yarn")
assert.NoError(t, biutils.CopyDir(testDataSource, testDataTarget, true, nil))

wd, err := os.Getwd()
assert.NoError(t, err, "Failed to get current dir")

yarnProjectPath := filepath.Join(testDataTarget, "yarnprojectV2")
// No config file created — native mode should work without it
chdirCallback := clientTestUtils.ChangeDirWithCallback(t, wd, yarnProjectPath)
defer chdirCallback()
cleanUpYarnGlobalFolder := clientTestUtils.SetEnvWithCallbackAndAssert(t, "YARN_GLOBAL_FOLDER", tempDirPath)
defer cleanUpYarnGlobalFolder()

// Add "localhost" to http whitelist
yarnExecPath, err := exec.LookPath("yarn")
assert.NoError(t, err)
origWhitelist, err := yarn.ConfigGet("unsafeHttpWhitelist", yarnExecPath, true)
assert.NoError(t, err)
assert.NoError(t, yarn.ConfigSet("unsafeHttpWhitelist", "[\"localhost\"]", yarnExecPath, true))
defer func() {
assert.NoError(t, yarn.ConfigSet("unsafeHttpWhitelist", origWhitelist, yarnExecPath, true))
}()

jfrogCli := coretests.NewJfrogCli(execMain, "jfrog", "")
assert.NoError(t, jfrogCli.Exec("yarn", "--build-name="+tests.YarnBuildName, "--build-number=4", "--module=yarnV2NativeModule", "--server-id=default"))

validateNpmLocalBuildInfo(t, tests.YarnBuildName, "4", "yarnV2NativeModule")

assert.NoError(t, artifactoryCli.WithoutCredentials().Exec("bp", tests.YarnBuildName, "4"))
publishedBuildInfo, found, err := tests.GetBuildInfo(serverDetails, tests.YarnBuildName, "4")
assert.NoError(t, err)
assert.True(t, found)
if assert.NotNil(t, publishedBuildInfo) && assert.NotNil(t, publishedBuildInfo.BuildInfo) {
assert.Equal(t, 1, len(publishedBuildInfo.BuildInfo.Modules))
if len(publishedBuildInfo.BuildInfo.Modules) > 0 {
assert.Equal(t, buildinfo.Npm, publishedBuildInfo.BuildInfo.Modules[0].Type)
expectedDependencies := []expectedDependency{{id: "xml:1.0.1"}, {id: "json:9.0.6"}}
equalDependenciesSlices(t, expectedDependencies, publishedBuildInfo.BuildInfo.Modules[0].Dependencies)
}
}
inttestutils.DeleteBuild(serverDetails.ArtifactoryUrl, tests.YarnBuildName, artHttpDetails)
}

// Checks if the expected dependencies match the actual dependencies. Only the dependencies' IDs and scopes (not more than one scope) are compared.
func equalDependenciesSlices(t *testing.T, expectedDependencies []expectedDependency, actualDependencies []buildinfo.Dependency) {
assert.Equal(t, len(expectedDependencies), len(actualDependencies))
Expand Down
3 changes: 2 additions & 1 deletion testdata/yarn/yarnprojectV4/.yarnrc.yml
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
yarnPath: .yarn/releases/yarn-4.6.0.cjs
yarnPath: .yarn/releases/yarn-4.6.0.cjs
enableImmutableInstalls: false
Loading