-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
/
patchCommit.ts
135 lines (102 loc) Β· 5.83 KB
/
patchCommit.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
import {BaseCommand, WorkspaceRequiredError} from '@yarnpkg/cli';
import {Configuration, Descriptor, DescriptorHash, Manifest, Project, structUtils, Workspace} from '@yarnpkg/core';
import {npath, xfs, ppath, Filename} from '@yarnpkg/fslib';
import {Command, Option, Usage, UsageError} from 'clipanion';
import * as patchUtils from '../patchUtils';
// eslint-disable-next-line arca/no-default-export
export default class PatchCommitCommand extends BaseCommand {
static paths = [
[`patch-commit`],
];
static usage: Usage = Command.Usage({
description: `generate a patch out of a directory`,
details: `
By default, this will print a patchfile on stdout based on the diff between the folder passed in and the original version of the package. Such file is suitable for consumption with the \`patch:\` protocol.
With the \`-s,--save\` option set, the patchfile won't be printed on stdout anymore and will instead be stored within a local file (by default kept within \`.yarn/patches\`, but configurable via the \`patchFolder\` setting). A \`resolutions\` entry will also be added to your top-level manifest, referencing the patched package via the \`patch:\` protocol.
Note that only folders generated by \`yarn patch\` are accepted as valid input for \`yarn patch-commit\`.
`,
});
save = Option.Boolean(`-s,--save`, false, {
description: `Add the patch to your resolution entries`,
});
patchFolder = Option.String();
async execute() {
const configuration = await Configuration.find(this.context.cwd, this.context.plugins);
const {project, workspace} = await Project.find(configuration, this.context.cwd);
if (!workspace)
throw new WorkspaceRequiredError(project.cwd, this.context.cwd);
await project.restoreInstallState();
const folderPath = ppath.resolve(this.context.cwd, npath.toPortablePath(this.patchFolder));
const sourcePath = ppath.join(folderPath, `../source`);
const metaPath = ppath.join(folderPath, `../.yarn-patch.json`);
if (!xfs.existsSync(sourcePath))
throw new UsageError(`The argument folder didn't get created by 'yarn patch'`);
const diff = await patchUtils.diffFolders(sourcePath, folderPath);
const meta = await xfs.readJsonPromise(metaPath);
const locator = structUtils.parseLocator(meta.locator, true);
if (!project.storedPackages.has(locator.locatorHash))
throw new UsageError(`No package found in the project for the given locator`);
if (!this.save) {
this.context.stdout.write(diff);
return;
}
const patchFolder = configuration.get(`patchFolder`);
const patchPath = ppath.join(patchFolder, `${structUtils.slugifyLocator(locator)}.patch`);
await xfs.mkdirPromise(patchFolder, {recursive: true});
await xfs.writeFilePromise(patchPath, diff);
const workspaceDependents: Array<Workspace> = [];
const transitiveDependencies = new Map<DescriptorHash, Descriptor>();
for (const pkg of project.storedPackages.values()) {
if (structUtils.isVirtualLocator(pkg))
continue;
const descriptor = pkg.dependencies.get(locator.identHash);
if (!descriptor)
continue;
const devirtualizedDescriptor = structUtils.ensureDevirtualizedDescriptor(descriptor);
const unpatchedDescriptor = patchUtils.ensureUnpatchedDescriptor(devirtualizedDescriptor);
const resolution = project.storedResolutions.get(unpatchedDescriptor.descriptorHash);
if (!resolution)
throw new Error(`Assertion failed: Expected the resolution to have been registered`);
const dependency = project.storedPackages.get(resolution);
if (!dependency)
throw new Error(`Assertion failed: Expected the package to have been registered`);
const workspace = project.tryWorkspaceByLocator(pkg);
if (workspace) {
workspaceDependents.push(workspace);
} else {
const originalPkg = project.originalPackages.get(pkg.locatorHash);
if (!originalPkg)
throw new Error(`Assertion failed: Expected the original package to have been registered`);
const originalDependency = originalPkg.dependencies.get(descriptor.identHash);
if (!originalDependency)
throw new Error(`Assertion failed: Expected the original dependency to have been registered`);
transitiveDependencies.set(originalDependency.descriptorHash, originalDependency);
}
}
for (const workspace of workspaceDependents) {
for (const dependencyType of Manifest.hardDependencies) {
const originalDescriptor = workspace.manifest[dependencyType].get(locator.identHash);
if (!originalDescriptor)
continue;
const newDescriptor = patchUtils.makeDescriptor(originalDescriptor, {
parentLocator: null,
sourceDescriptor: structUtils.convertLocatorToDescriptor(locator),
patchPaths: [ppath.join(Filename.home, ppath.relative(project.cwd, patchPath))],
});
workspace.manifest[dependencyType].set(originalDescriptor.identHash, newDescriptor);
}
}
for (const originalDescriptor of transitiveDependencies.values()) {
const newDescriptor = patchUtils.makeDescriptor(originalDescriptor, {
parentLocator: null,
sourceDescriptor: structUtils.convertLocatorToDescriptor(locator),
patchPaths: [ppath.join(Filename.home, ppath.relative(project.cwd, patchPath))],
});
project.topLevelWorkspace.manifest.resolutions.push({
pattern: {descriptor: {fullName: structUtils.stringifyIdent(newDescriptor), description: originalDescriptor.range}},
reference: newDescriptor.range,
});
}
await project.persist();
}
}