mirror of
https://github.com/zhigang1992/yarn.git
synced 2026-04-28 09:23:42 +08:00
**Summary** This pr fixes a bug ([#5655](https://github.com/yarnpkg/yarn/issues/5655)) in which `yarn run --non-interactive` prints a `Error: No command specified` message and also suppresses the message `question Which command would you like to run?:` when running non-interactively. **Test plan** ``` > yarn-local run --non-interactive yarn run v1.6.0 info Commands available from binary scripts: acorn, atob, babylon, browserslist, commitizen, detect-libc, errno, escodegen, esgenerate, eslint, esparse, esvalidate, flow, git-cz, git-release-notes, gulp, gunzip-maybe, handlebars, import-local-fixture, jest, jest-runtime, js-yaml, jsesc, jsinspect, json5, loose-envify, miller-rabin, mkdirp, node-pre-gyp, nopt, prettier, rc, regjsparser, rimraf, sane, semver, sha.js, shjs, sshpk-conv, sshpk-sign, sshpk-verify, strip-indent, strip-json-comments, uglifyjs, user-home, uuid, watch, webpack, which info Project commands - build gulp build - build-bundle node ./scripts/build-webpack.js - build-chocolatey powershell ./scripts/build-chocolatey.ps1 - build-deb ./scripts/build-deb.sh - build-dist bash ./scripts/build-dist.sh - build-win-installer scripts\build-windows-installer.bat - changelog git-release-notes $(git describe --tags --abbrev=0 $(git describe --tags --abbrev=0)^)..$(git describe --tags --abbrev=0) scripts/changelog.md - commit git-cz - dupe-check yarn jsinspect ./src - lint eslint . && flow check - pkg-tests yarn --cwd packages/pkg-tests jest yarn.test.js - prettier eslint src __tests__ --fix - release-branch ./scripts/release-branch.sh - test yarn lint && yarn test-only - test-coverage node --max_old_space_size=4096 node_modules/jest/bin/jest.js --coverage --verbose - test-only node --max_old_space_size=4096 node_modules/jest/bin/jest.js --verbose - watch gulp watch ✨ Done in 0.28s. ```
223 lines
8.5 KiB
JavaScript
223 lines
8.5 KiB
JavaScript
/* @flow */
|
|
|
|
jest.mock('../../src/util/execute-lifecycle-script', () => {
|
|
return {
|
|
// $FlowFixMe
|
|
...require.requireActual('../../src/util/execute-lifecycle-script'),
|
|
execCommand: jest.fn(),
|
|
};
|
|
});
|
|
|
|
import path from 'path';
|
|
|
|
import {run as buildRun} from './_helpers.js';
|
|
import {BufferReporter} from '../../src/reporters/index.js';
|
|
import {run} from '../../src/cli/commands/run.js';
|
|
import * as fs from '../../src/util/fs.js';
|
|
import * as reporters from '../../src/reporters/index.js';
|
|
|
|
jasmine.DEFAULT_TIMEOUT_INTERVAL = 90000;
|
|
|
|
const {execCommand}: $FlowFixMe = require('../../src/util/execute-lifecycle-script');
|
|
|
|
beforeEach(() => execCommand.mockClear());
|
|
|
|
const fixturesLoc = path.join(__dirname, '..', 'fixtures', 'run');
|
|
const runRun = buildRun.bind(null, BufferReporter, fixturesLoc, (args, flags, config, reporter): Promise<void> => {
|
|
return run(config, reporter, flags, args);
|
|
});
|
|
const runRunInWorkspacePackage = function(cwd, ...args): Promise<void> {
|
|
return buildRun.bind(null, BufferReporter, fixturesLoc, (args, flags, config, reporter): Promise<void> => {
|
|
const originalCwd = config.cwd;
|
|
config.cwd = path.join(originalCwd, cwd);
|
|
const retVal = run(config, reporter, flags, args);
|
|
retVal.then(() => {
|
|
config.cwd = originalCwd;
|
|
});
|
|
return retVal;
|
|
})(...args);
|
|
};
|
|
const runRunWithCustomShell = function(customShell, ...args): Promise<void> {
|
|
return buildRun.bind(null, BufferReporter, fixturesLoc, (args, flags, config, reporter): Promise<void> => {
|
|
const yarnRegistry = config.registries.yarn;
|
|
const originalCustomShell = yarnRegistry.config['script-shell'];
|
|
yarnRegistry.config['script-shell'] = customShell;
|
|
const retVal = run(config, reporter, flags, args);
|
|
retVal.then(() => {
|
|
yarnRegistry.config['script-shell'] = originalCustomShell;
|
|
});
|
|
return retVal;
|
|
})(...args);
|
|
};
|
|
|
|
test('lists all available commands with no arguments', (): Promise<void> =>
|
|
runRun([], {}, 'no-args', (config, reporter): ?Promise<void> => {
|
|
const rprtr = new reporters.BufferReporter({stdout: null, stdin: null});
|
|
const scripts = ['build', 'prestart', 'start'];
|
|
const hints = {
|
|
build: "echo 'building'",
|
|
prestart: "echo 'prestart'",
|
|
start: 'node index.js',
|
|
};
|
|
const bins = ['cat-names'];
|
|
|
|
// Emulate run output
|
|
rprtr.info(`${rprtr.lang('binCommands')}${bins.join(', ')}`);
|
|
rprtr.info(rprtr.lang('possibleCommands'));
|
|
rprtr.list('possibleCommands', scripts, hints);
|
|
rprtr.error(rprtr.lang('commandNotSpecified'));
|
|
|
|
expect(reporter.getBuffer()).toEqual(rprtr.getBuffer());
|
|
}));
|
|
|
|
test('lists all available commands with no arguments and --non-interactive', (): Promise<void> =>
|
|
runRun([], {nonInteractive: true}, 'no-args', (config, reporter): ?Promise<void> => {
|
|
const rprtr = new reporters.BufferReporter({stdout: null, stdin: null});
|
|
const scripts = ['build', 'prestart', 'start'];
|
|
const hints = {
|
|
build: "echo 'building'",
|
|
prestart: "echo 'prestart'",
|
|
start: 'node index.js',
|
|
};
|
|
const bins = ['cat-names'];
|
|
|
|
// Emulate run output
|
|
rprtr.info(`${rprtr.lang('binCommands')}${bins.join(', ')}`);
|
|
rprtr.info(rprtr.lang('possibleCommands'));
|
|
rprtr.list('possibleCommands', scripts, hints);
|
|
|
|
expect(reporter.getBuffer()).toEqual(rprtr.getBuffer());
|
|
}));
|
|
|
|
test('runs script containing spaces', (): Promise<void> =>
|
|
runRun(['build'], {}, 'spaces', async (config): ?Promise<void> => {
|
|
const pkg = await fs.readJson(path.join(config.cwd, 'package.json'));
|
|
// The command gets called with a space appended
|
|
expect(execCommand).toBeCalledWith({
|
|
stage: 'build',
|
|
config,
|
|
cmd: pkg.scripts.build,
|
|
cwd: config.cwd,
|
|
isInteractive: true,
|
|
});
|
|
}));
|
|
|
|
test('properly handles extra arguments and pre/post scripts', (): Promise<void> =>
|
|
runRun(['start', '--hello'], {}, 'extra-args', async (config): ?Promise<void> => {
|
|
const pkg = await fs.readJson(path.join(config.cwd, 'package.json'));
|
|
const poststart = {stage: 'poststart', config, cmd: pkg.scripts.poststart, cwd: config.cwd, isInteractive: true};
|
|
const prestart = {stage: 'prestart', config, cmd: pkg.scripts.prestart, cwd: config.cwd, isInteractive: true};
|
|
const start = {stage: 'start', config, cmd: pkg.scripts.start + ' --hello', cwd: config.cwd, isInteractive: true};
|
|
|
|
expect(execCommand.mock.calls[0]).toEqual([prestart]);
|
|
expect(execCommand.mock.calls[1]).toEqual([start]);
|
|
expect(execCommand.mock.calls[2]).toEqual([poststart]);
|
|
}));
|
|
|
|
test('properly handle bin scripts', (): Promise<void> =>
|
|
runRun(['cat-names'], {}, 'bin', config => {
|
|
const script = path.join(config.cwd, 'node_modules', '.bin', 'cat-names');
|
|
|
|
expect(execCommand).toBeCalledWith({
|
|
stage: 'cat-names',
|
|
config,
|
|
cmd: script,
|
|
cwd: config.cwd,
|
|
isInteractive: true,
|
|
});
|
|
}));
|
|
|
|
test('properly handle env command', (): Promise<void> =>
|
|
runRun(['env'], {}, 'no-args', (config, reporter): ?Promise<void> => {
|
|
// $FlowFixMe
|
|
const result = JSON.parse(reporter.getBuffer()[0].data);
|
|
|
|
const env = {};
|
|
let pathVarName = 'PATH';
|
|
for (const key of Object.keys(process.env)) {
|
|
// Filter out yarn-added `npm_` variables since we run tests through yarn already
|
|
if (key.startsWith('npm_')) {
|
|
continue;
|
|
}
|
|
// We need this below for Windows which has case-insensitive env vars
|
|
// If we used `process.env` directly, node takes care of this for us,
|
|
// but since we use a subset of it, we need to get the "real" path key
|
|
// name for Jest's case-sensitive object comparison below.
|
|
if (key.toUpperCase() === 'PATH') {
|
|
pathVarName = key;
|
|
}
|
|
env[key] = process.env[key];
|
|
}
|
|
|
|
result[pathVarName] = result[pathVarName] ? result[pathVarName].split(path.delimiter) : [];
|
|
// $FlowFixMe
|
|
env[pathVarName] = env[pathVarName] ? expect.arrayContaining(env[pathVarName].split(path.delimiter)) : [];
|
|
|
|
expect(result).toMatchObject(env);
|
|
expect(result).toHaveProperty('npm_lifecycle_event');
|
|
expect(result).toHaveProperty('npm_execpath');
|
|
expect(result).toHaveProperty('npm_node_execpath');
|
|
}));
|
|
|
|
test('adds string delimiters if args have spaces', (): Promise<void> =>
|
|
runRun(['cat-names', '--filter', 'cat names'], {}, 'bin', config => {
|
|
const script = path.join(config.cwd, 'node_modules', '.bin', 'cat-names');
|
|
const q = process.platform === 'win32' ? '"' : "'";
|
|
|
|
expect(execCommand).toBeCalledWith({
|
|
stage: 'cat-names',
|
|
config,
|
|
cmd: `${script} --filter ${q}cat names${q}`,
|
|
cwd: config.cwd,
|
|
isInteractive: true,
|
|
});
|
|
}));
|
|
|
|
test('adds quotes if args have spaces and quotes', (): Promise<void> =>
|
|
runRun(['cat-names', '--filter', '"cat names"'], {}, 'bin', config => {
|
|
const script = path.join(config.cwd, 'node_modules', '.bin', 'cat-names');
|
|
const quotedCatNames = process.platform === 'win32' ? '^"\\^"cat^ names\\^"^"' : `'"cat names"'`;
|
|
|
|
expect(execCommand).toBeCalledWith({
|
|
stage: 'cat-names',
|
|
config,
|
|
cmd: `${script} --filter ${quotedCatNames}`,
|
|
cwd: config.cwd,
|
|
isInteractive: true,
|
|
});
|
|
}));
|
|
|
|
test('returns noScriptsAvailable with no scripts', (): Promise<void> =>
|
|
runRun([], {}, 'no-scripts', (config, reporter) => {
|
|
expect(reporter.getBuffer()).toMatchSnapshot();
|
|
}));
|
|
|
|
test('returns noBinAvailable with no bins', (): Promise<void> =>
|
|
runRun([], {}, 'no-bin', (config, reporter) => {
|
|
expect(reporter.getBuffer()).toMatchSnapshot();
|
|
}));
|
|
|
|
test('adds workspace root node_modules/.bin to path when in a workspace', (): Promise<void> =>
|
|
runRunInWorkspacePackage('packages/pkg1', ['env'], {}, 'workspace', (config, reporter): ?Promise<void> => {
|
|
const logEntry = reporter.getBuffer().find(entry => entry.type === 'log');
|
|
const parsedLogData = JSON.parse(logEntry ? logEntry.data.toString() : '{}');
|
|
const envPaths = (parsedLogData.PATH || parsedLogData.Path).split(path.delimiter);
|
|
|
|
expect(envPaths).toContain(path.join(config.cwd, 'node_modules', '.bin'));
|
|
expect(envPaths).toContain(path.join(config.cwd, 'packages', 'pkg1', 'node_modules', '.bin'));
|
|
}));
|
|
|
|
test('runs script with custom script-shell', (): Promise<void> =>
|
|
runRunWithCustomShell('/usr/bin/dummy', ['start'], {}, 'script-shell', async (config): ?Promise<void> => {
|
|
const pkg = await fs.readJson(path.join(config.cwd, 'package.json'));
|
|
// The command gets called with the provided customShell
|
|
expect(execCommand).toBeCalledWith({
|
|
stage: 'start',
|
|
config,
|
|
cmd: pkg.scripts.start,
|
|
cwd: config.cwd,
|
|
isInteractive: true,
|
|
customShell: '/usr/bin/dummy',
|
|
});
|
|
}));
|