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

feat(plugin-essentials): yarn dedupe #1558

Merged
merged 27 commits into from
Aug 23, 2020
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
79f42d3
feat(plugin-essentials): yarn deduplicate
paul-soporan Jul 8, 2020
f1ce7d2
refactor: improve implementation
paul-soporan Jul 8, 2020
05781f1
Merge branch 'master' into paul/feat/deduplicate
paul-soporan Aug 17, 2020
4303e0d
refactor: shorten command name to `yarn dedupe`
paul-soporan Aug 17, 2020
408acbf
refactor: rewrite algorithm
paul-soporan Aug 17, 2020
7063670
refactor: more refactoring
paul-soporan Aug 17, 2020
d5a2291
refactor: even more refactoring
paul-soporan Aug 17, 2020
925112a
refactor: more refactoring + improved docs
paul-soporan Aug 19, 2020
ebfd0c1
docs: better dedupe docs
paul-soporan Aug 19, 2020
e8df62b
test: add tests
paul-soporan Aug 19, 2020
658055c
test: add tests for selective dedupe
paul-soporan Aug 19, 2020
ea4cc55
Merge remote-tracking branch 'yarnpkg/master' into paul/feat/deduplicate
paul-soporan Aug 19, 2020
bb60e25
refactor: refactor everything yet again
paul-soporan Aug 20, 2020
cc91662
refactor: fix cyclic dependency
paul-soporan Aug 20, 2020
51b342e
Update packages/plugin-essentials/sources/commands/dedupe.ts
paul-soporan Aug 22, 2020
4379dbe
Update packages/plugin-essentials/sources/commands/dedupe.ts
paul-soporan Aug 22, 2020
d637d8e
refactor: dedupeSkip -> null
paul-soporan Aug 22, 2020
1a705f5
refactor: skip -> assertion
paul-soporan Aug 22, 2020
cab447e
refactor: add note about virtual packages
paul-soporan Aug 22, 2020
d6b9a58
refactor: skip ->assertion failed
paul-soporan Aug 22, 2020
58ac353
Merge branch 'master' into paul/feat/deduplicate
paul-soporan Aug 22, 2020
9eb67d8
refactor: disable getSatisfying for some resolvers
paul-soporan Aug 23, 2020
aaf702f
refactor: remove resolutionDependencies
paul-soporan Aug 23, 2020
2465986
chore: lint
paul-soporan Aug 23, 2020
1fc26fa
refactor: skip -> assertion failed
paul-soporan Aug 23, 2020
d05b284
Apply suggestions from code review
paul-soporan Aug 23, 2020
50f0c51
refactor: use semver.SemVer instances
paul-soporan Aug 23, 2020
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
3 changes: 3 additions & 0 deletions .pnp.js

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

30 changes: 30 additions & 0 deletions .yarn/versions/e9b298c7.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
releases:
"@yarnpkg/cli": prerelease
"@yarnpkg/core": minor
"@yarnpkg/plugin-essentials": minor
"@yarnpkg/plugin-exec": patch
"@yarnpkg/plugin-file": patch
"@yarnpkg/plugin-git": patch
"@yarnpkg/plugin-http": patch
"@yarnpkg/plugin-link": patch
"@yarnpkg/plugin-npm": patch
"@yarnpkg/plugin-patch": patch

