UNPKG

23.9 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3exports.executeWorkspaceAccessibleBinary = exports.executePackageAccessibleBinary = exports.getWorkspaceAccessibleBinaries = exports.getPackageAccessibleBinaries = exports.maybeExecuteWorkspaceLifecycleScript = exports.executeWorkspaceLifecycleScript = exports.hasWorkspaceScript = exports.executeWorkspaceScript = exports.executePackageShellcode = exports.executePackageScript = exports.hasPackageScript = exports.prepareExternalProject = exports.makeScriptEnv = void 0;
4const tslib_1 = require("tslib");
5const fslib_1 = require("@yarnpkg/fslib");
6const fslib_2 = require("@yarnpkg/fslib");
7const libzip_1 = require("@yarnpkg/libzip");
8const shell_1 = require("@yarnpkg/shell");
9const binjumper_1 = require("binjumper");
10const capitalize_1 = tslib_1.__importDefault(require("lodash/capitalize"));
11const p_limit_1 = tslib_1.__importDefault(require("p-limit"));
12const stream_1 = require("stream");
13const Manifest_1 = require("./Manifest");
14const MessageName_1 = require("./MessageName");
15const Report_1 = require("./Report");
16const StreamReport_1 = require("./StreamReport");
17const YarnVersion_1 = require("./YarnVersion");
18const execUtils = tslib_1.__importStar(require("./execUtils"));
19const formatUtils = tslib_1.__importStar(require("./formatUtils"));
20const miscUtils = tslib_1.__importStar(require("./miscUtils"));
21const structUtils = tslib_1.__importStar(require("./structUtils"));
22var PackageManager;
23(function (PackageManager) {
24 PackageManager["Yarn1"] = "Yarn Classic";
25 PackageManager["Yarn2"] = "Yarn";
26 PackageManager["Npm"] = "npm";
27 PackageManager["Pnpm"] = "pnpm";
28})(PackageManager || (PackageManager = {}));
29async function makePathWrapper(location, name, argv0, args = []) {
30 if (process.platform === `win32`) {
31 await Promise.all([
32 fslib_2.xfs.writeFilePromise(fslib_2.ppath.format({ dir: location, name, ext: `.exe` }), binjumper_1.getBinjumper()),
33 fslib_2.xfs.writeFilePromise(fslib_2.ppath.format({ dir: location, name, ext: `.exe.info` }), [argv0, ...args].join(`\n`)),
34 fslib_2.xfs.writeFilePromise(fslib_2.ppath.format({ dir: location, name, ext: `.cmd` }), `@"${argv0}" ${args.map(arg => `"${arg.replace(`"`, `""`)}"`).join(` `)} %*\n`),
35 ]);
36 }
37 await fslib_2.xfs.writeFilePromise(fslib_2.ppath.join(location, name), `#!/bin/sh\nexec "${argv0}" ${args.map(arg => `'${arg.replace(/'/g, `'"'"'`)}'`).join(` `)} "$@"\n`);
38 await fslib_2.xfs.chmodPromise(fslib_2.ppath.join(location, name), 0o755);
39}
40async function detectPackageManager(location) {
41 let yarnLock = null;
42 try {
43 yarnLock = await fslib_2.xfs.readFilePromise(fslib_2.ppath.join(location, fslib_1.Filename.lockfile), `utf8`);
44 }
45 catch (_a) { }
46 if (yarnLock !== null) {
47 if (yarnLock.match(/^__metadata:$/m)) {
48 return PackageManager.Yarn2;
49 }
50 else {
51 return PackageManager.Yarn1;
52 }
53 }
54 if (fslib_2.xfs.existsSync(fslib_2.ppath.join(location, `package-lock.json`)))
55 return PackageManager.Npm;
56 if (fslib_2.xfs.existsSync(fslib_2.ppath.join(location, `pnpm-lock.yaml`)))
57 return PackageManager.Pnpm;
58 return null;
59}
60async function makeScriptEnv({ project, binFolder, lifecycleScript }) {
61 const scriptEnv = {};
62 for (const [key, value] of Object.entries(process.env))
63 if (typeof value !== `undefined`)
64 scriptEnv[key.toLowerCase() !== `path` ? key : `PATH`] = value;
65 const nBinFolder = fslib_2.npath.fromPortablePath(binFolder);
66 // We expose the base folder in the environment so that we can later add the
67 // binaries for the dependencies of the active package
68 scriptEnv.BERRY_BIN_FOLDER = fslib_2.npath.fromPortablePath(nBinFolder);
69 // Register some binaries that must be made available in all subprocesses
70 // spawned by Yarn (we thus ensure that they always use the right version)
71 await makePathWrapper(binFolder, `node`, process.execPath);
72 if (YarnVersion_1.YarnVersion !== null) {
73 await makePathWrapper(binFolder, `run`, process.execPath, [process.argv[1], `run`]);
74 await makePathWrapper(binFolder, `yarn`, process.execPath, [process.argv[1]]);
75 await makePathWrapper(binFolder, `yarnpkg`, process.execPath, [process.argv[1]]);
76 await makePathWrapper(binFolder, `node-gyp`, process.execPath, [process.argv[1], `run`, `--top-level`, `node-gyp`]);
77 }
78 if (project)
79 scriptEnv.INIT_CWD = fslib_2.npath.fromPortablePath(project.configuration.startingCwd);
80 scriptEnv.PATH = scriptEnv.PATH
81 ? `${nBinFolder}${fslib_2.npath.delimiter}${scriptEnv.PATH}`
82 : `${nBinFolder}`;
83 scriptEnv.npm_execpath = `${nBinFolder}${fslib_2.npath.sep}yarn`;
84 scriptEnv.npm_node_execpath = `${nBinFolder}${fslib_2.npath.sep}node`;
85 const version = YarnVersion_1.YarnVersion !== null
86 ? `yarn/${YarnVersion_1.YarnVersion}`
87 : `yarn/${miscUtils.dynamicRequire(`@yarnpkg/core`).version}-core`;
88 scriptEnv.npm_config_user_agent = `${version} npm/? node/${process.versions.node} ${process.platform} ${process.arch}`;
89 if (lifecycleScript)
90 scriptEnv.npm_lifecycle_event = lifecycleScript;
91 if (project) {
92 await project.configuration.triggerHook(hook => hook.setupScriptEnvironment, project, scriptEnv, async (name, argv0, args) => {
93 return await makePathWrapper(binFolder, fslib_2.toFilename(name), argv0, args);
94 });
95 }
96 return scriptEnv;
97}
98exports.makeScriptEnv = makeScriptEnv;
99/**
100 * Given a folder, prepares this project for use. Runs `yarn install` then
101 * `yarn build` if a `package.json` is found.
102 */
103const MAX_PREPARE_CONCURRENCY = 2;
104const prepareLimit = p_limit_1.default(MAX_PREPARE_CONCURRENCY);
105async function prepareExternalProject(cwd, outputPath, { configuration, report, workspace = null }) {
106 await prepareLimit(async () => {
107 await fslib_2.xfs.mktempPromise(async (logDir) => {
108 const logFile = fslib_2.ppath.join(logDir, `pack.log`);
109 const stdin = null;
110 const { stdout, stderr } = configuration.getSubprocessStreams(logFile, { prefix: cwd, report });
111 const packageManager = await detectPackageManager(cwd);
112 let effectivePackageManager;
113 if (packageManager !== null) {
114 stdout.write(`Installing the project using ${packageManager}\n\n`);
115 effectivePackageManager = packageManager;
116 }
117 else {
118 stdout.write(`No package manager detected; defaulting to Yarn\n\n`);
119 effectivePackageManager = PackageManager.Yarn2;
120 }
121 await fslib_2.xfs.mktempPromise(async (binFolder) => {
122 const env = await makeScriptEnv({ binFolder });
123 const workflows = new Map([
124 [PackageManager.Yarn1, async () => {
125 const workspaceCli = workspace !== null
126 ? [`workspace`, workspace]
127 : [];
128 // Makes sure that we'll be using Yarn 1.x
129 const version = await execUtils.pipevp(`yarn`, [`set`, `version`, `classic`, `--only-if-needed`], { cwd, env, stdin, stdout, stderr, end: execUtils.EndStrategy.ErrorCode });
130 if (version.code !== 0)
131 return version.code;
132 // Otherwise Yarn 1 will pack the .yarn directory :(
133 await fslib_2.xfs.appendFilePromise(fslib_2.ppath.join(cwd, `.npmignore`), `/.yarn\n`);
134 stdout.write(`\n`);
135 // Run an install; we can't avoid it unless we inspect the
136 // package.json, which I don't want to do to keep the codebase
137 // clean (even if it has a slight perf cost when cloning v1 repos)
138 const install = await execUtils.pipevp(`yarn`, [`install`], { cwd, env, stdin, stdout, stderr, end: execUtils.EndStrategy.ErrorCode });
139 if (install.code !== 0)
140 return install.code;
141 stdout.write(`\n`);
142 const pack = await execUtils.pipevp(`yarn`, [...workspaceCli, `pack`, `--filename`, fslib_2.npath.fromPortablePath(outputPath)], { cwd, env, stdin, stdout, stderr });
143 if (pack.code !== 0)
144 return pack.code;
145 return 0;
146 }],
147 [PackageManager.Yarn2, async () => {
148 const workspaceCli = workspace !== null
149 ? [`workspace`, workspace]
150 : [];
151 // We enable inline builds, because nobody wants to
152 // read a logfile telling them to open another logfile
153 env.YARN_ENABLE_INLINE_BUILDS = `1`;
154 // Yarn 2 supports doing the install and the pack in a single command,
155 // so we leverage that. We also don't need the "set version" call since
156 // we're already operating within a Yarn 2 context (plus people should
157 // really check-in their Yarn versions anyway).
158 const pack = await execUtils.pipevp(`yarn`, [...workspaceCli, `pack`, `--install-if-needed`, `--filename`, fslib_2.npath.fromPortablePath(outputPath)], { cwd, env, stdin, stdout, stderr });
159 if (pack.code !== 0)
160 return pack.code;
161 return 0;
162 }],
163 [PackageManager.Npm, async () => {
164 if (workspace !== null)
165 throw new Error(`Workspaces aren't supported by npm, which has been detected as the primary package manager for ${cwd}`);
166 // Otherwise npm won't properly set the user agent, using the Yarn
167 // one instead
168 delete env.npm_config_user_agent;
169 // We can't use `npm ci` because some projects don't have npm
170 // lockfiles that are up-to-date. Hopefully npm won't decide
171 // to change the versions randomly.
172 const install = await execUtils.pipevp(`npm`, [`install`], { cwd, env, stdin, stdout, stderr, end: execUtils.EndStrategy.ErrorCode });
173 if (install.code !== 0)
174 return install.code;
175 const packStream = new stream_1.PassThrough();
176 const packPromise = miscUtils.bufferStream(packStream);
177 packStream.pipe(stdout);
178 // It seems that npm doesn't support specifying the pack output path,
179 // so we have to extract the stdout on top of forking it to the logs.
180 const pack = await execUtils.pipevp(`npm`, [`pack`, `--silent`], { cwd, env, stdin, stdout: packStream, stderr });
181 if (pack.code !== 0)
182 return pack.code;
183 const packOutput = (await packPromise).toString().trim();
184 const packTarget = fslib_2.ppath.resolve(cwd, fslib_2.npath.toPortablePath(packOutput));
185 // Only then can we move the pack to its rightful location
186 await fslib_2.xfs.renamePromise(packTarget, outputPath);
187 return 0;
188 }],
189 ]);
190 const workflow = workflows.get(effectivePackageManager);
191 if (typeof workflow === `undefined`)
192 throw new Error(`Assertion failed: Unsupported workflow`);
193 const code = await workflow();
194 if (code === 0 || typeof code === `undefined`)
195 return;
196 fslib_2.xfs.detachTemp(logDir);
197 throw new Report_1.ReportError(MessageName_1.MessageName.PACKAGE_PREPARATION_FAILED, `Packing the package failed (exit code ${code}, logs can be found here: ${logFile})`);
198 });
199 });
200 });
201}
202exports.prepareExternalProject = prepareExternalProject;
203async function hasPackageScript(locator, scriptName, { project }) {
204 const pkg = project.storedPackages.get(locator.locatorHash);
205 if (!pkg)
206 throw new Error(`Package for ${structUtils.prettyLocator(project.configuration, locator)} not found in the project`);
207 return await fslib_1.ZipOpenFS.openPromise(async (zipOpenFs) => {
208 const configuration = project.configuration;
209 const linkers = project.configuration.getLinkers();
210 const linkerOptions = { project, report: new StreamReport_1.StreamReport({ stdout: new stream_1.PassThrough(), configuration }) };
211 const linker = linkers.find(linker => linker.supportsPackage(pkg, linkerOptions));
212 if (!linker)
213 throw new Error(`The package ${structUtils.prettyLocator(project.configuration, pkg)} isn't supported by any of the available linkers`);
214 const packageLocation = await linker.findPackageLocation(pkg, linkerOptions);
215 const packageFs = new fslib_1.CwdFS(packageLocation, { baseFs: zipOpenFs });
216 const manifest = await Manifest_1.Manifest.find(fslib_1.PortablePath.dot, { baseFs: packageFs });
217 return manifest.scripts.has(scriptName);
218 }, {
219 libzip: await libzip_1.getLibzipPromise(),
220 });
221}
222exports.hasPackageScript = hasPackageScript;
223async function executePackageScript(locator, scriptName, args, { cwd, project, stdin, stdout, stderr }) {
224 return await fslib_2.xfs.mktempPromise(async (binFolder) => {
225 const { manifest, env, cwd: realCwd } = await initializePackageEnvironment(locator, { project, binFolder, cwd, lifecycleScript: scriptName });
226 const script = manifest.scripts.get(scriptName);
227 if (typeof script === `undefined`)
228 return 1;
229 const realExecutor = async () => {
230 return await shell_1.execute(script, args, { cwd: realCwd, env, stdin, stdout, stderr });
231 };
232 const executor = await project.configuration.reduceHook(hooks => {
233 return hooks.wrapScriptExecution;
234 }, realExecutor, project, locator, scriptName, {
235 script, args, cwd: realCwd, env, stdin, stdout, stderr,
236 });
237 return await executor();
238 });
239}
240exports.executePackageScript = executePackageScript;
241async function executePackageShellcode(locator, command, args, { cwd, project, stdin, stdout, stderr }) {
242 return await fslib_2.xfs.mktempPromise(async (binFolder) => {
243 const { env, cwd: realCwd } = await initializePackageEnvironment(locator, { project, binFolder, cwd });
244 return await shell_1.execute(command, args, { cwd: realCwd, env, stdin, stdout, stderr });
245 });
246}
247exports.executePackageShellcode = executePackageShellcode;
248async function initializePackageEnvironment(locator, { project, binFolder, cwd, lifecycleScript }) {
249 const pkg = project.storedPackages.get(locator.locatorHash);
250 if (!pkg)
251 throw new Error(`Package for ${structUtils.prettyLocator(project.configuration, locator)} not found in the project`);
252 return await fslib_1.ZipOpenFS.openPromise(async (zipOpenFs) => {
253 const configuration = project.configuration;
254 const linkers = project.configuration.getLinkers();
255 const linkerOptions = { project, report: new StreamReport_1.StreamReport({ stdout: new stream_1.PassThrough(), configuration }) };
256 const linker = linkers.find(linker => linker.supportsPackage(pkg, linkerOptions));
257 if (!linker)
258 throw new Error(`The package ${structUtils.prettyLocator(project.configuration, pkg)} isn't supported by any of the available linkers`);
259 const env = await makeScriptEnv({ project, binFolder, lifecycleScript });
260 for (const [binaryName, [, binaryPath]] of await getPackageAccessibleBinaries(locator, { project }))
261 await makePathWrapper(binFolder, fslib_2.toFilename(binaryName), process.execPath, [binaryPath]);
262 const packageLocation = await linker.findPackageLocation(pkg, linkerOptions);
263 const packageFs = new fslib_1.CwdFS(packageLocation, { baseFs: zipOpenFs });
264 const manifest = await Manifest_1.Manifest.find(fslib_1.PortablePath.dot, { baseFs: packageFs });
265 if (typeof cwd === `undefined`)
266 cwd = packageLocation;
267 return { manifest, binFolder, env, cwd };
268 }, {
269 libzip: await libzip_1.getLibzipPromise(),
270 });
271}
272async function executeWorkspaceScript(workspace, scriptName, args, { cwd, stdin, stdout, stderr }) {
273 return await executePackageScript(workspace.anchoredLocator, scriptName, args, { cwd, project: workspace.project, stdin, stdout, stderr });
274}
275exports.executeWorkspaceScript = executeWorkspaceScript;
276function hasWorkspaceScript(workspace, scriptName) {
277 return workspace.manifest.scripts.has(scriptName);
278}
279exports.hasWorkspaceScript = hasWorkspaceScript;
280async function executeWorkspaceLifecycleScript(workspace, lifecycleScriptName, { cwd, report }) {
281 const { configuration } = workspace.project;
282 const stdin = null;
283 await fslib_2.xfs.mktempPromise(async (logDir) => {
284 const logFile = fslib_2.ppath.join(logDir, `${lifecycleScriptName}.log`);
285 const header = `# This file contains the result of Yarn calling the "${lifecycleScriptName}" lifecycle script inside a workspace ("${workspace.cwd}")\n`;
286 const { stdout, stderr } = configuration.getSubprocessStreams(logFile, {
287 report,
288 prefix: structUtils.prettyLocator(configuration, workspace.anchoredLocator),
289 header,
290 });
291 report.reportInfo(MessageName_1.MessageName.LIFECYCLE_SCRIPT, `Calling the "${lifecycleScriptName}" lifecycle script`);
292 const exitCode = await executeWorkspaceScript(workspace, lifecycleScriptName, [], { cwd, stdin, stdout, stderr });
293 stdout.end();
294 stderr.end();
295 if (exitCode !== 0) {
296 fslib_2.xfs.detachTemp(logDir);
297 throw new Report_1.ReportError(MessageName_1.MessageName.LIFECYCLE_SCRIPT, `${capitalize_1.default(lifecycleScriptName)} script failed (exit code ${formatUtils.pretty(configuration, exitCode, formatUtils.Type.NUMBER)}, logs can be found here: ${formatUtils.pretty(configuration, logFile, formatUtils.Type.PATH)}); run ${formatUtils.pretty(configuration, `yarn ${lifecycleScriptName}`, formatUtils.Type.CODE)} to investigate`);
298 }
299 });
300}
301exports.executeWorkspaceLifecycleScript = executeWorkspaceLifecycleScript;
302async function maybeExecuteWorkspaceLifecycleScript(workspace, lifecycleScriptName, opts) {
303 if (hasWorkspaceScript(workspace, lifecycleScriptName)) {
304 await executeWorkspaceLifecycleScript(workspace, lifecycleScriptName, opts);
305 }
306}
307exports.maybeExecuteWorkspaceLifecycleScript = maybeExecuteWorkspaceLifecycleScript;
308/**
309 * Return the binaries that can be accessed by the specified package
310 *
311 * @param locator The queried package
312 * @param project The project owning the package
313 */
314async function getPackageAccessibleBinaries(locator, { project }) {
315 const configuration = project.configuration;
316 const binaries = new Map();
317 const pkg = project.storedPackages.get(locator.locatorHash);
318 if (!pkg)
319 throw new Error(`Package for ${structUtils.prettyLocator(configuration, locator)} not found in the project`);
320 const stdout = new stream_1.Writable();
321 const linkers = configuration.getLinkers();
322 const linkerOptions = { project, report: new StreamReport_1.StreamReport({ configuration, stdout }) };
323 const visibleLocators = new Set([locator.locatorHash]);
324 for (const descriptor of pkg.dependencies.values()) {
325 const resolution = project.storedResolutions.get(descriptor.descriptorHash);
326 if (!resolution)
327 throw new Error(`Assertion failed: The resolution (${structUtils.prettyDescriptor(configuration, descriptor)}) should have been registered`);
328 visibleLocators.add(resolution);
329 }
330 for (const locatorHash of visibleLocators) {
331 const dependency = project.storedPackages.get(locatorHash);
332 if (!dependency)
333 throw new Error(`Assertion failed: The package (${locatorHash}) should have been registered`);
334 if (dependency.bin.size === 0)
335 continue;
336 const linker = linkers.find(linker => linker.supportsPackage(dependency, linkerOptions));
337 if (!linker)
338 continue;
339 const packageLocation = await linker.findPackageLocation(dependency, linkerOptions);
340 for (const [name, target] of dependency.bin) {
341 binaries.set(name, [dependency, fslib_2.npath.fromPortablePath(fslib_2.ppath.resolve(packageLocation, target))]);
342 }
343 }
344 return binaries;
345}
346exports.getPackageAccessibleBinaries = getPackageAccessibleBinaries;
347/**
348 * Return the binaries that can be accessed by the specified workspace
349 *
350 * @param workspace The queried workspace
351 */
352async function getWorkspaceAccessibleBinaries(workspace) {
353 return await getPackageAccessibleBinaries(workspace.anchoredLocator, { project: workspace.project });
354}
355exports.getWorkspaceAccessibleBinaries = getWorkspaceAccessibleBinaries;
356/**
357 * Execute a binary from the specified package.
358 *
359 * Note that "binary" in this sense means "a Javascript file". Actual native
360 * binaries cannot be executed this way, because we use Node in order to
361 * transparently read from the archives.
362 *
363 * @param locator The queried package
364 * @param binaryName The name of the binary file to execute
365 * @param args The arguments to pass to the file
366 */
367async function executePackageAccessibleBinary(locator, binaryName, args, { cwd, project, stdin, stdout, stderr, nodeArgs = [] }) {
368 const packageAccessibleBinaries = await getPackageAccessibleBinaries(locator, { project });
369 const binary = packageAccessibleBinaries.get(binaryName);
370 if (!binary)
371 throw new Error(`Binary not found (${binaryName}) for ${structUtils.prettyLocator(project.configuration, locator)}`);
372 return await fslib_2.xfs.mktempPromise(async (binFolder) => {
373 const [, binaryPath] = binary;
374 const env = await makeScriptEnv({ project, binFolder });
375 for (const [binaryName, [, binaryPath]] of packageAccessibleBinaries)
376 await makePathWrapper(env.BERRY_BIN_FOLDER, fslib_2.toFilename(binaryName), process.execPath, [binaryPath]);
377 let result;
378 try {
379 result = await execUtils.pipevp(process.execPath, [...nodeArgs, binaryPath, ...args], { cwd, env, stdin, stdout, stderr });
380 }
381 finally {
382 await fslib_2.xfs.removePromise(env.BERRY_BIN_FOLDER);
383 }
384 return result.code;
385 });
386}
387exports.executePackageAccessibleBinary = executePackageAccessibleBinary;
388/**
389 * Execute a binary from the specified workspace
390 *
391 * @param workspace The queried package
392 * @param binaryName The name of the binary file to execute
393 * @param args The arguments to pass to the file
394 */
395async function executeWorkspaceAccessibleBinary(workspace, binaryName, args, { cwd, stdin, stdout, stderr }) {
396 return await executePackageAccessibleBinary(workspace.anchoredLocator, binaryName, args, { project: workspace.project, cwd, stdin, stdout, stderr });
397}
398exports.executeWorkspaceAccessibleBinary = executeWorkspaceAccessibleBinary;