Skip to content

Commit

Permalink
fix: make sources zip work with Yarn Berry
Browse files Browse the repository at this point in the history
  • Loading branch information
rmn-mathieu-ezannic committed Sep 30, 2024
1 parent 9289f19 commit 1ff0b2f
Show file tree
Hide file tree
Showing 10 changed files with 192 additions and 6 deletions.
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
15 changes: 14 additions & 1 deletion packages/wxt/src/core/zip.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,14 +170,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 Down
3 changes: 2 additions & 1 deletion packages/wxt/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1438,7 +1438,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

0 comments on commit 1ff0b2f

Please sign in to comment.