diff --git a/src/generator-runner.js b/src/generator-runner.js index b45e791..9094cc5 100644 --- a/src/generator-runner.js +++ b/src/generator-runner.js @@ -5,17 +5,16 @@ import colors from 'colors'; import promptBypass from './prompt-bypass'; import * as buildInActions from './actions'; - -export default function (plopfileApi, flags) { +export default function(plopfileApi, flags) { let abort; // triggers inquirer with the correct prompts for this generator // returns a promise that resolves with the user's answers - const runGeneratorPrompts = co.wrap(function* (genObject, bypassArr = []) { - const {prompts} = genObject; + const runGeneratorPrompts = co.wrap(function*(genObject, bypassArr = []) { + const { prompts, name } = genObject; if (prompts == null) { - throw Error(`${genObject.name} has no prompts`); + throw Error(`${name} has no prompts`); } if (typeof prompts === 'function') { @@ -23,7 +22,11 @@ export default function (plopfileApi, flags) { } // handle bypass data when provided - const [promptsAfterBypass, bypassAnswers] = promptBypass(prompts, bypassArr, plopfileApi); + const [promptsAfterBypass, bypassAnswers] = promptBypass( + prompts, + bypassArr, + plopfileApi + ); return yield plopfileApi.inquirer .prompt(promptsAfterBypass) @@ -31,26 +34,26 @@ export default function (plopfileApi, flags) { }); // Run the actions for this generator - const runGeneratorActions = co.wrap(function* (genObject, data) { - var changes = []; // array of changed made by the actions - var failures = []; // array of actions that failed - var {actions} = genObject; // the list of actions to execute + const runGeneratorActions = co.wrap(function*({ actions, name }, data) { + var changes = []; // array of changed made by the actions + var failures = []; // array of actions that failed const customActionTypes = getCustomActionTypes(); const actionTypes = Object.assign({}, customActionTypes, buildInActions); - abort = false; // if action is a function, run it to get our array of actions - if (typeof actions === 'function') { actions = actions(data); } + if (typeof actions === 'function') { + actions = actions(data); + } // if actions are not defined... we cannot proceed. if (actions == null) { - throw Error(`${genObject.name} has no actions`); + throw Error(`${name} has no actions`); } // if actions are not an array, invalid! if (!(actions instanceof Array)) { - throw Error(`${genObject.name} has invalid actions`); + throw Error(`${name} has invalid actions`); } for (let [actionIdx, action] of actions.entries()) { @@ -64,14 +67,18 @@ export default function (plopfileApi, flags) { continue; } - action.force = (flags.force === true || action.force === true); + action.force = flags.force === true || action.force === true; const actionIsFunction = typeof action === 'function'; - const actionCfg = (actionIsFunction ? {} : action); - const actionLogic = (actionIsFunction ? action : actionTypes[actionCfg.type]); + const actionCfg = actionIsFunction ? {} : action; + const actionLogic = actionIsFunction + ? action + : actionTypes[actionCfg.type]; if (typeof actionLogic !== 'function') { - if (actionCfg.abortOnFail !== false) { abort = true; } + if (actionCfg.abortOnFail !== false) { + abort = true; + } failures.push({ type: action.type || '', path: action.path || '', @@ -81,9 +88,13 @@ export default function (plopfileApi, flags) { } try { - const actionResult = yield executeActionLogic(actionLogic, actionCfg, data); - changes.push(actionResult); - } catch(failure) { + const actionResults = yield executeActionLogic( + actionLogic, + actionCfg, + data + ); + changes.push(...actionResults); + } catch (failure) { failures.push(failure); } } @@ -92,20 +103,44 @@ export default function (plopfileApi, flags) { }); // handle action logic - const executeActionLogic = co.wrap(function* (action, cfg, data) { - const failure = makeErrorLogger(cfg.type || 'function', '', cfg.abortOnFail); + const executeActionLogic = co.wrap(function*(action, cfg, data) { + const failure = makeErrorLogger( + cfg.type || 'function', + '', + cfg.abortOnFail + ); + const actionApi = Object.assign({}, plopfileApi, { + runActions: co.wrap(function*(actions, data) { + const { changes, failures } = yield runGeneratorActions( + { actions, name: cfg.type }, + data + ); + if (failures.length > 0) { + // FIXME: error should always be error Object + // the logger should do the proper formatting + throw failures.map(f => '\n ->\t' + f.error.toString()); + } + + return changes; + }) + }); // convert any returned data into a promise to // return and wait on const fullData = Object.assign({}, cfg.data, data); - return yield Promise.resolve(action(fullData, cfg, plopfileApi)).then( + return yield Promise.resolve(action(fullData, cfg, actionApi)).then( // show the resolved value in the console - result => ({ - type: cfg.type || 'function', - path: colors.blue(result.toString()) - }), + result => + Array.isArray(result) + ? result + : [ + { + type: cfg.type || 'function', + path: colors.blue(result.toString()) + } + ], // a rejected promise is treated as a failure - function (err) { + function(err) { throw failure(err.message || err.toString()); } ); @@ -113,17 +148,18 @@ export default function (plopfileApi, flags) { // request the list of custom actions from the plopfile function getCustomActionTypes() { - return plopfileApi.getActionTypeList() - .reduce(function (types, name) { - types[name] = plopfileApi.getActionType(name); - return types; - }, {}); + return plopfileApi.getActionTypeList().reduce(function(types, name) { + types[name] = plopfileApi.getActionType(name); + return types; + }, {}); } // provide a function to handle action errors in a uniform way function makeErrorLogger(type, path, abortOnFail) { - return function (error) { - if (abortOnFail !== false) { abort = true; } + return function(error) { + if (abortOnFail !== false) { + abort = true; + } return { type, path, error }; }; } diff --git a/tests/sub-actions-mock/plop-templates/component.js b/tests/sub-actions-mock/plop-templates/component.js new file mode 100644 index 0000000..49646b0 --- /dev/null +++ b/tests/sub-actions-mock/plop-templates/component.js @@ -0,0 +1,2 @@ + +// this is a component named {{name}} diff --git a/tests/sub-actions-mock/plop-templates/component.story.js b/tests/sub-actions-mock/plop-templates/component.story.js new file mode 100644 index 0000000..bf73bb7 --- /dev/null +++ b/tests/sub-actions-mock/plop-templates/component.story.js @@ -0,0 +1,4 @@ + +import {{name}} from './{{name}}.js' +// this is a storybook file for a component named {{name}} +// (https://storybook.js.org/) diff --git a/tests/sub-actions-mock/plop-templates/component.test.js b/tests/sub-actions-mock/plop-templates/component.test.js new file mode 100644 index 0000000..8527115 --- /dev/null +++ b/tests/sub-actions-mock/plop-templates/component.test.js @@ -0,0 +1,2 @@ +import {{name}} from './{{name}}.js' +// this is a testfile for component named {{name}} diff --git a/tests/sub-actions-mock/plopfile.js b/tests/sub-actions-mock/plopfile.js new file mode 100644 index 0000000..8ee7d3f --- /dev/null +++ b/tests/sub-actions-mock/plopfile.js @@ -0,0 +1,47 @@ +module.exports = function(plop) { + 'use strict'; + + plop.setActionType( + 'component-with-tests-and-stories', + (answers, config, { runActions }) => { + return runActions( + [ + { + type: 'add', + path: './src/components/{{name}}.js', + templateFile: config.componentTemplate + }, + { + type: 'add', + path: './src/components/stories/{{name}}.story.js', + templateFile: config.storyTemplate + }, + { + type: 'add', + path: './src/components/tests/{{name}}.test.js', + templateFile: config.testTemplate + } + ], + answers + ); + } + ); + + plop.setGenerator('component', { + prompts: [ + { + type: 'input', + name: 'name', + message: 'What\'s the component name?' + } + ], + actions: [ + { + type: 'component-with-tests-and-stories', + componentTemplate: 'plop-templates/component.js', + testTemplate: 'plop-templates/component.test.js', + storyTemplate: 'plop-templates/component.story.js' + } + ] + }); +}; diff --git a/tests/sub-actions.ava.js b/tests/sub-actions.ava.js new file mode 100644 index 0000000..c76b887 --- /dev/null +++ b/tests/sub-actions.ava.js @@ -0,0 +1,24 @@ +import fs from 'fs'; +import path from 'path'; +import AvaTest from './_base-ava-test'; +const {test, mockPath, testSrcPath, nodePlop} = (new AvaTest(__filename)); + +const plop = nodePlop(`${mockPath}/plopfile.js`); +const componentGenerator = plop.getGenerator('component'); + +test.before(() => { + return componentGenerator.runActions({name: 'Header'}); +}); + +test('Check that all three files have been created', t => { + const componentFilePath = path.resolve(testSrcPath, 'components/Header.js'); + const componentTestFilePath = path.resolve(testSrcPath, 'components/tests/Header.test.js'); + const componentStoryFilePath = path.resolve(testSrcPath, 'components/stories/Header.story.js'); + + + // both files should have been created + t.true(fs.existsSync(componentFilePath), componentFilePath); + t.true(fs.existsSync(componentTestFilePath), componentTestFilePath); + t.true(fs.existsSync(componentStoryFilePath), componentStoryFilePath); + +});