-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
/
ExecFetcher.ts
161 lines (133 loc) Β· 6.05 KB
/
ExecFetcher.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
import {execUtils, scriptUtils, structUtils, tgzUtils} from '@yarnpkg/core';
import {Locator, formatUtils} from '@yarnpkg/core';
import {Fetcher, FetchOptions, MinimalFetchOptions} from '@yarnpkg/core';
import {PortablePath, npath, ppath, xfs, NativePath} from '@yarnpkg/fslib';
import {PROTOCOL} from './constants';
import {loadGeneratorFile} from './execUtils';
/**
* Contains various useful details about the execution context.
*/
export interface ExecEnv {
/**
* The absolute path of the empty temporary directory. It is created before the script is invoked.
*/
tempDir: NativePath;
/**
* The absolute path of the empty build directory that will be compressed into an archive and stored within the cache. It is created before the script is invoked.
*/
buildDir: NativePath;
/**
* The stringified Locator identifying the generator package.
*/
locator: string;
}
export class ExecFetcher implements Fetcher {
supports(locator: Locator, opts: MinimalFetchOptions) {
if (!locator.reference.startsWith(PROTOCOL))
return false;
return true;
}
getLocalPath(locator: Locator, opts: FetchOptions) {
const {parentLocator, path} = structUtils.parseFileStyleRange(locator.reference, {protocol: PROTOCOL});
if (ppath.isAbsolute(path))
return path;
const parentLocalPath = opts.fetcher.getLocalPath(parentLocator, opts);
if (parentLocalPath === null)
return null;
return ppath.resolve(parentLocalPath, path);
}
async fetch(locator: Locator, opts: FetchOptions) {
const expectedChecksum = opts.checksums.get(locator.locatorHash) || null;
const [packageFs, releaseFs, checksum] = await opts.cache.fetchPackageFromCache(locator, expectedChecksum, {
onHit: () => opts.report.reportCacheHit(locator),
onMiss: () => opts.report.reportCacheMiss(locator),
loader: () => this.fetchFromDisk(locator, opts),
...opts.cacheOptions,
});
return {
packageFs,
releaseFs,
prefixPath: structUtils.getIdentVendorPath(locator),
localPath: this.getLocalPath(locator, opts),
checksum,
};
}
private async fetchFromDisk(locator: Locator, opts: FetchOptions) {
const generatorFile = await loadGeneratorFile(locator.reference, PROTOCOL, opts);
return xfs.mktempPromise(async generatorDir => {
const generatorPath = ppath.join(generatorDir, `generator.js`);
await xfs.writeFilePromise(generatorPath, generatorFile);
return xfs.mktempPromise(async cwd => {
// Execute the specified script in the temporary directory
await this.generatePackage(cwd, locator, generatorPath, opts);
// Make sure the script generated the package
if (!xfs.existsSync(ppath.join(cwd, `build`)))
throw new Error(`The script should have generated a build directory`);
return await tgzUtils.makeArchiveFromDirectory(ppath.join(cwd, `build`), {
prefixPath: structUtils.getIdentVendorPath(locator),
compressionLevel: opts.project.configuration.get(`compressionLevel`),
});
});
});
}
private async generatePackage(cwd: PortablePath, locator: Locator, generatorPath: PortablePath, opts: FetchOptions) {
return await xfs.mktempPromise(async binFolder => {
const env = await scriptUtils.makeScriptEnv({project: opts.project, binFolder});
const runtimeFile = ppath.join(cwd, `runtime.js`);
return await xfs.mktempPromise(async logDir => {
const logFile = ppath.join(logDir, `buildfile.log`);
const tempDir = ppath.join(cwd, `generator`);
const buildDir = ppath.join(cwd, `build`);
await xfs.mkdirPromise(tempDir);
await xfs.mkdirPromise(buildDir);
/**
* Values exposed on the global `execEnv` variable.
*
* Must be stringifiable using `JSON.stringify`.
*/
const execEnvValues: ExecEnv = {
tempDir: npath.fromPortablePath(tempDir),
buildDir: npath.fromPortablePath(buildDir),
locator: structUtils.stringifyLocator(locator),
};
await xfs.writeFilePromise(runtimeFile, `
// Expose 'Module' as a global variable
Object.defineProperty(global, 'Module', {
get: () => require('module'),
configurable: true,
enumerable: false,
});
// Expose non-hidden built-in modules as global variables
for (const name of Module.builtinModules.filter((name) => name !== 'module' && !name.startsWith('_'))) {
Object.defineProperty(global, name, {
get: () => require(name),
configurable: true,
enumerable: false,
});
}
// Expose the 'execEnv' global variable
Object.defineProperty(global, 'execEnv', {
value: {
...${JSON.stringify(execEnvValues)},
},
enumerable: true,
});
`);
let nodeOptions = env.NODE_OPTIONS || ``;
const pnpRegularExpression = /\s*--require\s+\S*\.pnp\.c?js\s*/g;
nodeOptions = nodeOptions.replace(pnpRegularExpression, ` `).trim();
env.NODE_OPTIONS = nodeOptions;
const {stdout, stderr} = opts.project.configuration.getSubprocessStreams(logFile, {
header: `# This file contains the result of Yarn generating a package (${structUtils.stringifyLocator(locator)})\n`,
prefix: structUtils.prettyLocator(opts.project.configuration, locator),
report: opts.report,
});
const {code} = await execUtils.pipevp(process.execPath, [`--require`, npath.fromPortablePath(runtimeFile), npath.fromPortablePath(generatorPath), structUtils.stringifyIdent(locator)], {cwd, env, stdin: null, stdout, stderr});
if (code !== 0) {
xfs.detachTemp(logDir);
throw new Error(`Package generation failed (exit code ${code}, logs can be found here: ${formatUtils.pretty(opts.project.configuration, logFile, formatUtils.Type.PATH)})`);
}
});
});
}
}