Skip to content

Commit

Permalink
Add support for yarn-berry lockfiles (#28)
Browse files Browse the repository at this point in the history
* Add support for yarn-berry lockfiles

* use @yarnpkg/parsers to parse lockfile

* Update docs

* Update package.json

Co-authored-by: Joel Arvidsson <[email protected]>

* update lockfile

---------

Co-authored-by: Mike Duminy <[email protected]>
Co-authored-by: Joel Arvidsson <[email protected]>
  • Loading branch information
3 people authored Jul 11, 2023
1 parent e18680c commit 5908131
Show file tree
Hide file tree
Showing 16 changed files with 812 additions and 41 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@
# Generated files
*.log
node_modules/
.yarn/
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ npx diglett npm <optional path to project>
npx diglett yarn <optional path to project>
```

Supports both yarn v1 and yarn berry.

### Yarn workspaces

```
Expand All @@ -31,6 +33,8 @@ npx diglett yarn-workspace <optional path to project> [--package <package name>]

By default all workspace packages are analyzed. If you just want to analyze one, pass the `--package` option with the package name – not folder name. It's possible to pass the `--package` option multiple times to analyze multiple packages.

Supports both yarn v1 and yarn berry.

## General options

| **Name** | **Description** | **Default** |
Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
"main": "src/cli.js",
"scripts": {
"test": "jest",
"check-format": "prettier --check {src,test}/**/*.js README.md",
"format": "prettier --write {src,test}/**/*.js README.md"
"check-format": "prettier --check \"{src,test}/**/*.js\" README.md",
"format": "prettier --write \"{src,test}/**/*.js\" README.md"
},
"bin": {
"diglett": "./bin/diglett"
Expand Down Expand Up @@ -37,7 +37,7 @@
},
"homepage": "https://github.com/oblador/diglett#readme",
"dependencies": {
"@yarnpkg/lockfile": "^1.1.0",
"@yarnpkg/parsers": "^2.5.1",
"chalk": "^3.0.0",
"glob": "^7.1.6",
"yargs": "^15.1.0"
Expand Down
1 change: 0 additions & 1 deletion src/commands/yarn-workspace.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
const fs = require('fs');
const path = require('path');
const glob = require('glob');
const sharedArguments = require('../sharedArguments');
Expand Down
2 changes: 0 additions & 2 deletions src/errors.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
const path = require('path');

class DiglettError extends Error {
constructor(message, errorType) {
super(message);
Expand Down
9 changes: 5 additions & 4 deletions src/fs.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const fs = require('fs');
const path = require('path');
const lockfile = require('@yarnpkg/lockfile');
const { parseSyml } = require('@yarnpkg/parsers');
const { FileNotFoundError, ParseError } = require('./errors');

function readFile(fileName, projectPath) {
Expand All @@ -22,11 +22,12 @@ function readJSON(fileName, projectPath) {

function readYarnLockfile(projectPath) {
const file = readFile('yarn.lock', projectPath);
const parsed = lockfile.parse(file);
if (parsed.type !== 'success') {

try {
return parseSyml(file);
} catch {
throw new ParseError('Failed to parse yarn.lock');
}
return parsed.object;
}

function readPackageJSON(projectPath) {
Expand Down
2 changes: 0 additions & 2 deletions src/getDuplicateDependencies.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
const groupYarnDependencies = require('./groupYarnDependencies');

function getDuplicateDependencies(groupedVersions, packageNamePattern) {
const duplicates = new Map();
groupedVersions.forEach((versions, packageName) => {
Expand Down
32 changes: 28 additions & 4 deletions src/groupYarnDependencies.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,24 @@
const { StaleLockfileError } = require('./errors');
const parseYarnDescriptor = require('./parseYarnDescriptor');

function resolveDependency(packageName, requestedVersion, dependencies) {
// yarn v1 uses a simple format for the key: `${packageName}@${version}`
const simpleKey = `${packageName}@${requestedVersion}`;
if (simpleKey in dependencies) return dependencies[simpleKey];

for (const [key, value] of Object.entries(dependencies)) {
// ignore metadata keys
if (key === '__metadata') continue;

// yarn v2 uses a more complex format for the key: `${packageName}@${protocol}${version}`
const { packageName: name, version } = parseYarnDescriptor(key);
if (name === packageName && version === requestedVersion) {
return value;
}
}

return null;
}

function groupYarnDependencies(
packageDependencies,
Expand All @@ -16,18 +36,22 @@ function groupYarnDependencies(
}

const versions = installedVersions.get(packageName);
const dependencyKey = `${packageName}@${requestedVersion}`;
const resolvedDependency = dependencies[dependencyKey];
const resolvedDependency = resolveDependency(
packageName,
requestedVersion,
dependencies
);

if (!resolvedDependency) {
throw new StaleLockfileError(
`Unable to find resolution for "${dependencyKey}", ensure yarn.lock is up to date`
`Unable to find resolution for package "${packageName}" and version "${requestedVersion}", ensure yarn.lock is up to date`
);
}
const installedVersion = resolvedDependency.version;

if (!versions.has(installedVersion)) {
versions.add(installedVersion);
const subDependencies = dependencies[dependencyKey].dependencies;
const subDependencies = resolvedDependency.dependencies;
for (const subDependency in subDependencies) {
populateVersions(
subDependency,
Expand Down
46 changes: 46 additions & 0 deletions src/parseYarnDescriptor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/**
* A regular expression for parsing a yarn berry descriptor.
*/
const PARSE_REGEX = /(^@?[^/]+?\/?[^@/]+?)@(?:.*:)*(.+)/;

/**
* @typedef {Object} ParsedDescriptor
* @property {string} packageName
* @property {string} version
*/

/**
* Parses a yarn descriptor into an object.
* Handles both yarn v1 and yarn berry descriptors.
*
* Supported formats:
* - "package@version"
* - "@scope/package@version"
* - "package@protocol:version"
* - "@scope/package@protocol:version"
*
* Examples:
* - yarn v1: "@material/[email protected]"
* - yarn berry: "@material/ripple@npm:1.0.0"
*
* Both will be parsed to:
* ```
* {
* package: '@material/ripple',
* version: '1.0.0',
* }
* ```
*
* @param {string} descriptor - The yarn descriptor to parse.
* @returns {ParsedDescriptor} The parsed descriptor.
*/
function parseYarnDescriptor(descriptor) {
const result = PARSE_REGEX.exec(descriptor);
if (!result) {
throw new Error(`Unable to parse descriptor: ${descriptor}`);
}
const [, packageName, version] = result;
return { packageName, version };
}

module.exports = parseYarnDescriptor;
1 change: 1 addition & 0 deletions test/fixtures/yarn-berry/.yarnrc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
nodeLinker: node-modules
Loading

0 comments on commit 5908131

Please sign in to comment.