yarn-deduplicate
Advanced tools
Comparing version 1.1.1 to 1.2.0
@@ -103,2 +103,16 @@ const childProcess = require('child_process'); | ||
test('prints fixed yarn.lock when excluding lodash', async () => { | ||
const { stdout, stderr } = await execFile(process.execPath, [ | ||
cliFilePath, | ||
'--print', | ||
'--exclude', | ||
'lodash', | ||
yarnLockFilePath, | ||
]); | ||
expect(stdout).toContain('lodash@>=1.0.0:'); | ||
expect(stdout).toContain('lodash@>=2.0.0:'); | ||
expect(stdout).not.toContain('lodash@>=1.0.0, lodash@>=2.0.0:'); | ||
expect(stderr).toBe(''); | ||
}); | ||
test('edits yarn.lock and replaces its content with the fixed version', async () => { | ||
@@ -105,0 +119,0 @@ const oldFileContent = await readFile(yarnLockFilePath, 'utf8'); |
@@ -102,2 +102,41 @@ const { fixDuplicates, listDuplicates } = require('../index.js'); | ||
test('excludes packages to be de-duplicated', () => { | ||
const yarn_lock = outdent` | ||
a-package@^2.0.0: | ||
version "2.0.0" | ||
resolved "http://example.com/a-package/2.1.0" | ||
a-package@^2.0.1: | ||
version "2.0.1" | ||
resolved "http://example.com/a-package/2.2.0" | ||
other-package@^1.0.0: | ||
version "1.0.11" | ||
resolved "http://example.com/other-package/1.0.0" | ||
other-package@^1.0.1: | ||
version "1.0.12" | ||
resolved "http://example.com/other-package/1.0.0" | ||
`; | ||
const deduped = fixDuplicates(yarn_lock, { | ||
excludePackages: ['a-package'], | ||
}); | ||
const json = lockfile.parse(deduped).object; | ||
expect(json['a-package@^2.0.0']['version']).toEqual('2.0.0'); | ||
expect(json['a-package@^2.0.1']['version']).toEqual('2.0.1'); | ||
expect(json['other-package@^1.0.0']['version']).toEqual('1.0.12'); | ||
expect(json['other-package@^1.0.1']['version']).toEqual('1.0.12'); | ||
const list = listDuplicates(yarn_lock, { | ||
excludePackages: ['a-package'], | ||
}); | ||
expect(list).toHaveLength(1); | ||
expect(list).toContain( | ||
'Package "other-package" wants ^1.0.0 and could get 1.0.12, but got 1.0.11' | ||
); | ||
}); | ||
test('should support the integrity field if present', () => { | ||
@@ -104,0 +143,0 @@ const yarn_lock = outdent({ trimTrailingNewline: false })` |
@@ -8,2 +8,11 @@ # Changelog | ||
## [1.2.0] - 2020-02-29 | ||
### Added | ||
- Typescript definitions (thanks to @bluelovers) | ||
- Info about the source of duplication to README | ||
- CLI option to exclude packages (thanks to @JacobBlomgren) | ||
- Updated a bunch of dependencies | ||
## [1.1.1] - 2019-02-03 | ||
@@ -10,0 +19,0 @@ |
@@ -24,2 +24,5 @@ #!/usr/bin/env node | ||
) | ||
.option('--exclude <exclude>', 'a comma separated list of packages not to deduplicate.', val => | ||
val.split(',').map(v => v.trim()) | ||
) | ||
.option('--print', 'instead of saving the deduplicated yarn.lock, print the result in stdout'); | ||
@@ -44,2 +47,3 @@ | ||
includePackages: commander.packages, | ||
excludePackages: commander.exclude, | ||
}); | ||
@@ -54,2 +58,3 @@ duplicates.forEach(logLine => console.log(logLine)); | ||
includePackages: commander.packages, | ||
excludePackages: commander.exclude, | ||
}); | ||
@@ -56,0 +61,0 @@ |
25
index.js
@@ -6,3 +6,3 @@ const lockfile = require('@yarnpkg/lockfile'); | ||
const extractPackages = (json, includePackages = []) => { | ||
const extractPackages = (json, includePackages = [], excludePackages = []) => { | ||
const packages = {}; | ||
@@ -33,2 +33,4 @@ const re = /^(.*)@([^@]*?)$/; | ||
if (excludePackages.length > 0 && excludePackages.includes(packageName)) return; | ||
packages[packageName] = packages[packageName] || []; | ||
@@ -46,3 +48,3 @@ packages[packageName].push({ | ||
const computePackageIntances = (packages, name, useMostCommon) => { | ||
const computePackageInstances = (packages, name, useMostCommon) => { | ||
// Instances of this package in the tree | ||
@@ -67,3 +69,3 @@ const packageInstances = packages[name]; | ||
packageInstance.satisfiedBy.add(packageInstance.installedVersion); | ||
// In some cases th requested version is invalid form a semver point of view (for | ||
// In some cases the requested version is invalid form a semver point of view (for | ||
// example `sinon@next`). Just ignore those cases, they won't get deduped. | ||
@@ -106,7 +108,7 @@ if ( | ||
const getDuplicatedPackages = (json, { includePackages, useMostCommon }) => { | ||
const packages = extractPackages(json, includePackages); | ||
const getDuplicatedPackages = (json, { includePackages, excludePackages, useMostCommon }) => { | ||
const packages = extractPackages(json, includePackages, excludePackages); | ||
return Object.keys(packages) | ||
.reduce( | ||
(acc, name) => acc.concat(computePackageIntances(packages, name, useMostCommon)), | ||
(acc, name) => acc.concat(computePackageInstances(packages, name, useMostCommon)), | ||
[] | ||
@@ -119,3 +121,3 @@ ) | ||
yarnLock, | ||
{ includePackages = [], useMostCommon = false } = {} | ||
{ includePackages = [], excludePackages = [], useMostCommon = false } = {} | ||
) => { | ||
@@ -125,3 +127,3 @@ const json = parseYarnLock(yarnLock); | ||
getDuplicatedPackages(json, { includePackages, useMostCommon }).forEach( | ||
getDuplicatedPackages(json, { includePackages, excludePackages, useMostCommon }).forEach( | ||
({ bestVersion, name, installedVersion, requestedVersion }) => { | ||
@@ -137,6 +139,9 @@ result.push( | ||
module.exports.fixDuplicates = (yarnLock, { includePackages = [], useMostCommon = false } = {}) => { | ||
module.exports.fixDuplicates = ( | ||
yarnLock, | ||
{ includePackages = [], excludePackages = [], useMostCommon = false } = {} | ||
) => { | ||
const json = parseYarnLock(yarnLock); | ||
getDuplicatedPackages(json, { includePackages, useMostCommon }).forEach( | ||
getDuplicatedPackages(json, { includePackages, excludePackages, useMostCommon }).forEach( | ||
({ bestVersion, name, versions, requestedVersion }) => { | ||
@@ -143,0 +148,0 @@ json[`${name}@${requestedVersion}`] = versions[bestVersion].pkg; |
{ | ||
"name": "yarn-deduplicate", | ||
"version": "1.1.1", | ||
"version": "1.2.0", | ||
"bin": "./cli.js", | ||
@@ -34,12 +34,12 @@ "description": "Deduplication tool for yarn.lock files", | ||
"@yarnpkg/lockfile": "^1.1.0", | ||
"commander": "^2.10.0", | ||
"semver": "^5.3.0" | ||
"commander": "^4.1.1", | ||
"semver": "7.1.3" | ||
}, | ||
"devDependencies": { | ||
"eslint": "^5.10.0", | ||
"eslint-config-prettier": "^3.3.0", | ||
"eslint-plugin-jest": "^22.1.2", | ||
"eslint": "^6.8.0", | ||
"eslint-config-prettier": "^6.10.0", | ||
"eslint-plugin-jest": "^23.8.0", | ||
"eslint-plugin-prettier": "^3.0.0", | ||
"jest": "^21.2.1", | ||
"outdent": "^0.5.0", | ||
"jest": "^25.1.0", | ||
"outdent": "0.7.0", | ||
"prettier": "^1.15.3" | ||
@@ -46,0 +46,0 @@ }, |
@@ -53,5 +53,5 @@ Builds: [![CircleCI](https://circleci.com/gh/atlassian/yarn-deduplicate.svg?style=svg)](https://circleci.com/gh/atlassian/yarn-deduplicate) | ||
`yarn.lock` contains a list of all the dependencies required by your project (including transitive | ||
dependencies), and the actual package version installed to satistfy those dependencies. | ||
dependencies), and the actual package version installed to satisfy those dependencies. | ||
For the context of this project, a "duplicated package" is a package that appers on multiple nodes | ||
For the context of this project, a "duplicated package" is a package that appears on multiple nodes | ||
of the dependency tree with overlapping version ranges but resolved to different versions. | ||
@@ -74,2 +74,25 @@ | ||
### Why is this necessary? | ||
Yarn documentation seems to suggest this package shouldn't be necessary. For example, in | ||
https://classic.yarnpkg.com/en/docs/cli/dedupe/, it says | ||
> The dedupe command isn’t necessary. `yarn install` will already dedupe. | ||
This is, however, not exactly true. There are cases where yarn will *not* deduplicate existing | ||
packages. For example, this scenario: | ||
- Install `libA`. It depends on `libB ^1.1.0`. At this point, the latest version of `libB` is | ||
`1.1.2`, so it gets installed as a transitive dependency in your repo | ||
- After a few days, install `libC`. It also depends on `libB ^1.1.0`. But this time, the latest | ||
`libB` version is `1.1.3`. | ||
In the above scenario, you'll end up with `libB@1.1.2` and `libB@1.1.3` in your repo. | ||
Find more examples in: | ||
- [yarn-deduplicate — The Hero We Need](https://medium.com/@bnaya/yarn-deduplicate-the-hero-we-need-f4497a362128) | ||
- [De-duplicating yarn.lock](https://medium.com/@scinos/de-duplicating-yarn-lock-ae30be4aa41a) | ||
- https://github.com/yarnpkg/yarn/issues/3778 | ||
### Deduplication strategies | ||
@@ -127,2 +150,3 @@ | ||
--- | ||
@@ -129,0 +153,0 @@ ## Migration guide |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Deprecated
MaintenanceThe maintainer of the package marked it as deprecated. This could indicate that a single version should not be used, or that the package is no longer maintained and any new vulnerabilities will not be fixed.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
673718
144
14612
230
1
23
3
+ Addedcommander@4.1.1(transitive)
+ Addedsemver@7.1.3(transitive)
- Removedcommander@2.20.3(transitive)
- Removedsemver@5.7.2(transitive)
Updatedcommander@^4.1.1
Updatedsemver@7.1.3