diff --git a/extensions/roc-plugin-repo/src/actions/build.js b/extensions/roc-plugin-repo/src/actions/build.js index 5fc895e..402c687 100644 --- a/extensions/roc-plugin-repo/src/actions/build.js +++ b/extensions/roc-plugin-repo/src/actions/build.js @@ -7,39 +7,41 @@ function buildWithBabel(config, watch, createLogger) { return (projects, mode, multipleTargets) => Promise.all( projects.map(project => { - const logger = createLogger(project.name).logger; + const logger = createLogger(`${project.name} (${mode})`).logger; const babelConfig = invokeHook('babel-config', mode, project); - try { - logger(`Building for ${mode} with Babel`); - return babel( - { - mode, - path: project.path, - src: `${project.path}/${settings.input}`, - out: multipleTargets - ? `${project.path}/${settings.output}/${mode}` - : `${project.path}/${settings.output}`, - // We want to ignore potential __snapshots__ and __mocks__ directories - ignore: settings.test.concat([ - '**/__snapshots__/**', - '**/__mocks__/**', - ]), - copyFiles: true, - sourceMaps: true, - babelrc: false, - watch, - log: logger, - }, - babelConfig, - ); - } catch (err) { - err.projectName = project.name; + logger( + `Building for ${mode === 'cjs' + ? 'CommonJS' + : 'ES Modules'} with Babel`, + ); + return babel( + { + mode, + path: project.path, + src: `${project.path}/${settings.input}`, + out: multipleTargets + ? `${project.path}/${settings.output}/${mode}` + : `${project.path}/${settings.output}`, + // We want to ignore potential __snapshots__ and __mocks__ directories + ignore: settings.test.concat([ + '**/__snapshots__/**', + '**/__mocks__/**', + ]), + copyFiles: true, + sourceMaps: true, + babelrc: false, + watch, + log: logger, + }, + babelConfig, + ).catch(err => { + err.projectName = project.name; // eslint-disable-line no-param-reassign if (err._babel && err instanceof SyntaxError) { // Display codeFrame if it is an Babel Error - err.message = `${err.message}\n${err.codeFrame}`; + err.message = `${err.message}\n${err.codeFrame}`; // eslint-disable-line no-param-reassign } throw err; - } + }); }), ); } @@ -69,5 +71,14 @@ export default (context, projects, { options: { watch }, createLogger }) => { cjsJavaScriptBuild.length > 0, ); } + + if ( + watch && + (esmJavaScriptBuild.length > 0 || cjsJavaScriptBuild.length > 0) + ) { + return { watch: true }; + } + + return undefined; }; }; diff --git a/extensions/roc-plugin-repo/src/commands/exec.js b/extensions/roc-plugin-repo/src/commands/exec.js index 31bc684..837e8ef 100644 --- a/extensions/roc-plugin-repo/src/commands/exec.js +++ b/extensions/roc-plugin-repo/src/commands/exec.js @@ -1,11 +1,11 @@ -import execa from 'execa'; import log from 'roc/log/default/small'; -import Listr from 'listr'; -import isCI from 'is-ci'; + +import run from './utils/run'; export default projects => ({ + context, arguments: { managed: { projects: selectedProjects } }, - options: { managed: { silent, concurrent } }, + options: { managed: { concurrent } }, extraArguments, }) => { const selected = projects.filter( @@ -22,15 +22,9 @@ export default projects => ({ return log.warn('No command was given'); } - return new Listr( - selected.map(project => ({ - title: `Running "${command}" in ${project.name}`, - task: () => - execa.shell(command, { - stdout: silent ? undefined : 'inherit', - cwd: project.path, - }), - })), - { concurrent, renderer: isCI ? 'verbose' : 'default' }, - ).run(); + return run(selected, { + command, + concurrent, + isMonorepo: !!context.config.settings.repo.mono, + })(); }; diff --git a/extensions/roc-plugin-repo/src/commands/run.js b/extensions/roc-plugin-repo/src/commands/run.js index f63b280..3f5bdbe 100644 --- a/extensions/roc-plugin-repo/src/commands/run.js +++ b/extensions/roc-plugin-repo/src/commands/run.js @@ -1,7 +1,6 @@ -import execa from 'execa'; import log from 'roc/log/default/small'; -import Listr from 'listr'; -import isCI from 'is-ci'; + +import run from './utils/run'; function makeList(elements) { return `${elements.map(element => ` - ${element}`).join('\n')}\n`; @@ -68,14 +67,10 @@ export default projects => ({ return log.warn('Nothing matched the selected script'); } - return new Listr( - toRun.map(project => ({ - title: `Running "${command}" using ${binary} in ${project.name}`, - task: () => - execa(binary, ['run', command].concat(extraArguments), { - cwd: project.path, - }), - })), - { concurrent, renderer: isCI ? 'verbose' : 'default' }, - ).run(); + return run(toRun, { + binary, + command: ['run', command].concat(extraArguments), + concurrent, + isMonorepo: !!context.config.settings.repo.mono, + })(); }; diff --git a/extensions/roc-plugin-repo/src/commands/utils/run.js b/extensions/roc-plugin-repo/src/commands/utils/run.js new file mode 100644 index 0000000..44d87f9 --- /dev/null +++ b/extensions/roc-plugin-repo/src/commands/utils/run.js @@ -0,0 +1,84 @@ +import chalk from 'chalk'; +import execa from 'execa'; +import log from 'roc/log/default'; +import logTransformer from 'strong-log-transformer'; +import pAll from 'p-all'; + +const colorWheel = ['cyan', 'magenta', 'blue', 'yellow', 'green', 'red']; +let index = 0; +const getTag = projectName => { + const color = chalk[colorWheel[index++ % colorWheel.length]]; // eslint-disable-line no-plusplus + const tag = `${color(projectName)}:`; + return tag; +}; + +export const createLogger = projectName => { + const tag = getTag(projectName); + return { + logger: (msg, error) => log.small.log(`${tag} ${msg}`, error), + tag, + }; +}; + +export default function run( + projects, + { binary, command, concurrent = 1, isMonorepo = false }, +) { + const commandString = binary ? `${binary} ${command.join(' ')}` : command; + + return () => + pAll( + projects.map(project => () => { + const tag = getTag(project.name); + const prefixedStdout = logTransformer({ + tag, + }); + const prefixedStderr = logTransformer({ + tag, + mergeMultiline: true, + }); + log.small.log(`${isMonorepo ? `${tag} ` : ''}${commandString}`); + + const execaOptions = { + cwd: project.path, + env: { + FORCE_COLOR: true, + }, + }; + + const child = binary + ? execa(binary, command, execaOptions) + : execa.shell(command, execaOptions); + + if (isMonorepo) { + if (projects.length > process.stdout.listenerCount('close')) { + process.stdout.setMaxListeners(projects.length); + process.stderr.setMaxListeners(projects.length); + } + + child.stdout.pipe(prefixedStdout).pipe(process.stdout); + child.stderr.pipe(prefixedStderr).pipe(process.stderr); + } else { + child.stdout.pipe(process.stdout); + child.stderr.pipe(process.stderr); + } + + return child.catch(error => { + error.message = error.stderr; // eslint-disable-line no-param-reassign + error.projectName = project.name; // eslint-disable-line no-param-reassign + throw error; + }); + }), + { concurrency: concurrent }, + ).catch(error => { + if (error.projectName) { + log.large.error( + `Problem when running "${commandString}"`, + error.projectName || error, + error, + ); + } else { + log.large.error(`Problem when running "${commandString}"`, error); + } + }); +} diff --git a/extensions/roc-plugin-repo/src/commands/utils/scriptRunner.js b/extensions/roc-plugin-repo/src/commands/utils/scriptRunner.js index 7712c30..002e63f 100644 --- a/extensions/roc-plugin-repo/src/commands/utils/scriptRunner.js +++ b/extensions/roc-plugin-repo/src/commands/utils/scriptRunner.js @@ -1,11 +1,11 @@ -import execa from 'execa'; import chalk from 'chalk'; import log from 'roc/log/default'; import pAll from 'p-all'; -import logTransformer from 'strong-log-transformer'; import { invokeHook } from '../../util'; +import run, { createLogger } from './run'; + export default function scriptRunner(script) { const scriptName = script.charAt(0).toUpperCase() + script.slice(1); @@ -14,8 +14,9 @@ export default function scriptRunner(script) { settings, concurrent = 1, options = {}, - extraArguments, + extraArguments = [], ) => { + const isMonorepo = !!settings.mono; const selected = selectedProjects.filter( // Remove projects that have disabled a command, setting to `"build": false` for example ({ name, rawPackageJSON }) => { @@ -36,102 +37,77 @@ export default function scriptRunner(script) { // Projects with custom scripts const projectsWithCustomScript = selected.filter( - ({ packageJSON }) => packageJSON.scripts && packageJSON.scripts[script], + ({ packageJSON }) => + packageJSON.scripts && + packageJSON.scripts[script] && + // This check makes sure that we don't add the project again if we launched + // it with the same script through npm, only for normal repositories + (process.env.npm_lifecycle_event !== script && !isMonorepo), ); // Projects without custom scripts const projectsWithoutCustomScript = selected.filter( - ({ packageJSON }) => !packageJSON.scripts || !packageJSON.scripts[script], + ({ packageJSON }) => + !packageJSON.scripts || + !packageJSON.scripts[script] || + // This check makes sure that we add it even if it is has a script + // since we otherwise would miss it, only for normal repositories + (process.env.npm_lifecycle_event === script && !isMonorepo), ); - const colorWheel = ['cyan', 'magenta', 'blue', 'yellow', 'green', 'red']; let tasks = []; - let index = 0; - const createLogger = projectName => { - const color = chalk[colorWheel[index++ % colorWheel.length]]; // eslint-disable-line no-plusplus - const tag = `${color(projectName)}:`; - return { - logger: (msg, error) => log.small.log(`${tag} ${msg}`, error), - tag, - }; - }; - - // We don't want to run "repo XXXX" again if we are in a non-monorepo - // and we where already launched using the "XXXX" npm script. - // This because we would create a loop otherwise - if ( - projectsWithCustomScript.length !== 0 && - (!!settings.mono || process.env.npm_lifecycle_event !== script) - ) { - const children = projectsWithCustomScript.length; + if (projectsWithCustomScript.length !== 0) { tasks = tasks.concat( - projectsWithCustomScript.map(project => () => { - const tag = createLogger(project.name).tag; - const prefixedStdout = logTransformer({ - tag, - }); - const prefixedStderr = logTransformer({ - tag, - mergeMultiline: true, - }); - - // TODO Should we pass in the arguments here, like watch? - log.small.log(`${tag} Using custom script for ${script}`); - const child = execa(settings.npmBinary, ['run', script], { - cwd: project.path, - }); - - // Avoid "Possible EventEmitter memory leak detected" warning due to piped stdio - if (children > process.stdout.listenerCount('close')) { - process.stdout.setMaxListeners(children); - process.stderr.setMaxListeners(children); - } - - child.stdout.pipe(prefixedStdout).pipe(process.stdout); - child.stderr.pipe(prefixedStderr).pipe(process.stderr); - - // Return the child process and enhance potential errors - return child.catch(error => { - error.message = error.stderr; // eslint-disable-line no-param-reassign - error.projectName = project.name; // eslint-disable-line no-param-reassign - throw error; - }); + run(projectsWithCustomScript, { + binary: settings.npmBinary, + command: ['run', script].concat(extraArguments), + concurrent, + isMonorepo, }), ); } // Allow integrations for different kinds of projects - invokeHook('run-script', script, projectsWithoutCustomScript, { - options, - extraArguments, - createLogger, - })(task => { - tasks = tasks.concat(task); - }); + if (projectsWithoutCustomScript.length > 0) { + let rocTasks = []; + invokeHook('run-script', script, projectsWithoutCustomScript, { + options, + extraArguments, + createLogger, + })(task => { + rocTasks = rocTasks.concat(task); + }); - return pAll(tasks, { concurrency: concurrent || 1 }) - .then(() => { - if (process.exitCode === 1) { - log.small.error(`${scriptName} failed.`); - } + tasks.push(() => + pAll(rocTasks, { concurrency: concurrent || 1 }) + .then(results => { + // Flatten result and see if any of the jobs have enabled watch: true + const watchMode = [] + .concat(...results) + .some(({ watch } = {}) => watch); + + if (process.exitCode >= 1) { + log.small.error(`${scriptName} failed.`); + } else if (!watchMode) { + log.small.log(); // Create a new line for padding purposes + log.small.success(`${scriptName} succeeded.`); + } + }) + .catch(error => { + if (error.projectName) { + log.large.error( + `${scriptName} Problem`, + error.projectName || error, + error, + ); + } else { + log.large.error(`${scriptName} Problem`, error); + } + }), + ); + } - // Special handling of watch mode - if (!options.watch) { - log.small.log(); // Create a new line for padding purposes - log.small.success(`${scriptName} succeeded.`); - } - }) - .catch(error => { - if (error.projectName) { - log.large.error( - `${scriptName} Problem`, - error.projectName || error, - error, - ); - } else { - log.large.error(`${scriptName} Problem`, error); - } - }); + return pAll(tasks, { concurrency: 1 }); }; } diff --git a/extensions/roc-plugin-repo/src/index.js b/extensions/roc-plugin-repo/src/index.js index ed92bef..62da743 100644 --- a/extensions/roc-plugin-repo/src/index.js +++ b/extensions/roc-plugin-repo/src/index.js @@ -310,11 +310,6 @@ module.exports.roc = { }, }, options: { - silent: { - validator: validators.isBoolean, - description: 'Silent output', - default: true, - }, concurrent: { validator: validators.oneOf( validators.isBoolean,