Files
yarn/__tests__/commands/run.js
peijiesim 63c56c7131 fix(run): yarn run should never prompt when non-interactive (#5694)
**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.
```
2018-04-26 19:11:48 +01:00

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',
});
}));