-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
/
patchUtils.ts
151 lines (120 loc) Β· 5.68 KB
/
patchUtils.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
import {Cache, structUtils, Locator, Descriptor, Ident, Project, ThrowReport, miscUtils, FetchOptions} from '@yarnpkg/core';
import {npath, PortablePath, xfs, ppath, Filename, NodeFS} from '@yarnpkg/fslib';
import {Hooks as PatchHooks} from './index';
export {applyPatchFile} from './tools/apply';
export {parsePatchFile} from './tools/parse';
const BUILTIN_REGEXP = /^builtin<([^>]+)>$/;
function parseSpec<T>(spec: string, sourceParser: (source: string) => T) {
const {source, selector, params} = structUtils.parseRange(spec);
if (source === null)
throw new Error(`Patch locators must explicitly define their source`);
const patchPaths = selector
? selector.split(/&/).map(path => npath.toPortablePath(path))
: [];
const parentLocator = params && typeof params.locator === `string`
? structUtils.parseLocator(params.locator)
: null;
const sourceItem = sourceParser(source);
return {parentLocator, sourceItem, patchPaths};
}
export function parseDescriptor(descriptor: Descriptor) {
const {sourceItem, ...rest} = parseSpec(descriptor.range, structUtils.parseDescriptor);
return {...rest, sourceDescriptor: sourceItem};
}
export function parseLocator(locator: Locator) {
const {sourceItem, ...rest} = parseSpec(locator.reference, structUtils.parseLocator);
return {...rest, sourceLocator: sourceItem};
}
function makeSpec<T>({parentLocator, sourceItem, patchPaths, patchHash}: {parentLocator: Locator | null, sourceItem: T, patchPaths: Array<PortablePath>, patchHash?: string}, sourceStringifier: (source: T) => string) {
const parentLocatorSpread = parentLocator !== null
? {parentLocator: structUtils.stringifyLocator(parentLocator)}
: {} as {};
const patchHashSpread = typeof patchHash !== `undefined`
? {hash: patchHash}
: {} as {};
return structUtils.makeRange({
protocol: `patch:`,
source: sourceStringifier(sourceItem),
selector: patchPaths.join(`&`),
params: {
...parentLocatorSpread,
...patchHashSpread,
},
});
}
export function makeDescriptor(ident: Ident, {parentLocator, sourceDescriptor, patchPaths}: ReturnType<typeof parseDescriptor>) {
return structUtils.makeLocator(ident, makeSpec({parentLocator, sourceItem: sourceDescriptor, patchPaths}, structUtils.stringifyDescriptor));
}
export function makeLocator(ident: Ident, {parentLocator, sourceLocator, patchPaths, patchHash}: ReturnType<typeof parseLocator> & {patchHash: string}) {
return structUtils.makeLocator(ident, makeSpec({parentLocator, sourceItem: sourceLocator, patchPaths, patchHash}, structUtils.stringifyLocator));
}
type VisitPatchPathOptions<T> = {
onAbsolute: (p: PortablePath) => T,
onRelative: (p: PortablePath) => T,
onBuiltin: (name: string) => T,
};
function visitPatchPath<T>({onAbsolute, onRelative, onBuiltin}: VisitPatchPathOptions<T>, patchPath: PortablePath) {
const builtinMatch = patchPath.match(BUILTIN_REGEXP);
if (builtinMatch !== null)
return onBuiltin(builtinMatch[1]);
if (ppath.isAbsolute(patchPath)) {
return onAbsolute(patchPath);
} else {
return onRelative(patchPath);
}
}
export function isParentRequired(patchPath: PortablePath) {
return visitPatchPath({
onAbsolute: () => false,
onRelative: () => true,
onBuiltin: () => false,
}, patchPath);
}
export async function loadPatchFiles(parentLocator: Locator | null, patchPaths: Array<PortablePath>, opts: FetchOptions) {
// When the patch files use absolute paths we can directly access them via
// their location on the disk. Otherwise we must go through the package fs.
const parentFetch = parentLocator !== null
? await opts.fetcher.fetch(parentLocator, opts)
: null;
// If the package fs publicized its "original location" (for example like
// in the case of "file:" packages), we use it to derive the real location.
const effectiveParentFetch = parentFetch && parentFetch.localPath
? {packageFs: new NodeFS(), prefixPath: parentFetch.localPath, releaseFs: undefined}
: parentFetch;
// Discard the parent fs unless we really need it to access the files
if (parentFetch && parentFetch !== effectiveParentFetch && parentFetch.releaseFs)
parentFetch.releaseFs();
// First we obtain the specification for all the patches that we'll have to
// apply to the original package.
return await miscUtils.releaseAfterUseAsync(async () => {
return await Promise.all(patchPaths.map(async patchPath => visitPatchPath({
onAbsolute: async () => {
return await xfs.readFilePromise(patchPath, `utf8`);
},
onRelative: async () => {
if (parentFetch === null)
throw new Error(`Assertion failed: The parent locator should have been fetched`);
return await parentFetch.packageFs.readFilePromise(patchPath, `utf8`);
},
onBuiltin: async name => {
return await opts.project.configuration.firstHook((hooks: PatchHooks) => {
return hooks.getBuiltinPatch;
}, opts.project, name);
},
}, patchPath)));
});
}
export async function extractPackageToDisk(locator: Locator, {cache, project}: {cache: Cache, project: Project}) {
const checksums = project.storedChecksums;
const report = new ThrowReport();
const fetcher = project.configuration.makeFetcher();
const fetchResult = await fetcher.fetch(locator, {cache, project, fetcher, checksums, report});
const temp = await xfs.mktempPromise();
await xfs.copyPromise(temp, fetchResult.prefixPath, {
baseFs: fetchResult.packageFs,
});
await xfs.writeJsonPromise(ppath.join(temp, `.yarn-patch.json` as Filename), {
locator: structUtils.stringifyLocator(locator),
});
return temp;
}