forked from yarnpkg/yarn
/
run.js
190 lines (157 loc) 路 5.97 KB
/
run.js
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
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
/* @flow */
import type {Reporter} from '../../reporters/index.js';
import type Config from '../../config.js';
import {execCommand, makeEnv} from '../../util/execute-lifecycle-script.js';
import {dynamicRequire} from '../../util/dynamic-require.js';
import {MessageError} from '../../errors.js';
import * as fs from '../../util/fs.js';
import * as constants from '../../constants.js';
const invariant = require('invariant');
const leven = require('leven');
const path = require('path');
const {quoteForShell, sh, unquoted} = require('puka');
function toObject(input: Map<string, string>): Object {
const output = Object.create(null);
for (const [key, val] of input.entries()) {
output[key] = val;
}
return output;
}
export async function getBinEntries(config: Config): Promise<Map<string, string>> {
const binFolders = new Set();
const binEntries = new Map();
// Setup the node_modules/.bin folders for analysis
for (const registryFolder of config.registryFolders) {
binFolders.add(path.resolve(config.cwd, registryFolder, '.bin'));
binFolders.add(path.resolve(config.lockfileFolder, registryFolder, '.bin'));
}
// Same thing, but for the pnp dependencies, located inside the cache
if (await fs.exists(`${config.lockfileFolder}/${constants.PNP_FILENAME}`)) {
const pnpApi = dynamicRequire(`${config.lockfileFolder}/${constants.PNP_FILENAME}`);
const packageLocator = pnpApi.findPackageLocator(`${config.cwd}/`);
const packageInformation = pnpApi.getPackageInformation(packageLocator);
for (const [name, reference] of packageInformation.packageDependencies.entries()) {
const dependencyInformation = pnpApi.getPackageInformation({name, reference});
if (dependencyInformation.packageLocation) {
binFolders.add(`${dependencyInformation.packageLocation}/.bin`);
}
}
}
// Build up a list of possible scripts by exploring the folders marked for analysis
for (const binFolder of binFolders) {
if (await fs.exists(binFolder)) {
for (const name of await fs.readdir(binFolder)) {
binEntries.set(name, path.join(binFolder, name));
}
}
}
return binEntries;
}
export function setFlags(commander: Object) {
commander.description('Runs a defined package script.');
}
export function hasWrapper(commander: Object, args: Array<string>): boolean {
return true;
}
export async function run(config: Config, reporter: Reporter, flags: Object, args: Array<string>): Promise<void> {
const pkg = await config.readManifest(config.cwd);
const binCommands = new Set();
const pkgCommands = new Set();
const scripts: Map<string, string> = new Map();
for (const [name, loc] of await getBinEntries(config)) {
scripts.set(name, quoteForShell(loc));
binCommands.add(name);
}
const pkgScripts = pkg.scripts;
if (pkgScripts) {
for (const name of Object.keys(pkgScripts).sort()) {
scripts.set(name, pkgScripts[name] || '');
pkgCommands.add(name);
}
}
async function runCommand(args): Promise<void> {
const action = args.shift();
// build up list of commands
const cmds = [];
if (pkgScripts && action in pkgScripts) {
const preAction = `pre${action}`;
if (preAction in pkgScripts) {
cmds.push([preAction, pkgScripts[preAction]]);
}
const script = scripts.get(action);
invariant(script, 'Script must exist');
cmds.push([action, script]);
const postAction = `post${action}`;
if (postAction in pkgScripts) {
cmds.push([postAction, pkgScripts[postAction]]);
}
} else if (scripts.has(action)) {
const script = scripts.get(action);
invariant(script, 'Script must exist');
cmds.push([action, script]);
}
if (cmds.length) {
// Disable wrapper in executed commands
process.env.YARN_WRAP_OUTPUT = 'false';
for (const [stage, cmd] of cmds) {
// only tack on trailing arguments for default script, ignore for pre and post - #1595
const cmdWithArgs = stage === action ? sh`${unquoted(cmd)} ${args}` : cmd;
const customShell = config.getOption('script-shell');
await execCommand({
stage,
config,
cmd: cmdWithArgs,
cwd: flags.into || config.cwd,
isInteractive: true,
customShell: customShell ? String(customShell) : undefined,
});
}
} else if (action === 'env') {
reporter.log(JSON.stringify(await makeEnv('env', config.cwd, config), null, 2), {force: true});
} else {
let suggestion;
for (const commandName in scripts) {
const steps = leven(commandName, action);
if (steps < 2) {
suggestion = commandName;
}
}
let msg = `Command ${JSON.stringify(action)} not found.`;
if (suggestion) {
msg += ` Did you mean ${JSON.stringify(suggestion)}?`;
}
throw new MessageError(msg);
}
}
// list possible scripts if none specified
if (args.length === 0) {
if (binCommands.size > 0) {
reporter.info(`${reporter.lang('binCommands') + Array.from(binCommands).join(', ')}`);
} else {
reporter.error(reporter.lang('noBinAvailable'));
}
const printedCommands: Map<string, string> = new Map();
for (const pkgCommand of pkgCommands) {
const action = scripts.get(pkgCommand);
invariant(action, 'Action must exists');
printedCommands.set(pkgCommand, action);
}
if (pkgCommands.size > 0) {
reporter.info(`${reporter.lang('possibleCommands')}`);
reporter.list('possibleCommands', Array.from(pkgCommands), toObject(printedCommands));
if (!flags.nonInteractive) {
await reporter
.question(reporter.lang('commandQuestion'))
.then(
answer => runCommand(answer.trim().split(' ')),
() => reporter.error(reporter.lang('commandNotSpecified')),
);
}
} else {
reporter.error(reporter.lang('noScriptsAvailable'));
}
return Promise.resolve();
} else {
return runCommand(args);
}
}