From 71c5e6c5513db83b1f0993464e35a804532b2fa8 Mon Sep 17 00:00:00 2001
From: Mathieu Ezannic <2439519+mezannic@users.noreply.github.com>
Date: Thu, 3 Oct 2024 14:27:48 +0200
Subject: [PATCH 1/2] fix: use valid links for local packages in sources zip

---
 packages/wxt/e2e/tests/zip.test.ts | 2 +-
 packages/wxt/src/core/zip.ts       | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/packages/wxt/e2e/tests/zip.test.ts b/packages/wxt/e2e/tests/zip.test.ts
index 713ad68ab..fcacc07dc 100644
--- a/packages/wxt/e2e/tests/zip.test.ts
+++ b/packages/wxt/e2e/tests/zip.test.ts
@@ -66,7 +66,7 @@ describe('Zipping', () => {
           "flatten": "1.0.3"
         },
         "resolutions": {
-          "flatten@1.0.3": "file://./.wxt/local_modules/flatten-1.0.3.tgz"
+          "flatten@1.0.3": "file:./.wxt/local_modules/flatten-1.0.3.tgz"
         }
       }"
     `);
diff --git a/packages/wxt/src/core/zip.ts b/packages/wxt/src/core/zip.ts
index de7b17b6f..6ad8589b9 100644
--- a/packages/wxt/src/core/zip.ts
+++ b/packages/wxt/src/core/zip.ts
@@ -198,7 +198,7 @@ function addOverridesToPackageJson(
   };
   Object.entries(overrides).forEach(([key, absolutePath]) => {
     newPackage[wxt.pm.overridesKey][key] =
-      'file://./' + normalizePath(path.relative(packageJsonDir, absolutePath));
+      'file:./' + normalizePath(path.relative(packageJsonDir, absolutePath));
   });
   return JSON.stringify(newPackage, null, 2);
 }

From e48baea4d1e88b27035992d13c24829a4b16d818 Mon Sep 17 00:00:00 2001
From: Mathieu Ezannic <2439519+mezannic@users.noreply.github.com>
Date: Thu, 3 Oct 2024 14:27:48 +0200
Subject: [PATCH 2/2] fix: make sources zip work with Yarn Berry

---
 .../simple-yarn-berry-project/.gitattributes  |  4 +
 .../simple-yarn-berry-project/.gitignore      | 13 +++
 .../simple-yarn-berry-project/package.json    | 10 +++
 .../simple-yarn-berry-project/yarn.lock       | 38 ++++++++
 .../package.json                              |  2 +-
 .../yarn.lock                                 |  0
 .../package-managers/__tests__/yarn.test.ts   | 25 +++++-
 .../wxt/src/core/package-managers/yarn.ts     | 88 ++++++++++++++++++-
 packages/wxt/src/core/zip.ts                  | 15 +++-
 packages/wxt/src/types.ts                     |  3 +-
 10 files changed, 192 insertions(+), 6 deletions(-)
 create mode 100644 packages/wxt/src/core/package-managers/__tests__/fixtures/simple-yarn-berry-project/.gitattributes
 create mode 100644 packages/wxt/src/core/package-managers/__tests__/fixtures/simple-yarn-berry-project/.gitignore
 create mode 100644 packages/wxt/src/core/package-managers/__tests__/fixtures/simple-yarn-berry-project/package.json
 create mode 100644 packages/wxt/src/core/package-managers/__tests__/fixtures/simple-yarn-berry-project/yarn.lock
 rename packages/wxt/src/core/package-managers/__tests__/fixtures/{simple-yarn-project => simple-yarn-classic-project}/package.json (83%)
 rename packages/wxt/src/core/package-managers/__tests__/fixtures/{simple-yarn-project => simple-yarn-classic-project}/yarn.lock (100%)

diff --git a/packages/wxt/src/core/package-managers/__tests__/fixtures/simple-yarn-berry-project/.gitattributes b/packages/wxt/src/core/package-managers/__tests__/fixtures/simple-yarn-berry-project/.gitattributes
new file mode 100644
index 000000000..af3ad1281
--- /dev/null
+++ b/packages/wxt/src/core/package-managers/__tests__/fixtures/simple-yarn-berry-project/.gitattributes
@@ -0,0 +1,4 @@
+/.yarn/**            linguist-vendored
+/.yarn/releases/*    binary
+/.yarn/plugins/**/*  binary
+/.pnp.*              binary linguist-generated
diff --git a/packages/wxt/src/core/package-managers/__tests__/fixtures/simple-yarn-berry-project/.gitignore b/packages/wxt/src/core/package-managers/__tests__/fixtures/simple-yarn-berry-project/.gitignore
new file mode 100644
index 000000000..870eb6a50
--- /dev/null
+++ b/packages/wxt/src/core/package-managers/__tests__/fixtures/simple-yarn-berry-project/.gitignore
@@ -0,0 +1,13 @@
+.yarn/*
+!.yarn/patches
+!.yarn/plugins
+!.yarn/releases
+!.yarn/sdks
+!.yarn/versions
+
+# Swap the comments on the following lines if you wish to use zero-installs
+# In that case, don't forget to run `yarn config set enableGlobalCache false`!
+# Documentation here: https://yarnpkg.com/features/caching#zero-installs
+
+#!.yarn/cache
+.pnp.*
diff --git a/packages/wxt/src/core/package-managers/__tests__/fixtures/simple-yarn-berry-project/package.json b/packages/wxt/src/core/package-managers/__tests__/fixtures/simple-yarn-berry-project/package.json
new file mode 100644
index 000000000..00fd19335
--- /dev/null
+++ b/packages/wxt/src/core/package-managers/__tests__/fixtures/simple-yarn-berry-project/package.json
@@ -0,0 +1,10 @@
+{
+  "name": "yarn-berry-ls",
+  "packageManager": "yarn@4.4.0",
+  "dependencies": {
+    "mime-types": "2.1.35"
+  },
+  "devDependencies": {
+    "flatten": "1.0.3"
+  }
+}
diff --git a/packages/wxt/src/core/package-managers/__tests__/fixtures/simple-yarn-berry-project/yarn.lock b/packages/wxt/src/core/package-managers/__tests__/fixtures/simple-yarn-berry-project/yarn.lock
new file mode 100644
index 000000000..17825934b
--- /dev/null
+++ b/packages/wxt/src/core/package-managers/__tests__/fixtures/simple-yarn-berry-project/yarn.lock
@@ -0,0 +1,38 @@
+# This file is generated by running "yarn install" inside your project.
+# Manual changes might be lost - proceed with caution!
+
+__metadata:
+  version: 8
+  cacheKey: 10c0
+
+"flatten@npm:1.0.3":
+  version: 1.0.3
+  resolution: "flatten@npm:1.0.3"
+  checksum: 10c0/9f9b1f3dcd05be057bb83ec27f2513da5306e7bfc0cf8bd839ab423eb1b0f99683a25c97b48fafd5959819159659ce9f1397623a46f89a8577ba095fcf5fb753
+  languageName: node
+  linkType: hard
+
+"mime-db@npm:1.52.0":
+  version: 1.52.0
+  resolution: "mime-db@npm:1.52.0"
+  checksum: 10c0/0557a01deebf45ac5f5777fe7740b2a5c309c6d62d40ceab4e23da9f821899ce7a900b7ac8157d4548ddbb7beffe9abc621250e6d182b0397ec7f10c7b91a5aa
+  languageName: node
+  linkType: hard
+
+"mime-types@npm:2.1.35":
+  version: 2.1.35
+  resolution: "mime-types@npm:2.1.35"
+  dependencies:
+    mime-db: "npm:1.52.0"
+  checksum: 10c0/82fb07ec56d8ff1fc999a84f2f217aa46cb6ed1033fefaabd5785b9a974ed225c90dc72fff460259e66b95b73648596dbcc50d51ed69cdf464af2d237d3149b2
+  languageName: node
+  linkType: hard
+
+"yarn-berry-ls@workspace:.":
+  version: 0.0.0-use.local
+  resolution: "yarn-berry-ls@workspace:."
+  dependencies:
+    flatten: "npm:1.0.3"
+    mime-types: "npm:2.1.35"
+  languageName: unknown
+  linkType: soft
diff --git a/packages/wxt/src/core/package-managers/__tests__/fixtures/simple-yarn-project/package.json b/packages/wxt/src/core/package-managers/__tests__/fixtures/simple-yarn-classic-project/package.json
similarity index 83%
rename from packages/wxt/src/core/package-managers/__tests__/fixtures/simple-yarn-project/package.json
rename to packages/wxt/src/core/package-managers/__tests__/fixtures/simple-yarn-classic-project/package.json
index f19e87458..8f6cca076 100644
--- a/packages/wxt/src/core/package-managers/__tests__/fixtures/simple-yarn-project/package.json
+++ b/packages/wxt/src/core/package-managers/__tests__/fixtures/simple-yarn-classic-project/package.json
@@ -1,5 +1,5 @@
 {
-  "name": "yarn-ls",
+  "name": "yarn-classic-ls",
   "packageManager": "yarn@1.22.22",
   "dependencies": {
     "mime-types": "2.1.35"
diff --git a/packages/wxt/src/core/package-managers/__tests__/fixtures/simple-yarn-project/yarn.lock b/packages/wxt/src/core/package-managers/__tests__/fixtures/simple-yarn-classic-project/yarn.lock
similarity index 100%
rename from packages/wxt/src/core/package-managers/__tests__/fixtures/simple-yarn-project/yarn.lock
rename to packages/wxt/src/core/package-managers/__tests__/fixtures/simple-yarn-classic-project/yarn.lock
diff --git a/packages/wxt/src/core/package-managers/__tests__/yarn.test.ts b/packages/wxt/src/core/package-managers/__tests__/yarn.test.ts
index 40415b7df..67aa24af4 100644
--- a/packages/wxt/src/core/package-managers/__tests__/yarn.test.ts
+++ b/packages/wxt/src/core/package-managers/__tests__/yarn.test.ts
@@ -3,8 +3,8 @@ import path from 'node:path';
 import { yarn } from '../yarn';
 
 describe('Yarn Package Management Utils', () => {
-  describe('listDependencies', () => {
-    const cwd = path.resolve(__dirname, 'fixtures/simple-yarn-project');
+  describe('listDependencies (Yarn 1 / Classic)', () => {
+    const cwd = path.resolve(__dirname, 'fixtures/simple-yarn-classic-project');
 
     it('should list direct dependencies', async () => {
       const actual = await yarn.listDependencies({ cwd });
@@ -24,4 +24,25 @@ describe('Yarn Package Management Utils', () => {
       ]);
     });
   });
+
+  describe('listDependencies (Yarn 2+ / Berry)', () => {
+    const cwd = path.resolve(__dirname, 'fixtures/simple-yarn-berry-project');
+
+    it('should list direct dependencies', async () => {
+      const actual = await yarn.listDependencies({ cwd });
+      expect(actual).toEqual([
+        { name: 'mime-types', version: 'npm:2.1.35' },
+        { name: 'flatten', version: 'npm:1.0.3' },
+      ]);
+    });
+
+    it('should list all dependencies', async () => {
+      const actual = await yarn.listDependencies({ cwd, all: true });
+      expect(actual).toEqual([
+        { name: 'mime-types', version: 'npm:2.1.35' },
+        { name: 'mime-db', version: 'npm:1.52.0' },
+        { name: 'flatten', version: 'npm:1.0.3' },
+      ]);
+    });
+  });
 });
diff --git a/packages/wxt/src/core/package-managers/yarn.ts b/packages/wxt/src/core/package-managers/yarn.ts
index c313ca8d6..8e64bcc46 100644
--- a/packages/wxt/src/core/package-managers/yarn.ts
+++ b/packages/wxt/src/core/package-managers/yarn.ts
@@ -1,8 +1,12 @@
+import { dirname, resolve } from 'node:path';
+import { fileURLToPath } from 'node:url';
+import { pathExists } from 'fs-extra';
 import { Dependency } from '../../types';
 import { WxtPackageManagerImpl } from './types';
 import { dedupeDependencies, npm } from './npm';
 
-export const yarn: WxtPackageManagerImpl = {
+// Yarn 1 (Classic) package manager implementation
+export const yarnClassic: WxtPackageManagerImpl = {
   overridesKey: 'resolutions',
   downloadDependency(...args) {
     return npm.downloadDependency(...args);
@@ -39,6 +43,88 @@ export const yarn: WxtPackageManagerImpl = {
   },
 };
 
+// Returns the absolute path of the root directory of a Yarn Berry mono-repository
+const getMonorepoRootDir = async () => {
+  let monorepoRootDir = dirname(fileURLToPath(import.meta.url));
+  while (!(await pathExists(resolve(monorepoRootDir, 'yarn.lock'))) && monorepoRootDir !== '/') {
+    monorepoRootDir = dirname(monorepoRootDir);
+  }
+  return monorepoRootDir;
+}
+
+// yarn 2+ (Berry) package manager implementation
+export const yarnBerry: WxtPackageManagerImpl = {
+  overridesKey: 'resolutions',
+  async downloadDependency(id: string, downloadDir: string) {
+    const { execa } = await import('execa');
+    if (!id.includes('@workspace:')) {
+      return npm.downloadDependency(id.replace('@npm:', '@'), downloadDir);
+    }
+    const monorepoRootDir = await getMonorepoRootDir();
+    const [dependencyName, dependencyDirRelativeToMonorepoRootDir] = id.split('@workspace:');
+    const dependencyDir = resolve(monorepoRootDir, dependencyDirRelativeToMonorepoRootDir);
+    const packedFilename = `${dependencyName.replace('@', '').replace('/', '-')}.tgz`;
+    const archivePath = resolve(downloadDir, packedFilename);
+    await execa('yarn', ['pack', '--out', archivePath], {
+      cwd: dependencyDir
+    });
+    return archivePath;
+  },
+  async listDependencies(options) {
+    const monorepoRootDir = await getMonorepoRootDir();
+    let currentWorkspace = '.';
+    if (monorepoRootDir !== '/' && options?.cwd?.startsWith(monorepoRootDir)) {
+      currentWorkspace = options.cwd.substring(monorepoRootDir.length);
+    }
+
+    const args = ['info', '--name-only', '--json'];
+    if (options?.all) {
+      args.push('--all');
+      args.push('--recursive');
+    }
+    const { execa } = await import('execa');
+    const res = await execa('yarn', args, { cwd: options?.cwd });
+    const lines = res.stdout.split('\n').map((line) => JSON.parse(line));
+
+    const dependencies: Dependency[] = [];
+
+    while (lines.length > 0) {
+      const line = lines.pop();
+      // example output formats
+      // - "foo@npm:0.0.1"
+      // - "@acme/foo@npm:1.2.3"
+      // - "@acme/bar@workspace:packages/bar"
+      // - "typescript@patch:typescript@npm%3A5.6.2#optional!builtin<compat/typescript>::version=5.6.2&hash=8c6c40"
+      const name = line.substring(0, line.substring(1).indexOf('@') + 1);
+      const version = line.substring(name.length + 1);
+      const isCurrentPackage = version === `workspace:${currentWorkspace}`;
+      if (name === '' || version === '' || isCurrentPackage) {
+        continue;
+      }
+      dependencies.push({ name, version });
+    }
+
+    return dedupeDependencies(dependencies);
+  },
+};
+
+// Yarn 1 (Classic) and Yarn 2+ (Berry) have different CLI and output formats
+export const yarn: WxtPackageManagerImpl = {
+  overridesKey: 'resolutions',
+  async downloadDependency(id: string, downloadDir: string) {
+    const { execa } = await import('execa');
+    const execRes = await execa('yarn', ['--version']);
+    const _yarn = execRes.stdout.startsWith('1.') ? yarnClassic : yarnBerry;
+    return _yarn.downloadDependency(id, downloadDir);
+  },
+  async listDependencies(options) {
+    const { execa } = await import('execa');
+    const execRes = await execa('yarn', ['--version'], { cwd: options?.cwd });
+    const _yarn = execRes.stdout.startsWith('1.') ? yarnClassic : yarnBerry;
+    return _yarn.listDependencies(options);
+  }
+};
+
 type JsonLine =
   | { type: unknown; data: unknown }
   | { type: 'tree'; data: JsonLineTree };
diff --git a/packages/wxt/src/core/zip.ts b/packages/wxt/src/core/zip.ts
index 6ad8589b9..70a9b4ad3 100644
--- a/packages/wxt/src/core/zip.ts
+++ b/packages/wxt/src/core/zip.ts
@@ -169,6 +169,16 @@ async function downloadPrivatePackages() {
     );
 
     for (const pkg of downloadPackages) {
+      const protocol = pkg.version.split(':')[0];
+      const isDownloadable = !['file', 'patch'].includes(protocol);
+
+      if (!isDownloadable) {
+        wxt.logger.warn(
+          `Skipping package download: ${pkg.name}@${pkg.version}`,
+        );
+        continue;
+      }
+
       wxt.logger.info(`Downloading package: ${pkg.name}@${pkg.version}`);
       const id = `${pkg.name}@${pkg.version}`;
       const tgzPath = await wxt.pm.downloadDependency(
@@ -176,7 +186,10 @@ async function downloadPrivatePackages() {
         wxt.config.zip.downloadedPackagesDir,
       );
       files.push(tgzPath);
-      overrides[id] = tgzPath;
+
+      // removes version strings that may cause issues with Yarn Berry
+      const overrideKey = id.replace(/@(npm|workspace):.*$/, '');
+      overrides[overrideKey] = tgzPath;
     }
   }
 
diff --git a/packages/wxt/src/types.ts b/packages/wxt/src/types.ts
index 445f04476..46e4df548 100644
--- a/packages/wxt/src/types.ts
+++ b/packages/wxt/src/types.ts
@@ -1452,7 +1452,8 @@ export interface WxtPackageManager extends Nypm.PackageManager {
   /**
    * Run `npm ls`, `pnpm ls`, or `bun pm ls`, or `yarn list` and return the results.
    *
-   * WARNING: Yarn always returns all dependencies
+   * WARNING: Yarn Classic always returns all dependencies
+   * WARNING: Yarn Berry prefixes dependency versions with a protocol name (such as `npm:` or `workspace:`)
    */
   listDependencies: (options?: {
     cwd?: string;