Skip to content

Commit d67f403

Browse files
committed
RTECO-1035 - Added Support for yarn v4
1 parent 7fdbd56 commit d67f403

5 files changed

Lines changed: 150 additions & 24 deletions

File tree

buildtools/cli.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -805,7 +805,11 @@ func YarnCmd(c *cli.Context) error {
805805

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

811815
yarnCmd := yarn.NewYarnCommand().SetConfigFilePath(configFilePath).SetArgs(c.Args())

go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -248,13 +248,13 @@ require (
248248

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

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

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

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

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

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

go.sum

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -418,10 +418,10 @@ github.com/jfrog/jfrog-apps-config v1.0.1 h1:mtv6k7g8A8BVhlHGlSveapqf4mJfonwvXYL
418418
github.com/jfrog/jfrog-apps-config v1.0.1/go.mod h1:8AIIr1oY9JuH5dylz2S6f8Ym2MaadPLR6noCBO4C22w=
419419
github.com/jfrog/jfrog-cli-application v1.0.2-0.20260405065840-c930d515ef34 h1:qD53oDmaw7+5HjaU7FupqbB55saabNzMoMtu3kJfmg4=
420420
github.com/jfrog/jfrog-cli-application v1.0.2-0.20260405065840-c930d515ef34/go.mod h1:xum2HquWO5uExa/A7MQs3TgJJVEeoqTR+6Z4mfBr1Xw=
421-
github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260414085427-0d891fde6d05 h1:BNLs4VBRU3UrQTwEQSJw/5CaCgGMc1YDftpUgtb1GUI=
422-
github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260414085427-0d891fde6d05/go.mod h1:u2yb7nO6VLxXwHKoICnxd8NEY4OclCk9YAi2n/Sbpn8=
423-
github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20260414083544-243b4d55328b h1:PCvNCdTYojr9u5X5TVTj/3oSnHEe8kJ9I50X0Wb5iJg=
424-
github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20260414083544-243b4d55328b/go.mod h1:RLLUO+oGDq88e5DPtP/KK2sVgMF32OuoRdVMxSFfb30=
421+
github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260416054314-f941180fccc3 h1:zkF06kQHuL9tYGlQ260xizAvd0iuU3rIbUfIiHr0hCc=
422+
github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260416054314-f941180fccc3/go.mod h1:OpSaxi/jI3aSCl56KQx3mqeJ2a2/LD4E1g7Tl5pE7Ac=
423+
github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20260416053049-61d23df74bd3 h1:dzjGrmTOL2FlUV7WCM25w5C11FJv/4hssiOJ0P7/HFA=
424+
github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20260416053049-61d23df74bd3/go.mod h1:nYC5zoLtokfPvNfc/04XS+1F/SmOQebCo17Tq2R9KgQ=
425425
github.com/jfrog/jfrog-cli-evidence v0.9.2 h1:huiBzQSI9z3OF3l2RphthdXl1aH9zBsvAt+zLsApORI=
426426
github.com/jfrog/jfrog-cli-evidence v0.9.2/go.mod h1:R9faPfyQESBmKrdZCmHvlpmYSHmffswjNnFeT3RMq8I=
427427
github.com/jfrog/jfrog-cli-platform-services v1.10.1-0.20260306102152-984d60a80cec h1:d8CJ/LUGjNwPDPfYLJkAQJNmu+GCWxFsjZrmTcuV5wY=

npm_test.go

Lines changed: 137 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -588,6 +588,14 @@ func initNpmTest(t *testing.T) {
588588
createJfrogHomeConfig(t, true)
589589
}
590590

591+
// enableNativeMode sets JFROG_RUN_NATIVE=true and returns a cleanup function that unsets it.
592+
func enableNativeMode(t *testing.T) func() {
593+
clientTestUtils.SetEnvAndAssert(t, "JFROG_RUN_NATIVE", "true")
594+
return func() {
595+
clientTestUtils.UnSetEnvAndAssert(t, "JFROG_RUN_NATIVE")
596+
}
597+
}
598+
591599
func TestNpmPublishDetailedSummary(t *testing.T) {
592600
initNpmTest(t)
593601
defer cleanNpmTest(t)
@@ -1058,11 +1066,10 @@ func TestYarnSetVersion(t *testing.T) {
10581066
modifyExistingYarnRc(t, "3.2.1")
10591067
}
10601068

1061-
func TestYarnUpgradeToV4(t *testing.T) {
1069+
// TestYarnUpgradeToV5 verifies that upgrading to an unsupported yarn major version (v5+) is blocked.
1070+
func TestYarnUpgradeToV5(t *testing.T) {
10621071
initNpmTest(t)
10631072
defer cleanNpmTest(t)
1064-
1065-
// Temporarily change the cache folder to a temporary folder - to make sure the cache is clean and dependencies will be downloaded from Artifactory
10661073
tempDirPath, createTempDirCallback := coretests.CreateTempDirWithCallbackAndAssert(t)
10671074
defer createTempDirCallback()
10681075

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

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

10951101
jfrogCli := coretests.NewJfrogCli(execMain, "jfrog", "")
1096-
err = jfrogCli.Exec("yarn", "set", "version", "4.0.1")
1102+
// Yarn v5 is not yet supported — should fail
1103+
err = jfrogCli.Exec("yarn", "set", "version", "5.0.0")
10971104
assert.Error(t, err)
10981105
}
10991106

@@ -1120,11 +1127,29 @@ func TestYarnInV4(t *testing.T) {
11201127
defer cleanUpYarnGlobalFolder()
11211128

11221129
jfrogCli := coretests.NewJfrogCli(execMain, "jfrog", "")
1123-
err = jfrogCli.Exec("yarn", "install")
1124-
assert.Error(t, err)
1130+
// Yarn v4 is now supported — install should succeed and collect build-info
1131+
assert.NoError(t, jfrogCli.Exec("yarn", "--build-name="+tests.YarnBuildName, "--build-number=2", "--module=yarnV4Module"))
1132+
1133+
validateNpmLocalBuildInfo(t, tests.YarnBuildName, "2", "yarnV4Module")
1134+
1135+
assert.NoError(t, artifactoryCli.WithoutCredentials().Exec("bp", tests.YarnBuildName, "2"))
1136+
publishedBuildInfo, found, err := tests.GetBuildInfo(serverDetails, tests.YarnBuildName, "2")
1137+
assert.NoError(t, err)
1138+
assert.True(t, found)
1139+
if assert.NotNil(t, publishedBuildInfo) && assert.NotNil(t, publishedBuildInfo.BuildInfo) {
1140+
assert.Equal(t, 1, len(publishedBuildInfo.BuildInfo.Modules))
1141+
if len(publishedBuildInfo.BuildInfo.Modules) > 0 {
1142+
assert.Equal(t, buildinfo.Npm, publishedBuildInfo.BuildInfo.Modules[0].Type)
1143+
expectedDependencies := []expectedDependency{{id: "xml:1.0.1"}, {id: "json:9.0.6"}}
1144+
equalDependenciesSlices(t, expectedDependencies, publishedBuildInfo.BuildInfo.Modules[0].Dependencies)
1145+
}
1146+
}
1147+
inttestutils.DeleteBuild(serverDetails.ArtifactoryUrl, tests.YarnBuildName, artHttpDetails)
11251148
}
11261149

1127-
func TestYarnChangeVersionInV4(t *testing.T) {
1150+
// TestYarnChangeVersionInV4ToV5 verifies that upgrading from v4 to unsupported v5 is blocked,
1151+
// while downgrading to v3 is allowed.
1152+
func TestYarnChangeVersionInV4ToV5(t *testing.T) {
11281153
initNpmTest(t)
11291154
defer cleanNpmTest(t)
11301155

@@ -1146,33 +1171,31 @@ func TestYarnChangeVersionInV4(t *testing.T) {
11461171
cleanUpYarnGlobalFolder := clientTestUtils.SetEnvWithCallbackAndAssert(t, "YARN_GLOBAL_FOLDER", tempDirPath)
11471172
defer cleanUpYarnGlobalFolder()
11481173

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

11531177
yarnrcPath := ".yarnrc.yml"
11541178
data, err := os.ReadFile(yarnrcPath)
11551179
assert.NoError(t, err)
1156-
// Parse YAML
11571180
var config = make(map[string]any)
11581181
err = yaml.Unmarshal(data, &config)
1159-
if err != nil {
1160-
assert.NoError(t, err)
1161-
}
1182+
assert.NoError(t, err)
11621183
config["unsafeHttpWhitelist"] = []string{"localhost"}
11631184
updatedYamlData, err := yaml.Marshal(&config)
11641185
assert.NoError(t, err)
11651186
err = os.WriteFile(yarnrcPath, updatedYamlData, 0644)
1166-
assert.NoError(t, err)
1167-
11681187
assert.NoError(t, err)
11691188
defer func() {
1170-
// Restore original whitelist config
11711189
assert.NoError(t, yarn.ConfigSet("unsafeHttpWhitelist", "[]", yarnExecPath, true))
11721190
}()
11731191

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

1194+
// Upgrading to v5 should fail
1195+
err = jfrogCli.Exec("yarn", "set", "version", "5.0.0")
1196+
assert.Error(t, err)
1197+
1198+
// Downgrading to v3 should succeed
11761199
err = jfrogCli.Exec("yarn", "set", "version", "3.2.1")
11771200
assert.NoError(t, err)
11781201
modifyExistingYarnRc(t, "3.2.1")
@@ -1181,6 +1204,104 @@ func TestYarnChangeVersionInV4(t *testing.T) {
11811204
assert.NoError(t, err)
11821205
}
11831206

1207+
// TestYarnV4NativeMode verifies that yarn v4 works in native mode (JFROG_RUN_NATIVE=true)
1208+
// without a jfrog config file, using the default server for build-info collection.
1209+
func TestYarnV4NativeMode(t *testing.T) {
1210+
initNpmTest(t)
1211+
defer cleanNpmTest(t)
1212+
defer enableNativeMode(t)()
1213+
1214+
tempDirPath, createTempDirCallback := coretests.CreateTempDirWithCallbackAndAssert(t)
1215+
defer createTempDirCallback()
1216+
1217+
testDataSource := filepath.Join(filepath.FromSlash(tests.GetTestResourcesPath()), "yarn")
1218+
testDataTarget := filepath.Join(tempDirPath, tests.Out, "yarn")
1219+
assert.NoError(t, biutils.CopyDir(testDataSource, testDataTarget, true, nil))
1220+
1221+
wd, err := os.Getwd()
1222+
assert.NoError(t, err, "Failed to get current dir")
1223+
1224+
yarnProjectPath := filepath.Join(testDataTarget, "yarnprojectV4")
1225+
// No config file created — native mode should work without it
1226+
chdirCallback := clientTestUtils.ChangeDirWithCallback(t, wd, yarnProjectPath)
1227+
defer chdirCallback()
1228+
cleanUpYarnGlobalFolder := clientTestUtils.SetEnvWithCallbackAndAssert(t, "YARN_GLOBAL_FOLDER", tempDirPath)
1229+
defer cleanUpYarnGlobalFolder()
1230+
1231+
jfrogCli := coretests.NewJfrogCli(execMain, "jfrog", "")
1232+
assert.NoError(t, jfrogCli.Exec("yarn", "--build-name="+tests.YarnBuildName, "--build-number=3", "--module=yarnV4NativeModule"))
1233+
1234+
validateNpmLocalBuildInfo(t, tests.YarnBuildName, "3", "yarnV4NativeModule")
1235+
1236+
assert.NoError(t, artifactoryCli.WithoutCredentials().Exec("bp", tests.YarnBuildName, "3"))
1237+
publishedBuildInfo, found, err := tests.GetBuildInfo(serverDetails, tests.YarnBuildName, "3")
1238+
assert.NoError(t, err)
1239+
assert.True(t, found)
1240+
if assert.NotNil(t, publishedBuildInfo) && assert.NotNil(t, publishedBuildInfo.BuildInfo) {
1241+
assert.Equal(t, 1, len(publishedBuildInfo.BuildInfo.Modules))
1242+
if len(publishedBuildInfo.BuildInfo.Modules) > 0 {
1243+
assert.Equal(t, buildinfo.Npm, publishedBuildInfo.BuildInfo.Modules[0].Type)
1244+
expectedDependencies := []expectedDependency{{id: "xml:1.0.1"}, {id: "json:9.0.6"}}
1245+
equalDependenciesSlices(t, expectedDependencies, publishedBuildInfo.BuildInfo.Modules[0].Dependencies)
1246+
}
1247+
}
1248+
inttestutils.DeleteBuild(serverDetails.ArtifactoryUrl, tests.YarnBuildName, artHttpDetails)
1249+
}
1250+
1251+
// TestYarnV2NativeModeWithServerId verifies that yarn v2+ works in native mode
1252+
// with an explicit --server-id flag for build-info collection.
1253+
func TestYarnV2NativeModeWithServerId(t *testing.T) {
1254+
initNpmTest(t)
1255+
defer cleanNpmTest(t)
1256+
defer enableNativeMode(t)()
1257+
1258+
tempDirPath, createTempDirCallback := coretests.CreateTempDirWithCallbackAndAssert(t)
1259+
defer createTempDirCallback()
1260+
1261+
testDataSource := filepath.Join(filepath.FromSlash(tests.GetTestResourcesPath()), "yarn")
1262+
testDataTarget := filepath.Join(tempDirPath, tests.Out, "yarn")
1263+
assert.NoError(t, biutils.CopyDir(testDataSource, testDataTarget, true, nil))
1264+
1265+
wd, err := os.Getwd()
1266+
assert.NoError(t, err, "Failed to get current dir")
1267+
1268+
yarnProjectPath := filepath.Join(testDataTarget, "yarnprojectV2")
1269+
// No config file created — native mode should work without it
1270+
chdirCallback := clientTestUtils.ChangeDirWithCallback(t, wd, yarnProjectPath)
1271+
defer chdirCallback()
1272+
cleanUpYarnGlobalFolder := clientTestUtils.SetEnvWithCallbackAndAssert(t, "YARN_GLOBAL_FOLDER", tempDirPath)
1273+
defer cleanUpYarnGlobalFolder()
1274+
1275+
// Add "localhost" to http whitelist
1276+
yarnExecPath, err := exec.LookPath("yarn")
1277+
assert.NoError(t, err)
1278+
origWhitelist, err := yarn.ConfigGet("unsafeHttpWhitelist", yarnExecPath, true)
1279+
assert.NoError(t, err)
1280+
assert.NoError(t, yarn.ConfigSet("unsafeHttpWhitelist", "[\"localhost\"]", yarnExecPath, true))
1281+
defer func() {
1282+
assert.NoError(t, yarn.ConfigSet("unsafeHttpWhitelist", origWhitelist, yarnExecPath, true))
1283+
}()
1284+
1285+
jfrogCli := coretests.NewJfrogCli(execMain, "jfrog", "")
1286+
assert.NoError(t, jfrogCli.Exec("yarn", "--build-name="+tests.YarnBuildName, "--build-number=4", "--module=yarnV2NativeModule", "--server-id=default"))
1287+
1288+
validateNpmLocalBuildInfo(t, tests.YarnBuildName, "4", "yarnV2NativeModule")
1289+
1290+
assert.NoError(t, artifactoryCli.WithoutCredentials().Exec("bp", tests.YarnBuildName, "4"))
1291+
publishedBuildInfo, found, err := tests.GetBuildInfo(serverDetails, tests.YarnBuildName, "4")
1292+
assert.NoError(t, err)
1293+
assert.True(t, found)
1294+
if assert.NotNil(t, publishedBuildInfo) && assert.NotNil(t, publishedBuildInfo.BuildInfo) {
1295+
assert.Equal(t, 1, len(publishedBuildInfo.BuildInfo.Modules))
1296+
if len(publishedBuildInfo.BuildInfo.Modules) > 0 {
1297+
assert.Equal(t, buildinfo.Npm, publishedBuildInfo.BuildInfo.Modules[0].Type)
1298+
expectedDependencies := []expectedDependency{{id: "xml:1.0.1"}, {id: "json:9.0.6"}}
1299+
equalDependenciesSlices(t, expectedDependencies, publishedBuildInfo.BuildInfo.Modules[0].Dependencies)
1300+
}
1301+
}
1302+
inttestutils.DeleteBuild(serverDetails.ArtifactoryUrl, tests.YarnBuildName, artHttpDetails)
1303+
}
1304+
11841305
// Checks if the expected dependencies match the actual dependencies. Only the dependencies' IDs and scopes (not more than one scope) are compared.
11851306
func equalDependenciesSlices(t *testing.T, expectedDependencies []expectedDependency, actualDependencies []buildinfo.Dependency) {
11861307
assert.Equal(t, len(expectedDependencies), len(actualDependencies))
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
yarnPath: .yarn/releases/yarn-4.6.0.cjs
1+
yarnPath: .yarn/releases/yarn-4.6.0.cjs
2+
enableImmutableInstalls: false

0 commit comments

Comments
 (0)