-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
/
remove.ts
170 lines (135 loc) Β· 6.22 KB
/
remove.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
import {BaseCommand, WorkspaceRequiredError} from '@yarnpkg/cli';
import {Configuration, Cache, Descriptor, Project, formatUtils} from '@yarnpkg/core';
import {Workspace, InstallMode} from '@yarnpkg/core';
import {structUtils} from '@yarnpkg/core';
import {Command, Option, Usage, UsageError} from 'clipanion';
import micromatch from 'micromatch';
import * as t from 'typanion';
import * as suggestUtils from '../suggestUtils';
import {Hooks} from '..';
// eslint-disable-next-line arca/no-default-export
export default class RemoveCommand extends BaseCommand {
static paths = [
[`remove`],
];
static usage: Usage = Command.Usage({
description: `remove dependencies from the project`,
details: `
This command will remove the packages matching the specified patterns from the current workspace.
If the \`--mode=<mode>\` option is set, Yarn will change which artifacts are generated. The modes currently supported are:
- \`skip-build\` will not run the build scripts at all. Note that this is different from setting \`enableScripts\` to false because the latter will disable build scripts, and thus affect the content of the artifacts generated on disk, whereas the former will just disable the build step - but not the scripts themselves, which just won't run.
- \`update-lockfile\` will skip the link step altogether, and only fetch packages that are missing from the lockfile (or that have no associated checksums). This mode is typically used by tools like Renovate or Dependabot to keep a lockfile up-to-date without incurring the full install cost.
This command accepts glob patterns as arguments (if valid Idents and supported by [micromatch](https://github.com/micromatch/micromatch)). Make sure to escape the patterns, to prevent your own shell from trying to expand them.
`,
examples: [[
`Remove a dependency from the current project`,
`$0 remove lodash`,
], [
`Remove a dependency from all workspaces at once`,
`$0 remove lodash --all`,
], [
`Remove all dependencies starting with \`eslint-\``,
`$0 remove 'eslint-*'`,
], [
`Remove all dependencies with the \`@babel\` scope`,
`$0 remove '@babel/*'`,
], [
`Remove all dependencies matching \`react-dom\` or \`react-helmet\``,
`$0 remove 'react-{dom,helmet}'`,
]],
});
all = Option.Boolean(`-A,--all`, false, {
description: `Apply the operation to all workspaces from the current project`,
});
mode = Option.String(`--mode`, {
description: `Change what artifacts installs generate`,
validator: t.isEnum(InstallMode),
});
patterns = Option.Rest();
async execute() {
const configuration = await Configuration.find(this.context.cwd, this.context.plugins);
const {project, workspace} = await Project.find(configuration, this.context.cwd);
const cache = await Cache.find(configuration);
if (!workspace)
throw new WorkspaceRequiredError(project.cwd, this.context.cwd);
await project.restoreInstallState({
restoreResolutions: false,
});
const affectedWorkspaces = this.all
? project.workspaces
: [workspace];
const targets = [
suggestUtils.Target.REGULAR,
suggestUtils.Target.DEVELOPMENT,
suggestUtils.Target.PEER,
];
const unreferencedPatterns = [];
let hasChanged = false;
const afterWorkspaceDependencyRemovalList: Array<[
Workspace,
suggestUtils.Target,
Descriptor,
]> = [];
for (const pattern of this.patterns) {
let isReferenced = false;
// This isn't really needed - It's just for consistency:
// All patterns are either valid or not for all commands (e.g. remove, up)
const pseudoIdent = structUtils.parseIdent(pattern);
for (const workspace of affectedWorkspaces) {
const peerDependenciesMeta = [...workspace.manifest.peerDependenciesMeta.keys()];
for (const stringifiedIdent of micromatch(peerDependenciesMeta, pattern)) {
workspace.manifest.peerDependenciesMeta.delete(stringifiedIdent);
hasChanged = true;
isReferenced = true;
}
for (const target of targets) {
const descriptors = workspace.manifest.getForScope(target);
const stringifiedIdents = [...descriptors.values()].map(descriptor => {
return structUtils.stringifyIdent(descriptor);
});
for (const stringifiedIdent of micromatch(stringifiedIdents, structUtils.stringifyIdent(pseudoIdent))) {
const {identHash} = structUtils.parseIdent(stringifiedIdent);
const removedDescriptor = descriptors.get(identHash);
if (typeof removedDescriptor === `undefined`)
throw new Error(`Assertion failed: Expected the descriptor to be registered`);
workspace.manifest[target].delete(identHash);
afterWorkspaceDependencyRemovalList.push([
workspace,
target,
removedDescriptor,
]);
hasChanged = true;
isReferenced = true;
}
}
}
if (!isReferenced) {
unreferencedPatterns.push(pattern);
}
}
const patterns = unreferencedPatterns.length > 1
? `Patterns`
: `Pattern`;
const dont = unreferencedPatterns.length > 1
? `don't`
: `doesn't`;
const which = this.all
? `any`
: `this`;
if (unreferencedPatterns.length > 0)
throw new UsageError(`${patterns} ${formatUtils.prettyList(configuration, unreferencedPatterns, formatUtils.Type.CODE)} ${dont} match any packages referenced by ${which} workspace`);
if (hasChanged) {
await configuration.triggerMultipleHooks(
(hooks: Hooks) => hooks.afterWorkspaceDependencyRemoval,
afterWorkspaceDependencyRemovalList,
);
return await project.installWithNewReport({
stdout: this.context.stdout,
}, {
cache,
mode: this.mode,
});
}
return 0;
}
}