@@ -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+
591599func 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.
11851306func equalDependenciesSlices (t * testing.T , expectedDependencies []expectedDependency , actualDependencies []buildinfo.Dependency ) {
11861307 assert .Equal (t , len (expectedDependencies ), len (actualDependencies ))
0 commit comments