Skip to content
This repository was archived by the owner on Apr 26, 2022. It is now read-only.

allow to execute subactions #58

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 72 additions & 36 deletions src/generator-runner.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,52 +5,55 @@ 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') {
return yield prompts(plopfileApi.inquirer);
}

// 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)
.then(answers => Object.assign(answers, bypassAnswers));
});

// 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()) {
Expand All @@ -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 || '',
Expand All @@ -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);
}
}
Expand All @@ -92,38 +103,63 @@ 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());
}
);
});

// 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 };
};
}
Expand Down
2 changes: 2 additions & 0 deletions tests/sub-actions-mock/plop-templates/component.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@

// this is a component named {{name}}
4 changes: 4 additions & 0 deletions tests/sub-actions-mock/plop-templates/component.story.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@

import {{name}} from './{{name}}.js'
// this is a storybook file for a component named {{name}}
// (https://storybook.js.org/)
2 changes: 2 additions & 0 deletions tests/sub-actions-mock/plop-templates/component.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import {{name}} from './{{name}}.js'
// this is a testfile for component named {{name}}
47 changes: 47 additions & 0 deletions tests/sub-actions-mock/plopfile.js
Original file line number Diff line number Diff line change
@@ -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'
}
]
});
};
24 changes: 24 additions & 0 deletions tests/sub-actions.ava.js
Original file line number Diff line number Diff line change
@@ -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);

});