Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Support Yarn Berry when creating sources zip #945

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/wxt/e2e/tests/zip.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ describe('Zipping', () => {
"flatten": "1.0.3"
},
"resolutions": {
"[email protected]": "file://./.wxt/local_modules/flatten-1.0.3.tgz"
"[email protected]": "file:./.wxt/local_modules/flatten-1.0.3.tgz"
}
}"
`);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/.yarn/** linguist-vendored
/.yarn/releases/* binary
/.yarn/plugins/**/* binary
/.pnp.* binary linguist-generated
Original file line number Diff line number Diff line change
@@ -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.*
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"name": "yarn-berry-ls",
"packageManager": "[email protected]",
"dependencies": {
"mime-types": "2.1.35"
},
"devDependencies": {
"flatten": "1.0.3"
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "yarn-ls",
"name": "yarn-classic-ls",
"packageManager": "[email protected]",
"dependencies": {
"mime-types": "2.1.35"
Expand Down
25 changes: 23 additions & 2 deletions packages/wxt/src/core/package-managers/__tests__/yarn.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 });
Expand All @@ -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' },
]);
});
});
});
88 changes: 87 additions & 1 deletion packages/wxt/src/core/package-managers/yarn.ts
Original file line number Diff line number Diff line change
@@ -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);
Expand Down Expand Up @@ -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 };
Expand Down
17 changes: 15 additions & 2 deletions packages/wxt/src/core/zip.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,14 +169,27 @@ 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(
id,
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;
}
}

Expand All @@ -198,7 +211,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);
}
3 changes: 2 additions & 1 deletion packages/wxt/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down