@@ -2717,3 +2717,241 @@ func getPackageNames(ws *Workspace) []string {
27172717 }
27182718 return names
27192719}
2720+
2721+ // TestYarnPackage_ScopedLinkDependencies_Integration verifies that yarn packages with link:
2722+ // dependencies to scoped packages (e.g., @scope/pkg) are correctly built.
2723+ //
2724+ // Scoped packages have an extra directory level in node_modules:
2725+ // ./node_modules/@scope/pkg-name/package.json
2726+ //
2727+ // This requires different --strip-components handling than non-scoped packages.
2728+ func TestYarnPackage_ScopedLinkDependencies_Integration (t * testing.T ) {
2729+ if testing .Short () {
2730+ t .Skip ("Skipping integration test in short mode" )
2731+ }
2732+
2733+ // Ensure yarn is available
2734+ if err := exec .Command ("yarn" , "--version" ).Run (); err != nil {
2735+ t .Skip ("yarn not available, skipping integration test" )
2736+ }
2737+
2738+ // Ensure node is available
2739+ if err := exec .Command ("node" , "--version" ).Run (); err != nil {
2740+ t .Skip ("node not available, skipping integration test" )
2741+ }
2742+
2743+ tmpDir := t .TempDir ()
2744+
2745+ // Create WORKSPACE.yaml
2746+ workspaceYAML := `defaultTarget: "app:lib"`
2747+ workspacePath := filepath .Join (tmpDir , "WORKSPACE.yaml" )
2748+ if err := os .WriteFile (workspacePath , []byte (workspaceYAML ), 0644 ); err != nil {
2749+ t .Fatal (err )
2750+ }
2751+
2752+ // Create scoped package directory structure: packages/@test/utils
2753+ scopedPkgDir := filepath .Join (tmpDir , "packages" , "@test" , "utils" )
2754+ if err := os .MkdirAll (scopedPkgDir , 0755 ); err != nil {
2755+ t .Fatal (err )
2756+ }
2757+
2758+ // Create scoped package package.json
2759+ scopedPkgPackageJSON := `{
2760+ "name": "@test/utils",
2761+ "version": "1.0.0",
2762+ "main": "index.js"
2763+ }`
2764+ if err := os .WriteFile (filepath .Join (scopedPkgDir , "package.json" ), []byte (scopedPkgPackageJSON ), 0644 ); err != nil {
2765+ t .Fatal (err )
2766+ }
2767+
2768+ // Create scoped package index.js
2769+ scopedPkgIndexJS := `module.exports = {
2770+ formatName: function(name) {
2771+ return "Formatted: " + name;
2772+ }
2773+ };`
2774+ if err := os .WriteFile (filepath .Join (scopedPkgDir , "index.js" ), []byte (scopedPkgIndexJS ), 0644 ); err != nil {
2775+ t .Fatal (err )
2776+ }
2777+
2778+ // Create scoped package yarn.lock (empty but required for YarnApp)
2779+ scopedPkgYarnLock := `# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2780+ # yarn lockfile v1
2781+
2782+ `
2783+ if err := os .WriteFile (filepath .Join (scopedPkgDir , "yarn.lock" ), []byte (scopedPkgYarnLock ), 0644 ); err != nil {
2784+ t .Fatal (err )
2785+ }
2786+
2787+ // Create scoped package BUILD.yaml
2788+ // Using packaging: app (YarnApp) to test the ./node_modules/@scope/pkg/ extraction path
2789+ // YarnApp creates a tarball with ./node_modules/<pkg-name>/ structure
2790+ scopedPkgBuildYAML := `packages:
2791+ - name: lib
2792+ type: yarn
2793+ srcs:
2794+ - "package.json"
2795+ - "yarn.lock"
2796+ - "index.js"
2797+ config:
2798+ packaging: app
2799+ dontTest: true
2800+ commands:
2801+ build: ["echo", "build complete"]`
2802+ if err := os .WriteFile (filepath .Join (scopedPkgDir , "BUILD.yaml" ), []byte (scopedPkgBuildYAML ), 0644 ); err != nil {
2803+ t .Fatal (err )
2804+ }
2805+
2806+ // Create app directory (depends on @test/utils)
2807+ appDir := filepath .Join (tmpDir , "app" )
2808+ if err := os .MkdirAll (appDir , 0755 ); err != nil {
2809+ t .Fatal (err )
2810+ }
2811+
2812+ // Create app package.json with link: dependency to scoped package
2813+ appPackageJSON := `{
2814+ "name": "test-app",
2815+ "version": "1.0.0",
2816+ "dependencies": {
2817+ "@test/utils": "link:./../packages/@test/utils"
2818+ },
2819+ "scripts": {
2820+ "test": "node test.js"
2821+ }
2822+ }`
2823+ if err := os .WriteFile (filepath .Join (appDir , "package.json" ), []byte (appPackageJSON ), 0644 ); err != nil {
2824+ t .Fatal (err )
2825+ }
2826+
2827+ // Create app yarn.lock with link: reference to scoped package
2828+ // Note: yarn.lock normalizes the path
2829+ appYarnLock := `# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2830+ # yarn lockfile v1
2831+
2832+
2833+ "@test/utils@link:../packages/@test/utils":
2834+ version "1.0.0"
2835+ `
2836+ if err := os .WriteFile (filepath .Join (appDir , "yarn.lock" ), []byte (appYarnLock ), 0644 ); err != nil {
2837+ t .Fatal (err )
2838+ }
2839+
2840+ // Create app test.js that uses the scoped package
2841+ appTestJS := `const utils = require('@test/utils');
2842+ const result = utils.formatName('World');
2843+ if (result !== 'Formatted: World') {
2844+ console.error('Expected "Formatted: World" but got:', result);
2845+ process.exit(1);
2846+ }
2847+ console.log('Test passed:', result);`
2848+ if err := os .WriteFile (filepath .Join (appDir , "test.js" ), []byte (appTestJS ), 0644 ); err != nil {
2849+ t .Fatal (err )
2850+ }
2851+
2852+ // Create app BUILD.yaml
2853+ appBuildYAML := `packages:
2854+ - name: lib
2855+ type: yarn
2856+ srcs:
2857+ - "package.json"
2858+ - "yarn.lock"
2859+ - "test.js"
2860+ deps:
2861+ - packages/@test/utils:lib
2862+ config:
2863+ packaging: library
2864+ dontTest: true
2865+ commands:
2866+ build: ["echo", "build complete"]`
2867+ if err := os .WriteFile (filepath .Join (appDir , "BUILD.yaml" ), []byte (appBuildYAML ), 0644 ); err != nil {
2868+ t .Fatal (err )
2869+ }
2870+
2871+ // Initialize git repository (required for leeway)
2872+ gitInit := exec .Command ("git" , "init" )
2873+ gitInit .Dir = tmpDir
2874+ gitInit .Env = append (os .Environ (), "GIT_CONFIG_GLOBAL=/dev/null" , "GIT_CONFIG_SYSTEM=/dev/null" )
2875+ if err := gitInit .Run (); err != nil {
2876+ t .Fatalf ("Failed to initialize git repository: %v" , err )
2877+ }
2878+
2879+ gitConfigName := exec .Command ("git" , "config" , "user.name" , "Test User" )
2880+ gitConfigName .Dir = tmpDir
2881+ gitConfigName .Env = append (os .Environ (), "GIT_CONFIG_GLOBAL=/dev/null" , "GIT_CONFIG_SYSTEM=/dev/null" )
2882+ if err := gitConfigName .Run (); err != nil {
2883+ t .Fatalf ("Failed to configure git user.name: %v" , err )
2884+ }
2885+
2886+ gitConfigEmail := exec .Command ("git" , "config" , "user.email" , "test@example.com" )
2887+ gitConfigEmail .Dir = tmpDir
2888+ gitConfigEmail .Env = append (os .Environ (), "GIT_CONFIG_GLOBAL=/dev/null" , "GIT_CONFIG_SYSTEM=/dev/null" )
2889+ if err := gitConfigEmail .Run (); err != nil {
2890+ t .Fatalf ("Failed to configure git user.email: %v" , err )
2891+ }
2892+
2893+ gitAdd := exec .Command ("git" , "add" , "." )
2894+ gitAdd .Dir = tmpDir
2895+ gitAdd .Env = append (os .Environ (), "GIT_CONFIG_GLOBAL=/dev/null" , "GIT_CONFIG_SYSTEM=/dev/null" )
2896+ if err := gitAdd .Run (); err != nil {
2897+ t .Fatalf ("Failed to git add: %v" , err )
2898+ }
2899+
2900+ gitCommit := exec .Command ("git" , "commit" , "-m" , "initial" )
2901+ gitCommit .Dir = tmpDir
2902+ gitCommit .Env = append (os .Environ (),
2903+ "GIT_CONFIG_GLOBAL=/dev/null" ,
2904+ "GIT_CONFIG_SYSTEM=/dev/null" ,
2905+ "GIT_AUTHOR_DATE=2021-01-01T00:00:00Z" ,
2906+ "GIT_COMMITTER_DATE=2021-01-01T00:00:00Z" ,
2907+ )
2908+ if err := gitCommit .Run (); err != nil {
2909+ t .Fatalf ("Failed to git commit: %v" , err )
2910+ }
2911+
2912+ // Load workspace
2913+ workspace , err := FindWorkspace (tmpDir , Arguments {}, "" , "" )
2914+ if err != nil {
2915+ t .Fatalf ("Failed to load workspace: %v" , err )
2916+ }
2917+
2918+ // Get app package
2919+ pkg , ok := workspace .Packages ["app:lib" ]
2920+ if ! ok {
2921+ t .Fatalf ("Package app:lib not found in workspace. Available packages: %v" , getPackageNames (& workspace ))
2922+ }
2923+
2924+ // Create local cache
2925+ cacheDir := filepath .Join (tmpDir , ".cache" )
2926+ if err := os .MkdirAll (cacheDir , 0755 ); err != nil {
2927+ t .Fatal (err )
2928+ }
2929+
2930+ localCache , err := local .NewFilesystemCache (cacheDir )
2931+ if err != nil {
2932+ t .Fatalf ("Failed to create local cache: %v" , err )
2933+ }
2934+
2935+ // Build the app package (which depends on @test/utils via link:)
2936+ t .Log ("Building app:lib which depends on @test/utils:lib via link: dependency (scoped package)" )
2937+
2938+ err = Build (pkg ,
2939+ WithLocalCache (localCache ),
2940+ WithDontTest (true ),
2941+ )
2942+
2943+ if err != nil {
2944+ t .Fatalf ("Build failed: %v\n \n This likely means scoped package link: dependency handling is broken." , err )
2945+ }
2946+
2947+ t .Log ("✅ Build succeeded - scoped package link: dependency was correctly resolved" )
2948+
2949+ // Verify cache artifact exists
2950+ cachePath , exists := localCache .Location (pkg )
2951+ if ! exists {
2952+ t .Fatal ("Package not found in cache after build" )
2953+ }
2954+
2955+ t .Logf ("Cache artifact created at: %s" , cachePath )
2956+ t .Log ("✅ Yarn scoped package link: dependency integration test passed" )
2957+ }
0 commit comments