declined:
- "@yarnpkg/plugin-compat"
- "@yarnpkg/plugin-constraints"
- "@yarnpkg/plugin-dlx"
- "@yarnpkg/plugin-github"
- "@yarnpkg/plugin-init"
- "@yarnpkg/plugin-interactive-tools"
- "@yarnpkg/plugin-node-modules"
- "@yarnpkg/plugin-npm-cli"
- "@yarnpkg/plugin-pack"
- "@yarnpkg/plugin-pnp"
- "@yarnpkg/plugin-stage"
- "@yarnpkg/plugin-typescript"
- "@yarnpkg/plugin-version"
- "@yarnpkg/plugin-workspace-tools"
- "@yarnpkg/builder"
- "@yarnpkg/doctor"
- "@yarnpkg/pnpify"
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export type ExecResult = {
stdout: string;
stderr: string;
code: number;
} | Error & {
} | cp.ExecException & {
stdout: string;
stderr: string;
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/* @flow */

module.exports = require(`./package.json`);

for (const key of [`dependencies`, `devDependencies`, `peerDependencies`]) {
for (const dep of Object.keys(module.exports[key] || {})) {
// $FlowFixMe The whole point of this file is to be dynamic
module.exports[key][dep] = require(dep);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"name": "one-range-dep-too",
"version": "1.0.0",
"dependencies": {
"no-deps": "^1.0.0"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/* @flow */

module.exports = require(`./package.json`);

for (const key of [`dependencies`, `devDependencies`, `peerDependencies`]) {
for (const dep of Object.keys(module.exports[key] || {})) {
// $FlowFixMe The whole point of this file is to be dynamic
module.exports[key][dep] = require(dep);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"name": "two-range-deps",
"version": "1.0.0",
"dependencies": {
"no-deps": "^1.0.0",
"@types/is-number": ">=1.0.0"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,269 @@
import {tests} from 'pkg-tests-core';

const {setPackageWhitelist} = tests;

describe(`Commands`, () => {
describe(`dedupe`, () => {
it(
`should include a footer`,
makeTemporaryEnv({}, async ({path, run, source}) => {
await setPackageWhitelist(new Map([
[`no-deps`, new Set([`1.0.0`])],
[`@types/is-number`, new Set([`1.0.0`])],
]), async () => {
await run(`add`, `two-range-deps`);
});

await run(`add`, `no-deps@1.1.0`, `@types/is-number@2.0.0`);

await expect(run(`dedupe`, `--check`)).rejects.toMatchObject({
stdout: expect.stringContaining(`2 packages can be deduped using the highest strategy`),
});
})
);

describe(`strategies`, () => {
describe(`highest`, () => {
it(
`should dedupe dependencies`,
makeTemporaryEnv({}, async ({path, run, source}) => {
await setPackageWhitelist(new Map([[`no-deps`, new Set([`1.0.0`])]]), async () => {
await run(`add`, `one-range-dep`);
});

await run(`add`, `no-deps@1.1.0`);

await run(`dedupe`);

await expect(run(`dedupe`, `--check`)).resolves.toMatchObject({
code: 0,
});

await expect(source(`require('no-deps')`)).resolves.toMatchObject({
version: `1.1.0`,
});
await expect(source(`require('one-range-dep')`)).resolves.toMatchObject({
dependencies: {
[`no-deps`]: {
version: `1.1.0`,
},
},
});
})
);

it(
`should dedupe dependencies to the highest possible version`,
makeTemporaryEnv({}, async ({path, run, source}) => {
await setPackageWhitelist(new Map([[`no-deps`, new Set([`1.0.0`])]]), async () => {
await run(`add`, `one-range-dep`, `one-range-dep-too`);
});

await run(`add`, `no-deps@1.1.0`);

await run(`dedupe`);

await expect(source(`require('no-deps')`)).resolves.toMatchObject({
version: `1.1.0`,
});
await expect(source(`require('one-range-dep')`)).resolves.toMatchObject({
dependencies: {
[`no-deps`]: {
version: `1.1.0`,
},
},
});
await expect(source(`require('one-range-dep-too')`)).resolves.toMatchObject({
dependencies: {
[`no-deps`]: {
version: `1.1.0`,
},
},
});
})
);
});
});

describe(`patterns`, () => {
it(
`should support selective dedupe (ident)`,
makeTemporaryEnv({}, async ({path, run, source}) => {
await setPackageWhitelist(new Map([
[`no-deps`, new Set([`1.0.0`])],
[`@types/is-number`, new Set([`1.0.0`])],
]), async () => {
await run(`add`, `two-range-deps`);
});

await run(`add`, `no-deps@1.1.0`, `@types/is-number@2.0.0`);

await run(`dedupe`, `no-deps`);

await expect(source(`require('two-range-deps')`)).resolves.toMatchObject({
dependencies: {
[`no-deps`]: {
version: `1.1.0`,
},
[`@types/is-number`]: {
version: `1.0.0`,
},
},
});
})
);

it(
`should support selective dedupe (scoped ident)`,
makeTemporaryEnv({}, async ({path, run, source}) => {
await setPackageWhitelist(new Map([
[`no-deps`, new Set([`1.0.0`])],
[`@types/is-number`, new Set([`1.0.0`])],
]), async () => {
await run(`add`, `two-range-deps`);
});

await run(`add`, `no-deps@1.1.0`, `@types/is-number@2.0.0`);

await run(`dedupe`, `@types/is-number`);

await expect(source(`require('two-range-deps')`)).resolves.toMatchObject({
dependencies: {
[`no-deps`]: {
version: `1.0.0`,
},
[`@types/is-number`]: {
version: `2.0.0`,
},
},
});
})
);

it(
`should support selective dedupe (ident glob)`,
makeTemporaryEnv({}, async ({path, run, source}) => {
await setPackageWhitelist(new Map([
[`no-deps`, new Set([`1.0.0`])],
[`@types/is-number`, new Set([`1.0.0`])],
]), async () => {
await run(`add`, `two-range-deps`);
});

await run(`add`, `no-deps@1.1.0`, `@types/is-number@2.0.0`);

await run(`dedupe`, `no-*`);

await expect(source(`require('two-range-deps')`)).resolves.toMatchObject({
dependencies: {
[`no-deps`]: {
version: `1.1.0`,
},
[`@types/is-number`]: {
version: `1.0.0`,
},
},
});
})
);

it(
`should support selective dedupe (scoped ident glob)`,
makeTemporaryEnv({}, async ({path, run, source}) => {
await setPackageWhitelist(new Map([
[`no-deps`, new Set([`1.0.0`])],
[`@types/is-number`, new Set([`1.0.0`])],
]), async () => {
await run(`add`, `two-range-deps`);
});

await run(`add`, `no-deps@1.1.0`, `@types/is-number@2.0.0`);

await run(`dedupe`, `@types/*`);

await expect(source(`require('two-range-deps')`)).resolves.toMatchObject({
dependencies: {
[`no-deps`]: {
version: `1.0.0`,
},
[`@types/is-number`]: {
version: `2.0.0`,
},
},
});
})
);
});

describe(`flags`, () => {
describe(`-c,--check`, () => {
it(
`should reject with error code 1 when there are duplicates`,
makeTemporaryEnv({}, async ({path, run, source}) => {
await setPackageWhitelist(new Map([[`no-deps`, new Set([`1.0.0`])]]), async () => {
await run(`add`, `one-range-dep`);
});

await run(`add`, `no-deps@1.1.0`);

await expect(run(`dedupe`, `--check`)).rejects.toMatchObject({
code: 1,
});
})
);

it(
`should resolve with error code 0 when there are no duplicates`,
makeTemporaryEnv({}, async ({path, run, source}) => {
await setPackageWhitelist(new Map([[`no-deps`, new Set([`1.0.0`])]]), async () => {
await run(`add`, `one-range-dep`);
});

await run(`add`, `no-deps@2.0.0`);

await expect(run(`dedupe`, `--check`)).resolves.toMatchObject({
code: 0,
});
})
);
});

test(
`--json`,
makeTemporaryEnv({}, async ({path, run, source}) => {
await setPackageWhitelist(new Map([[`no-deps`, new Set([`1.0.0`])]]), async () => {
await run(`add`, `one-range-dep`);
});

await run(`add`, `no-deps@1.1.0`);

// We also use the check flag so that the stdout doesn't include the install report
await run(`dedupe`, `--json`, `--check`).catch(({stdout}) => {
expect(JSON.parse(stdout.trim())).toMatchObject({
descriptor: `no-deps@npm:^1.0.0`,
currentResolution: `no-deps@npm:1.0.0`,
updatedResolution: `no-deps@npm:1.1.0`,
});
});

expect.assertions(1);
})
);

test(
`-s,--strategy`,
makeTemporaryEnv({}, async ({path, run, source}) => {
await setPackageWhitelist(new Map([[`no-deps`, new Set([`1.0.0`])]]), async () => {
await run(`add`, `one-range-dep`);
});

await run(`add`, `no-deps@1.1.0`);

await expect(run(`dedupe`, `--check`, `--strategy`, `highest`)).rejects.toMatchObject({
code: 1,
});
})
);
});
});
});
1 change: 1 addition & 0 deletions packages/plugin-essentials/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"@types/micromatch": "^4.0.1",
"@types/semver": "^7.1.0",
"@types/treeify": "^1.0.0",
"@types/yup": "0.26.12",
"@yarnpkg/cli": "workspace:^2.1.1",
"@yarnpkg/core": "workspace:^2.1.1"
},
Expand Down
Loading