diff --git a/.gitignore b/.gitignore index 02effa0..ab3508f 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ # Generated files *.log node_modules/ +.yarn/ diff --git a/package.json b/package.json index ce3dbee..bacd35d 100644 --- a/package.json +++ b/package.json @@ -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" @@ -40,6 +40,7 @@ "@yarnpkg/lockfile": "^1.1.0", "chalk": "^3.0.0", "glob": "^7.1.6", + "js-yaml": "^4.1.0", "yargs": "^15.1.0" }, "devDependencies": { diff --git a/src/commands/yarn-workspace.js b/src/commands/yarn-workspace.js index 31e8d18..8579791 100644 --- a/src/commands/yarn-workspace.js +++ b/src/commands/yarn-workspace.js @@ -1,4 +1,3 @@ -const fs = require('fs'); const path = require('path'); const glob = require('glob'); const sharedArguments = require('../sharedArguments'); diff --git a/src/errors.js b/src/errors.js index b5b9aff..815202a 100644 --- a/src/errors.js +++ b/src/errors.js @@ -1,5 +1,3 @@ -const path = require('path'); - class DiglettError extends Error { constructor(message, errorType) { super(message); diff --git a/src/fs.js b/src/fs.js index 67b0ef4..2ad0bcd 100644 --- a/src/fs.js +++ b/src/fs.js @@ -1,6 +1,7 @@ const fs = require('fs'); const path = require('path'); const lockfile = require('@yarnpkg/lockfile'); +const yml = require('js-yaml'); const { FileNotFoundError, ParseError } = require('./errors'); function readFile(fileName, projectPath) { @@ -22,11 +23,21 @@ function readJSON(fileName, projectPath) { function readYarnLockfile(projectPath) { const file = readFile('yarn.lock', projectPath); - const parsed = lockfile.parse(file); - if (parsed.type !== 'success') { - throw new ParseError('Failed to parse yarn.lock'); + + try { + const parsed = lockfile.parse(file); + if (parsed.type !== 'success') { + throw new ParseError('Failed to parse yarn.lock'); + } + return parsed.object; + } catch { + try { + const parsed = yml.load(file); + return parsed; + } catch (error) { + throw new ParseError('Failed to parse yarn.lock'); + } } - return parsed.object; } function readPackageJSON(projectPath) { diff --git a/src/getDuplicateDependencies.js b/src/getDuplicateDependencies.js index 309ca3d..fb323bf 100644 --- a/src/getDuplicateDependencies.js +++ b/src/getDuplicateDependencies.js @@ -1,5 +1,3 @@ -const groupYarnDependencies = require('./groupYarnDependencies'); - function getDuplicateDependencies(groupedVersions, packageNamePattern) { const duplicates = new Map(); groupedVersions.forEach((versions, packageName) => { diff --git a/src/groupYarnDependencies.js b/src/groupYarnDependencies.js index a6b08fb..452f11b 100644 --- a/src/groupYarnDependencies.js +++ b/src/groupYarnDependencies.js @@ -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, @@ -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, diff --git a/src/parseYarnDescriptor.js b/src/parseYarnDescriptor.js new file mode 100644 index 0000000..113ea23 --- /dev/null +++ b/src/parseYarnDescriptor.js @@ -0,0 +1,41 @@ +/** + * A regular expression for parsing a yarn berry descriptor. + */ +const PARSE_REGEX = /(^@?[^/]+?\/?[^@/]+?)@(?:.*:)*(.+)/; + +/** + * @typedef {Object} ParsedDescriptor + * @property {string} packageName + * @property {string} version + */ + +/** + * Parses a yarn berry descriptor into an object. + * + * For example `@material/ripple@npm:1.0.0` will be parsed to: + * ``` + * { + * package: '@material/ripple', + * version: '1.0.0', + * } + * ``` + * + * Supported formats: + * - `package@version` + * - `@scope/package@version` + * - `package@protocol:version` + * - `@scope/package@protocol:version` + * + * @param {string} descriptor - The yarn berry 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; diff --git a/test/fixtures/yarn-berry/.yarnrc.yml b/test/fixtures/yarn-berry/.yarnrc.yml new file mode 100644 index 0000000..3186f3f --- /dev/null +++ b/test/fixtures/yarn-berry/.yarnrc.yml @@ -0,0 +1 @@ +nodeLinker: node-modules diff --git a/test/fixtures/yarn-berry/package-lock.json b/test/fixtures/yarn-berry/package-lock.json new file mode 100644 index 0000000..8ae64ac --- /dev/null +++ b/test/fixtures/yarn-berry/package-lock.json @@ -0,0 +1,353 @@ +{ + "name": "diglett-yarn-berry", + "requires": true, + "lockfileVersion": 1, + "dependencies": { + "@material/animation": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@material/animation/-/animation-3.1.0.tgz", + "integrity": "sha512-ZfP95awrPBLhpaCTPNx+xKYPp2D88fzf5p5YNVp6diUAGRpq3g12Aq7qJfIHDXAFo5CtrYhgOKRqIKxUVcFisQ==", + "requires": { + "tslib": "^1.9.3" + } + }, + "@material/base": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@material/base/-/base-3.1.0.tgz", + "integrity": "sha512-pWEBHyPrMV3rdnjqWWH96h5t3MxQI6V1J9jOor+UBG7bXQtr6InTabTqhz5CLY7r+qZU8YvNh2OKIy8heP0cyQ==", + "requires": { + "tslib": "^1.9.3" + } + }, + "@material/button": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@material/button/-/button-3.2.0.tgz", + "integrity": "sha512-VEASy3Dtc7BCo8/cuUIp6w0+/l4U1myGZffK5GeFVInP/erStSQOmYXT7jGXkZpUglRzWOpVvEpc6nsvhMqGbw==", + "requires": { + "@material/elevation": "^3.1.0", + "@material/feature-targeting": "^3.1.0", + "@material/ripple": "^3.2.0", + "@material/rtl": "^3.2.0", + "@material/shape": "^3.1.0", + "@material/theme": "^3.1.0", + "@material/typography": "^3.1.0" + } + }, + "@material/density": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@material/density/-/density-4.0.0.tgz", + "integrity": "sha512-PuOCPCXlWjimTq+OuCS8biAb1JE9aXCZwT1dRG9REAIAK7bN8KeeTzkeJp6jTj+ggZjWphwKF0lKeX6Gv+e/lw==" + }, + "@material/dom": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@material/dom/-/dom-3.1.0.tgz", + "integrity": "sha512-RtBLSkrBjMfHwknaGBifAIC8cBWF9pXjz2IYqfI2braB6SfQI4vhdJviwyiA5BmA/psn3cKpBUZbHI0ym0O0SQ==", + "requires": { + "tslib": "^1.9.3" + } + }, + "@material/elevation": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@material/elevation/-/elevation-3.1.0.tgz", + "integrity": "sha512-e45LqiG6LfbR1M52YkSLA7pQPeYJOuOVzLp27xy2072TnLuJexMxhEaG4O1novEIjsTtMjqfrfJ/koODU5vEew==", + "requires": { + "@material/animation": "^3.1.0", + "@material/feature-targeting": "^3.1.0", + "@material/theme": "^3.1.0" + } + }, + "@material/feature-targeting": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@material/feature-targeting/-/feature-targeting-3.1.0.tgz", + "integrity": "sha512-aXAa1Pv6w32URacE9LfMsl9zI6hFwx1K0Lp3Xpyf4rAkmaAB6z0gOkhicOrVFc0f64YheJgHjE7hJFieVenQdw==" + }, + "@material/floating-label": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@material/floating-label/-/floating-label-4.0.0.tgz", + "integrity": "sha512-ovuZKhH7U+YmZk8kXftYCdEaU9InJdkBsPe4TP+dg4HiO1lWmd7ZxVsMo6iTl4yaFofkBPj3VDkbE1fLnHxKPA==", + "requires": { + "@material/animation": "^4.0.0", + "@material/base": "^4.0.0", + "@material/rtl": "^4.0.0", + "@material/theme": "^4.0.0", + "@material/typography": "^4.0.0", + "tslib": "^1.9.3" + }, + "dependencies": { + "@material/animation": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@material/animation/-/animation-4.0.0.tgz", + "integrity": "sha512-IfzXzstWdtKQcsNWu+s2Hpz5dBwkTHtgtzoesr+FC7TqENH+SJdsF1ntnZI1XVi2C9ZlBf7f4BSmXpWHD0MIlw==", + "requires": { + "tslib": "^1.9.3" + } + }, + "@material/base": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@material/base/-/base-4.0.0.tgz", + "integrity": "sha512-vHm7fkqXzjdfxifXvlmaZColoIfKuWmO+1rvdzDORTWP+A8Dq70cgKd2I1SBqxzDGjOasMzHbQI6f9MISQf2vQ==", + "requires": { + "tslib": "^1.9.3" + } + }, + "@material/feature-targeting": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@material/feature-targeting/-/feature-targeting-4.0.0.tgz", + "integrity": "sha512-0gk+f151vqmEdWkrQ9ocPlQRU9aUtSGsVBhletqIbsthLUsZIz9qk25FHjV1wHd/bGHknd9NH+T8ENprv3KLFg==" + }, + "@material/rtl": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@material/rtl/-/rtl-4.0.0.tgz", + "integrity": "sha512-AP8zByVDEWAJVJoxByVccUbH+BX24IeG7ol+L6Qd8JjzPpz1fzPVJ4BeDNaF0a6sXtHsRmj2zN5dsx/BGC3IHg==" + }, + "@material/theme": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@material/theme/-/theme-4.0.0.tgz", + "integrity": "sha512-vS4G4rusJTatTH50kSYO1U3UGN8EY9kGRvPaFsEFKikJBOqcR6KWK9H9/wCLqqd6nDNifEj9H2sdWw1AV4NA6Q==", + "requires": { + "@material/feature-targeting": "^4.0.0" + } + }, + "@material/typography": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@material/typography/-/typography-4.0.0.tgz", + "integrity": "sha512-lUG4yjG9fl1ryNX4OVnOmi+EjhiV4WsWcYt4yzffHrFg1RfKuCAV59j7TtmlMfZIkNDwqK5jvk3oOpTRDFpL8Q==", + "requires": { + "@material/feature-targeting": "^4.0.0" + } + } + } + }, + "@material/line-ripple": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@material/line-ripple/-/line-ripple-4.0.0.tgz", + "integrity": "sha512-+vrgKlb2gUBIKxlzVKLn/tX76qJ0Z19RkH2i7mh4IdH8KxeEai8NQQYWqeOOKtyp8JbH0ObGyqJCwPo2VbHDmw==", + "requires": { + "@material/animation": "^4.0.0", + "@material/base": "^4.0.0", + "@material/theme": "^4.0.0", + "tslib": "^1.9.3" + }, + "dependencies": { + "@material/animation": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@material/animation/-/animation-4.0.0.tgz", + "integrity": "sha512-IfzXzstWdtKQcsNWu+s2Hpz5dBwkTHtgtzoesr+FC7TqENH+SJdsF1ntnZI1XVi2C9ZlBf7f4BSmXpWHD0MIlw==", + "requires": { + "tslib": "^1.9.3" + } + }, + "@material/base": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@material/base/-/base-4.0.0.tgz", + "integrity": "sha512-vHm7fkqXzjdfxifXvlmaZColoIfKuWmO+1rvdzDORTWP+A8Dq70cgKd2I1SBqxzDGjOasMzHbQI6f9MISQf2vQ==", + "requires": { + "tslib": "^1.9.3" + } + }, + "@material/feature-targeting": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@material/feature-targeting/-/feature-targeting-4.0.0.tgz", + "integrity": "sha512-0gk+f151vqmEdWkrQ9ocPlQRU9aUtSGsVBhletqIbsthLUsZIz9qk25FHjV1wHd/bGHknd9NH+T8ENprv3KLFg==" + }, + "@material/theme": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@material/theme/-/theme-4.0.0.tgz", + "integrity": "sha512-vS4G4rusJTatTH50kSYO1U3UGN8EY9kGRvPaFsEFKikJBOqcR6KWK9H9/wCLqqd6nDNifEj9H2sdWw1AV4NA6Q==", + "requires": { + "@material/feature-targeting": "^4.0.0" + } + } + } + }, + "@material/notched-outline": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@material/notched-outline/-/notched-outline-4.0.0.tgz", + "integrity": "sha512-9WNG7dZS83nu72kmoknA3XyvZQIxx8QBjEB2Q2KGyRp4Q8zrzWxfcvTYgiOsYgvz013JnTqaqe4g0wjl6v789g==", + "requires": { + "@material/base": "^4.0.0", + "@material/floating-label": "^4.0.0", + "@material/rtl": "^4.0.0", + "@material/shape": "^4.0.0", + "@material/theme": "^4.0.0", + "tslib": "^1.9.3" + }, + "dependencies": { + "@material/base": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@material/base/-/base-4.0.0.tgz", + "integrity": "sha512-vHm7fkqXzjdfxifXvlmaZColoIfKuWmO+1rvdzDORTWP+A8Dq70cgKd2I1SBqxzDGjOasMzHbQI6f9MISQf2vQ==", + "requires": { + "tslib": "^1.9.3" + } + }, + "@material/feature-targeting": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@material/feature-targeting/-/feature-targeting-4.0.0.tgz", + "integrity": "sha512-0gk+f151vqmEdWkrQ9ocPlQRU9aUtSGsVBhletqIbsthLUsZIz9qk25FHjV1wHd/bGHknd9NH+T8ENprv3KLFg==" + }, + "@material/rtl": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@material/rtl/-/rtl-4.0.0.tgz", + "integrity": "sha512-AP8zByVDEWAJVJoxByVccUbH+BX24IeG7ol+L6Qd8JjzPpz1fzPVJ4BeDNaF0a6sXtHsRmj2zN5dsx/BGC3IHg==" + }, + "@material/shape": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@material/shape/-/shape-4.0.0.tgz", + "integrity": "sha512-wmr05YBrEL462QPiJ+t9xh5RqxzylXYo/8DVZnb/1WA9GZ6m38UK/8Awtip1cZAN34pzD/9p5AydyywlQVoI+g==", + "requires": { + "@material/feature-targeting": "^4.0.0" + } + }, + "@material/theme": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@material/theme/-/theme-4.0.0.tgz", + "integrity": "sha512-vS4G4rusJTatTH50kSYO1U3UGN8EY9kGRvPaFsEFKikJBOqcR6KWK9H9/wCLqqd6nDNifEj9H2sdWw1AV4NA6Q==", + "requires": { + "@material/feature-targeting": "^4.0.0" + } + } + } + }, + "@material/ripple": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@material/ripple/-/ripple-3.2.0.tgz", + "integrity": "sha512-GtwkfNakALmfGLs6TpdFIVeAWjRqbyT7WfEw9aU7elUokABfHES+O0KoSKQSMQiSQ8Vjl90MONzNsN1Evi/1YQ==", + "requires": { + "@material/animation": "^3.1.0", + "@material/base": "^3.1.0", + "@material/dom": "^3.1.0", + "@material/feature-targeting": "^3.1.0", + "@material/theme": "^3.1.0", + "tslib": "^1.9.3" + } + }, + "@material/rtl": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@material/rtl/-/rtl-3.2.0.tgz", + "integrity": "sha512-L/w9m9Yx1ceOw/VjEfeJoqD4rW9QP3IBb9MamXAg3qUi/zsztoXD/FUw179pxkLn4huFFNlVYZ4Y1y6BpM0PMA==" + }, + "@material/shape": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@material/shape/-/shape-3.1.0.tgz", + "integrity": "sha512-Oyvs7YjHfByA0e9IVVp7ojAlPwgSu3Bl0cioiE0OdkidkAaNu0izM2ryRzMBDH5o8+lRD0kpZoT+9CVVCdaYIg==", + "requires": { + "@material/feature-targeting": "^3.1.0" + } + }, + "@material/textfield": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@material/textfield/-/textfield-4.0.0.tgz", + "integrity": "sha512-1Xg+nriTqFB8+5k8sGkR8LBeD3dRgNuhvwyU8XrUeAs5l/HMfn4CWgbEyZTaJ2EEtFPxnKGedAYqCEwXngwGWg==", + "requires": { + "@material/animation": "^4.0.0", + "@material/base": "^4.0.0", + "@material/density": "^4.0.0", + "@material/dom": "^4.0.0", + "@material/floating-label": "^4.0.0", + "@material/line-ripple": "^4.0.0", + "@material/notched-outline": "^4.0.0", + "@material/ripple": "^4.0.0", + "@material/rtl": "^4.0.0", + "@material/shape": "^4.0.0", + "@material/theme": "^4.0.0", + "@material/typography": "^4.0.0", + "tslib": "^1.9.3" + }, + "dependencies": { + "@material/animation": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@material/animation/-/animation-4.0.0.tgz", + "integrity": "sha512-IfzXzstWdtKQcsNWu+s2Hpz5dBwkTHtgtzoesr+FC7TqENH+SJdsF1ntnZI1XVi2C9ZlBf7f4BSmXpWHD0MIlw==", + "requires": { + "tslib": "^1.9.3" + } + }, + "@material/base": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@material/base/-/base-4.0.0.tgz", + "integrity": "sha512-vHm7fkqXzjdfxifXvlmaZColoIfKuWmO+1rvdzDORTWP+A8Dq70cgKd2I1SBqxzDGjOasMzHbQI6f9MISQf2vQ==", + "requires": { + "tslib": "^1.9.3" + } + }, + "@material/dom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@material/dom/-/dom-4.0.0.tgz", + "integrity": "sha512-GRCJT9+PGWqygZwGf1XLTrbmzP35YWG7+T0hpfhoIJO8VDiMTeyfvhJXFuA2wh9pD0noEjte0lmbdBlykrbWZw==", + "requires": { + "tslib": "^1.9.3" + } + }, + "@material/feature-targeting": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@material/feature-targeting/-/feature-targeting-4.0.0.tgz", + "integrity": "sha512-0gk+f151vqmEdWkrQ9ocPlQRU9aUtSGsVBhletqIbsthLUsZIz9qk25FHjV1wHd/bGHknd9NH+T8ENprv3KLFg==" + }, + "@material/ripple": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@material/ripple/-/ripple-4.0.0.tgz", + "integrity": "sha512-9BLIOvyCP5sM+fQpLlcJZWyrHguusJq8E5A1pxg0wQwputOyaPBM7recHhYkJmVjzRpTcPgf1PkvkpN6DKGcNg==", + "requires": { + "@material/animation": "^4.0.0", + "@material/base": "^4.0.0", + "@material/dom": "^4.0.0", + "@material/feature-targeting": "^4.0.0", + "@material/theme": "^4.0.0", + "tslib": "^1.9.3" + } + }, + "@material/rtl": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@material/rtl/-/rtl-4.0.0.tgz", + "integrity": "sha512-AP8zByVDEWAJVJoxByVccUbH+BX24IeG7ol+L6Qd8JjzPpz1fzPVJ4BeDNaF0a6sXtHsRmj2zN5dsx/BGC3IHg==" + }, + "@material/shape": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@material/shape/-/shape-4.0.0.tgz", + "integrity": "sha512-wmr05YBrEL462QPiJ+t9xh5RqxzylXYo/8DVZnb/1WA9GZ6m38UK/8Awtip1cZAN34pzD/9p5AydyywlQVoI+g==", + "requires": { + "@material/feature-targeting": "^4.0.0" + } + }, + "@material/theme": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@material/theme/-/theme-4.0.0.tgz", + "integrity": "sha512-vS4G4rusJTatTH50kSYO1U3UGN8EY9kGRvPaFsEFKikJBOqcR6KWK9H9/wCLqqd6nDNifEj9H2sdWw1AV4NA6Q==", + "requires": { + "@material/feature-targeting": "^4.0.0" + } + }, + "@material/typography": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@material/typography/-/typography-4.0.0.tgz", + "integrity": "sha512-lUG4yjG9fl1ryNX4OVnOmi+EjhiV4WsWcYt4yzffHrFg1RfKuCAV59j7TtmlMfZIkNDwqK5jvk3oOpTRDFpL8Q==", + "requires": { + "@material/feature-targeting": "^4.0.0" + } + } + } + }, + "@material/theme": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@material/theme/-/theme-3.1.0.tgz", + "integrity": "sha512-N4JX+akOwg1faAvFvIEhDcwW4cZfUpwEn8lct6Vs3WczjLF6/KdIoLVaYh+eVl1bzfsoIuWvx56j0B1PjXZw9g==", + "requires": { + "@material/feature-targeting": "^3.1.0" + } + }, + "@material/typography": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@material/typography/-/typography-3.1.0.tgz", + "integrity": "sha512-aSNBQvVxIH1kORSYdLGuSTivx6oJ1MSOSTUAsUwhXPQLQlvbdFeZaqUp7xgn+EvRsHGRFhWk5YGuiBds9+7zQg==", + "requires": { + "@material/feature-targeting": "^3.1.0" + } + }, + "tslib": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", + "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==" + } + } +} diff --git a/test/fixtures/yarn-berry/package.json b/test/fixtures/yarn-berry/package.json new file mode 100644 index 0000000..b731c67 --- /dev/null +++ b/test/fixtures/yarn-berry/package.json @@ -0,0 +1,9 @@ +{ + "name": "diglett-yarn-berry", + "private": true, + "dependencies": { + "@material/button": "^3.2.0", + "@material/textfield": "^4.0.0" + }, + "packageManager": "yarn@3.6.0" +} diff --git a/test/fixtures/yarn-berry/yarn.lock b/test/fixtures/yarn-berry/yarn.lock new file mode 100644 index 0000000..ad17839 --- /dev/null +++ b/test/fixtures/yarn-berry/yarn.lock @@ -0,0 +1,280 @@ +# This file is generated by running "yarn install" inside your project. +# Manual changes might be lost - proceed with caution! + +__metadata: + version: 6 + cacheKey: 8 + +"@material/animation@npm:^3.1.0": + version: 3.1.0 + resolution: "@material/animation@npm:3.1.0" + dependencies: + tslib: ^1.9.3 + checksum: e381cbf7b820f08cc8b1d4266eb175f300c7ac9b51dd77187db09ad4a230ba4b92526781811f667161ae7320a38661a1f1e6b4658bdfa3b241a05b1b4499bc2a + languageName: node + linkType: hard + +"@material/animation@npm:^4.0.0": + version: 4.0.0 + resolution: "@material/animation@npm:4.0.0" + dependencies: + tslib: ^1.9.3 + checksum: 93c91bfdd1256cf3e4be95d437d6952bc1e966e1efa9887c4b24553ba00eb33e13c3a2c097df002e306698c9d9da0679bf2f690f6de28646cfff29aa716dd0b5 + languageName: node + linkType: hard + +"@material/base@npm:^3.1.0": + version: 3.1.0 + resolution: "@material/base@npm:3.1.0" + dependencies: + tslib: ^1.9.3 + checksum: b9552ec944b81826fc85f78412b13dbe8a6ab499d805b2203ff054c7fab995e88062b8ccdb856efcffb6e45d269b0268f75da858235c0d7e4eba05dee081e862 + languageName: node + linkType: hard + +"@material/base@npm:^4.0.0": + version: 4.0.0 + resolution: "@material/base@npm:4.0.0" + dependencies: + tslib: ^1.9.3 + checksum: 7bf5a37f28684c3f9b0a3027ec14c24f1fdc90cea90d136ba37c09369927da18fbbe228522ac105d9d445d1c2ad697130de7cb300ca0da6ef42a8c86aa6039e9 + languageName: node + linkType: hard + +"@material/button@npm:^3.2.0": + version: 3.2.0 + resolution: "@material/button@npm:3.2.0" + dependencies: + "@material/elevation": ^3.1.0 + "@material/feature-targeting": ^3.1.0 + "@material/ripple": ^3.2.0 + "@material/rtl": ^3.2.0 + "@material/shape": ^3.1.0 + "@material/theme": ^3.1.0 + "@material/typography": ^3.1.0 + checksum: 118a58b60a642487b616b0913298d8fa748bb298875109d2988c68c7731c94ef88ad3793104db94c6138cb00e3fea32beed608b01bf22057da7b27c8e6d41285 + languageName: node + linkType: hard + +"@material/density@npm:^4.0.0": + version: 4.0.0 + resolution: "@material/density@npm:4.0.0" + checksum: 0f808ded52dd15bc5a862c515325e179fb09146d2ebf4443bbd6e9de3343d9c43c29a46b55966cc185bdc85799628e1e280d1d3160fb0a8d8569ee93cfc94fb8 + languageName: node + linkType: hard + +"@material/dom@npm:^3.1.0": + version: 3.1.0 + resolution: "@material/dom@npm:3.1.0" + dependencies: + tslib: ^1.9.3 + checksum: 4d8058158420c4402deec106e7bb065ef38985d556ec747f19ce7537e8f53340d3640d6b9d1bfaa95c724f7d7657325962fc89bc368187a7d1eb0b41145b3787 + languageName: node + linkType: hard + +"@material/dom@npm:^4.0.0": + version: 4.0.0 + resolution: "@material/dom@npm:4.0.0" + dependencies: + tslib: ^1.9.3 + checksum: eea98f633f7db14d5769633fb3dcf54ab76ea0da03a06eac116d5f45cd68e03433872bb34af158de38f674916cf90d27c3491755694518b54e81d19367e8f3dc + languageName: node + linkType: hard + +"@material/elevation@npm:^3.1.0": + version: 3.1.0 + resolution: "@material/elevation@npm:3.1.0" + dependencies: + "@material/animation": ^3.1.0 + "@material/feature-targeting": ^3.1.0 + "@material/theme": ^3.1.0 + checksum: b91763c7f1e2344bc49ae839afe7b5cd7e204b0095b33beb531578ae82b5109c5c671db4c2ff4035f5dbcdcf945b007ee37c40ba762993c973f5f096d154d3b2 + languageName: node + linkType: hard + +"@material/feature-targeting@npm:^3.1.0": + version: 3.1.0 + resolution: "@material/feature-targeting@npm:3.1.0" + checksum: 2a56b468b889a7152014124197821d901d156a2bc21c4b6bed86fc8a3b4a918188c8e9f842ddf7915e270b768e7eaade77efd09211fc005c89276e4c8a777357 + languageName: node + linkType: hard + +"@material/feature-targeting@npm:^4.0.0": + version: 4.0.0 + resolution: "@material/feature-targeting@npm:4.0.0" + checksum: 17da66877d009321dc9f8a3fab42cb8c1e90c6929a320bb249c1bdc78bb94853c6123ff5a874f325efcbbd40fb9eb747e95df924cb9948072c1c8f244171b291 + languageName: node + linkType: hard + +"@material/floating-label@npm:^4.0.0": + version: 4.0.0 + resolution: "@material/floating-label@npm:4.0.0" + dependencies: + "@material/animation": ^4.0.0 + "@material/base": ^4.0.0 + "@material/rtl": ^4.0.0 + "@material/theme": ^4.0.0 + "@material/typography": ^4.0.0 + tslib: ^1.9.3 + checksum: e5d0dbe36c16fe9513cf6c76cf60c45ff368dc7fcb783387dc74f2605342cbc0f9941e69bd9acbfda268a9ea0659af9610ef31c805c49106e6e487777069b015 + languageName: node + linkType: hard + +"@material/line-ripple@npm:^4.0.0": + version: 4.0.0 + resolution: "@material/line-ripple@npm:4.0.0" + dependencies: + "@material/animation": ^4.0.0 + "@material/base": ^4.0.0 + "@material/theme": ^4.0.0 + tslib: ^1.9.3 + checksum: 6c1079df72ce80f879ed66e11e43c322dd451a891fd9745b3c021f532d9c8d1552ae56a3b52a527f78b43e7bed3b0011e7335c3652b85302d2be5d564b170856 + languageName: node + linkType: hard + +"@material/notched-outline@npm:^4.0.0": + version: 4.0.0 + resolution: "@material/notched-outline@npm:4.0.0" + dependencies: + "@material/base": ^4.0.0 + "@material/floating-label": ^4.0.0 + "@material/rtl": ^4.0.0 + "@material/shape": ^4.0.0 + "@material/theme": ^4.0.0 + tslib: ^1.9.3 + checksum: 4e14ec5e6afc18fcb613e132c08e1a39d66a85b305a1198266c7132e4dc055fb68a5f03d3c6d00252159fb21096d0b58667462155662c41001fa09689d8e4092 + languageName: node + linkType: hard + +"@material/ripple@npm:^3.2.0": + version: 3.2.0 + resolution: "@material/ripple@npm:3.2.0" + dependencies: + "@material/animation": ^3.1.0 + "@material/base": ^3.1.0 + "@material/dom": ^3.1.0 + "@material/feature-targeting": ^3.1.0 + "@material/theme": ^3.1.0 + tslib: ^1.9.3 + checksum: 194ca51c4e74f29802c1e85b177f2bdf6f399aafb3cf7a863eed57b13c53c6e821f4948f9d89cb7b61146992c9206c23ff63972416a4fd50bedd6748d17bc1de + languageName: node + linkType: hard + +"@material/ripple@npm:^4.0.0": + version: 4.0.0 + resolution: "@material/ripple@npm:4.0.0" + dependencies: + "@material/animation": ^4.0.0 + "@material/base": ^4.0.0 + "@material/dom": ^4.0.0 + "@material/feature-targeting": ^4.0.0 + "@material/theme": ^4.0.0 + tslib: ^1.9.3 + checksum: 9fe64349561cd7e88425bf853667f796032d0e03a3177b90cda17524d23d59eb25fca54f58461331fdeeb59204b9ee88d5e141917230ff30502b3b184ff6bd8a + languageName: node + linkType: hard + +"@material/rtl@npm:^3.2.0": + version: 3.2.0 + resolution: "@material/rtl@npm:3.2.0" + checksum: 9db6c26454ed323089699fbf150f179ddbb3564dae52292fcd6a12c56bc206b2ebf7a4a19b0e9bb06e0a63574d2c1ba4e597b0937b786c3fd5fd08c34029b845 + languageName: node + linkType: hard + +"@material/rtl@npm:^4.0.0": + version: 4.0.0 + resolution: "@material/rtl@npm:4.0.0" + checksum: 10b9b00fe74d87f5066269635662fd8af8e2621aebbb4df759ac3bbae469a8b1ba752114436198e40cdf5ed586c1ad19b283d963ca1d0367b3c54aad5cc9b16b + languageName: node + linkType: hard + +"@material/shape@npm:^3.1.0": + version: 3.1.0 + resolution: "@material/shape@npm:3.1.0" + dependencies: + "@material/feature-targeting": ^3.1.0 + checksum: c6d7cdc89cef2d0f1abb2896202b86467ff0cc37c1d9f5b0cbcfad74557c6cd2409fcc46b9359e352271dc0e796c4fbfef3e228c74e3a82ce60e8fd2e225a455 + languageName: node + linkType: hard + +"@material/shape@npm:^4.0.0": + version: 4.0.0 + resolution: "@material/shape@npm:4.0.0" + dependencies: + "@material/feature-targeting": ^4.0.0 + checksum: 31d37eee93e98df3ddb50fbd4fc0c7defc179f3c289ae576e073c1eced7f4934344d52768f691d07f3130c810ee396160caceab021f2520dd4e4a16e8fbeca33 + languageName: node + linkType: hard + +"@material/textfield@npm:^4.0.0": + version: 4.0.0 + resolution: "@material/textfield@npm:4.0.0" + dependencies: + "@material/animation": ^4.0.0 + "@material/base": ^4.0.0 + "@material/density": ^4.0.0 + "@material/dom": ^4.0.0 + "@material/floating-label": ^4.0.0 + "@material/line-ripple": ^4.0.0 + "@material/notched-outline": ^4.0.0 + "@material/ripple": ^4.0.0 + "@material/rtl": ^4.0.0 + "@material/shape": ^4.0.0 + "@material/theme": ^4.0.0 + "@material/typography": ^4.0.0 + tslib: ^1.9.3 + checksum: d64c47e92a73bc374ee8e72d993b7c5482f28b37e053c3705ce2fe1425b9dfdce3aa7e252ae5e162781e0cc9ee3cb02108267e430417787f87ecc187b9e792f3 + languageName: node + linkType: hard + +"@material/theme@npm:^3.1.0": + version: 3.1.0 + resolution: "@material/theme@npm:3.1.0" + dependencies: + "@material/feature-targeting": ^3.1.0 + checksum: d667ea629c2f22dc0c91a8d6c3cb3fb5417743c9b24c2329b5404e85df43917768edd069acbf6b93a5f2ba84fd0351d37ad2f254df457c221f3bd713536f9505 + languageName: node + linkType: hard + +"@material/theme@npm:^4.0.0": + version: 4.0.0 + resolution: "@material/theme@npm:4.0.0" + dependencies: + "@material/feature-targeting": ^4.0.0 + checksum: d55caee243ca0e4033054d3673232245f956ef1c58b760841bcc22876f68ac212f404121542cfc88d103b1a895ede1283315ffcc7da96bdc088071c480bdf7e6 + languageName: node + linkType: hard + +"@material/typography@npm:^3.1.0": + version: 3.1.0 + resolution: "@material/typography@npm:3.1.0" + dependencies: + "@material/feature-targeting": ^3.1.0 + checksum: 9e42085753f12ffe516d00b3f01abb8699be4c7b0a68c9317efc421cd3c4a5871bd2e0095bc4e02c5d73b1cc281e35d1a269ed29c637eaa4a1b11f9e0f48690e + languageName: node + linkType: hard + +"@material/typography@npm:^4.0.0": + version: 4.0.0 + resolution: "@material/typography@npm:4.0.0" + dependencies: + "@material/feature-targeting": ^4.0.0 + checksum: 489140aa0d233384b952e3d6c6ed2517b96a927ed3e8346b5ba6babe1743c9d6c5469b3218061b932ebc407dc0b796ba238682f1f716d981d8ef966e02835789 + languageName: node + linkType: hard + +"diglett-yarn-berry@workspace:.": + version: 0.0.0-use.local + resolution: "diglett-yarn-berry@workspace:." + dependencies: + "@material/button": ^3.2.0 + "@material/textfield": ^4.0.0 + languageName: unknown + linkType: soft + +"tslib@npm:^1.9.3": + version: 1.10.0 + resolution: "tslib@npm:1.10.0" + checksum: 1d0450dc6f64b918b14acaf3b956ebe1c72d7401c632adce932a60e3cd8d2a70f6040ceef6a7c3561146c3f29bcf584c41c2e09a5d20a27d6c3057f0d5f2a836 + languageName: node + linkType: hard diff --git a/test/parse-yarn-descriptor.spec.js b/test/parse-yarn-descriptor.spec.js new file mode 100644 index 0000000..470b9b0 --- /dev/null +++ b/test/parse-yarn-descriptor.spec.js @@ -0,0 +1,39 @@ +const parseYarnDescriptor = require('../src/parseYarnDescriptor'); + +describe('parse-yarn-descriptor', () => { + it('should parse a yarn v1 descriptor', () => { + expect(parseYarnDescriptor('hest@1.0.0')).toEqual({ + packageName: 'hest', + version: '1.0.0', + }); + }); + + it('should parse a yarn berry descriptor with protocol', () => { + expect(parseYarnDescriptor('hest@npm:1.0.0')).toEqual({ + packageName: 'hest', + version: '1.0.0', + }); + }); + + it('should parse a yarn berry descriptor with scope', () => { + expect(parseYarnDescriptor('@snel/hest@1.0.0')).toEqual({ + packageName: '@snel/hest', + version: '1.0.0', + }); + }); + + it('should parse a yarn berry descriptor with scope and protocol', () => { + expect(parseYarnDescriptor('@snel/hest@npm:1.0.0')).toEqual({ + packageName: '@snel/hest', + version: '1.0.0', + }); + }); + + it('should throw an error if the descriptor is invalid', () => { + expect(() => parseYarnDescriptor('invalid')).toThrow(); + }); + + it('should throw an error if the descriptor is undefined', () => { + expect(() => parseYarnDescriptor()).toThrow(); + }); +}); diff --git a/test/single-project.spec.js b/test/single-project.spec.js index 5341a09..e003834 100644 --- a/test/single-project.spec.js +++ b/test/single-project.spec.js @@ -1,4 +1,3 @@ -const path = require('path'); const { exec, getFixturePath } = require('./helpers'); describe.each([ @@ -29,28 +28,31 @@ describe.each([ }); }); - describe('Package with duplicate dependencies', () => { - const fixture = getFixturePath('regular'); - it('fails with 9 duplicate dependencies', async () => { - const { stderr } = await exec([command, fixture]); - expect(stderr).toContain('Found 9 duplicate dependencies'); - }); + describe.each(['regular', 'yarn-berry'])( + '%s package with duplicate dependencies', + fixtureName => { + const fixture = getFixturePath(fixtureName); + it('fails with 9 duplicate dependencies', async () => { + const { stderr } = await exec([command, fixture]); + expect(stderr).toContain('Found 9 duplicate dependencies'); + }); - it('passes with non-matching filter', async () => { - const { stderr } = await exec([command, fixture, '--filter', 'hest']); - expect(stderr).toBeFalsy(); - }); + it('passes with non-matching filter', async () => { + const { stderr } = await exec([command, fixture, '--filter', 'hest']); + expect(stderr).toBeFalsy(); + }); - it('fails with matching filter', async () => { - const { stderr } = await exec([ - command, - fixture, - '--filter', - '^@material/ripple$', - ]); - expect(stderr).toContain('Found 1 duplicate dependency'); - }); - }); + it('fails with matching filter', async () => { + const { stderr } = await exec([ + command, + fixture, + '--filter', + '^@material/ripple$', + ]); + expect(stderr).toContain('Found 1 duplicate dependency'); + }); + } + ); describe('Package with duplicate devDependencies', () => { const fixture = getFixturePath('dev-dependencies'); diff --git a/yarn.lock b/yarn.lock index 0d81f25..b464b82 100644 --- a/yarn.lock +++ b/yarn.lock @@ -643,6 +643,11 @@ argparse@^1.0.7: dependencies: sprintf-js "~1.0.2" +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + arr-diff@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" @@ -2169,6 +2174,13 @@ js-yaml@^3.13.1: argparse "^1.0.7" esprima "^4.0.0" +js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + jsbn@~0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513"