diff --git a/buildtools/cli.go b/buildtools/cli.go index 60058bc2d..5c8eb5924 100644 --- a/buildtools/cli.go +++ b/buildtools/cli.go @@ -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()) diff --git a/go.mod b/go.mod index 5df9cb3a7..50430cc07 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index 8c572cc44..864856bff 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/npm_test.go b/npm_test.go index f9d646876..d3c671a91 100644 --- a/npm_test.go +++ b/npm_test.go @@ -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) @@ -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() @@ -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 @@ -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) } @@ -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) @@ -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") @@ -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)) diff --git a/testdata/yarn/yarnprojectV4/.yarnrc.yml b/testdata/yarn/yarnprojectV4/.yarnrc.yml index e65639eee..154040b0e 100644 --- a/testdata/yarn/yarnprojectV4/.yarnrc.yml +++ b/testdata/yarn/yarnprojectV4/.yarnrc.yml @@ -1 +1,2 @@ -yarnPath: .yarn/releases/yarn-4.6.0.cjs \ No newline at end of file +yarnPath: .yarn/releases/yarn-4.6.0.cjs +enableImmutableInstalls: false \ No newline at end of file