Skip to content

Instantly share code, notes, and snippets.

@hallamoore
Created January 25, 2018 15:43
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save hallamoore/bf4e18e64cca1b9e45bf69901e56ee43 to your computer and use it in GitHub Desktop.
Save hallamoore/bf4e18e64cca1b9e45bf69901e56ee43 to your computer and use it in GitHub Desktop.
A copy of the scaffold script mentioned in https://dev.to/nylas/raising-the-limits-on-developer-speed-4dgl
#!/usr/bin/env node
/* eslint flowtype/require-valid-file-annotation: 0 */
const program = require('commander');
const mkdirp = require('mkdirp');
const fs = require('fs');
const errors = [];
const statefulComponentTemplate = (componentName, { container }) =>
`class ${componentName} extends Component {
${container
? `props: {
dispatch: Dispatch,
};
`
: ''}
render() {
return <div className={css(styleSheet.${componentName})} />;
}
}`;
const statelessComponentTemplate = (componentName, { container }) =>
`function ${componentName}(${container
? '{ dispatch }: { dispatch: Dispatch }'
: ''}) {
return <div className={css(styleSheet.${componentName})} />;
}`;
const componentTemplate = (componentName, { stateful, container }) =>
`// @flow
import React${stateful ? ', { Component }' : ''} from 'react';${container
? `
import { connect } from 'react-redux';
import * as selectors from 'store/selectors';
import type { Dispatch } from 'store/types';`
: ''}
import { StyleSheet, css } from 'aphrodite/no-important';
const styleSheet = StyleSheet.create({
${componentName}: {},
});
${container
? `
function mapStateToProps(state) {
return {
// e.g.
// key: selectors.getData(state),
};
}`
: ''}
${container ? 'export ' : 'export default '}${stateful
? statefulComponentTemplate(componentName, { container })
: statelessComponentTemplate(componentName, { container })}${container
? `
export default connect(mapStateToProps)(${componentName});`
: ''}`;
const componentTestTemplate = (componentName, { container }) =>
`// @flow
import React from 'react';
import { shallow } from 'enzyme';
import ${container ? `{ ${componentName} }` : componentName} from './index';
test('renders as expected', () => {
const component = shallow(<${componentName} />);
expect(component).toMatchSnapshot();
});
`;
const storeActionsTemplate = () =>
`// @flow
import createAction from 'store/createAction';
import type { Action, ActionCreator } from 'store/types';
export const exampleSyncAction: ({ arg: any }) => Action = createAction(
'EXAMPLE_SYNC_ACTION'
);
export const exampleAsyncAction: ActionCreator<{arg: any}> = ({ arg }) => {
return async (dispatch, getState, api) => {
// ...
};
}
`;
const storeEndpointsTemplate = ({ lowerCaseName, capitalizedName }) =>
`// @flow
import apiRequest from 'modules/apiRequest';
export async function fetch${capitalizedName}(data: any): Promise<any> {
const result = await apiRequest('/${lowerCaseName}', {
method: 'GET',
});
return result;
}
`;
const storeReducersTemplate = ({ lowerCaseName }) =>
`// @flow
import type { Action } from 'store/types';
import { exampleSyncAction } from './actions';
export type State = {
// TODO define shape of state
// e.g.
// items: Array<string>,
};
const initialState: State = {
// TODO define intial state
// e.g.
// items: [],
}
const actionHandlers = {
[exampleSyncAction.name](state: State, action): State {
const nextState = state;
return nextState;
},
};
export default function mainReducer(state: State = initialState, action: Action) {
const { type } = action;
const actionHandler = actionHandlers[type];
if (actionHandler) {
return actionHandler(state, action);
}
return state;
}
`;
const storeConstantsTemplate = ({ lowerCaseName }) =>
`// @flow
export const STATE_KEY = '${lowerCaseName}';
`;
const storeSelectorsTemplate = ({ capitalizedName }) =>
`// @flow
import type { State } from 'store/types';
import { STATE_KEY } from './constants';
export function get${capitalizedName}(state: State): any {
return state[STATE_KEY];
}
`;
const modelTemplate = name =>
`// @flow
import { BaseModel } from './BaseModel';
export type ${name}JSONData = {};
export type ${name}Data = {};
export default class ${name} implements BaseModel<${name}, ${name}JSONData, ${name}Data> {
static fromJSON(json: ${name}JSONData = {}): ${name} {
return new ${name}({});
}
constructor(data: ${name}Data = {}) {
Object.keys(data).forEach(key => {
// $FlowFixMe
this[key] = data[key];
});
}
update(data: ${name}Data = {}): ${name} {
return new ${name}({
...this,
...data,
});
}
toJSON(): ${name}JSONData {
return {};
}
}`;
function writeComponent(
subdir,
name,
{ stateful = false, container = false } = {}
) {
try {
const capitalizedName = `${name[0].toUpperCase()}${name.slice(1)}`;
let compName = capitalizedName;
if (subdir === 'screens' && !name.endsWith('Screen')) {
compName = `${name}Screen`;
}
if (subdir === 'layouts' && !name.endsWith('Layout')) {
compName = `${name}Layout`;
}
const dir = `src/${subdir}/${compName}`;
const componentOutput = componentTemplate(compName, {
stateful,
container,
});
const componentTestOutput = componentTestTemplate(compName, {
stateful,
container,
});
mkdirp.sync(dir);
fs.writeFileSync(`${dir}/index.js`, componentOutput);
fs.writeFileSync(`${dir}/index.test.js`, componentTestOutput);
} catch (error) {
errors.push({ name, error });
}
}
function writeStore(name, options) {
try {
const capitalizedName = `${name[0].toUpperCase()}${name.slice(1)}`;
const lowerCaseName = `${name[0].toLowerCase()}${name.slice(1)}`;
const dir = `src/store/${lowerCaseName}`;
const actionsOutput = storeActionsTemplate({
capitalizedName,
lowerCaseName,
});
const endpointsOutput = storeEndpointsTemplate({
capitalizedName,
lowerCaseName,
});
const reducersOutput = storeReducersTemplate({
capitalizedName,
lowerCaseName,
});
const selectorsOutput = storeSelectorsTemplate({
capitalizedName,
lowerCaseName,
});
const constantsOutput = storeConstantsTemplate({
capitalizedName,
lowerCaseName,
});
mkdirp.sync(dir);
fs.writeFileSync(`${dir}/actions.js`, actionsOutput);
fs.writeFileSync(`${dir}/endpoints.js`, endpointsOutput);
fs.writeFileSync(`${dir}/reducers.js`, reducersOutput);
fs.writeFileSync(`${dir}/selectors.js`, selectorsOutput);
fs.writeFileSync(`${dir}/constants.js`, constantsOutput);
} catch (error) {
errors.push({ name, error });
}
}
function writeModel(name, options) {
try {
const capitalizedName = `${name[0].toUpperCase()}${name.slice(1)}`;
const dir = `src/models`;
const modelOutput = modelTemplate(capitalizedName);
mkdirp.sync(dir);
fs.writeFileSync(`${dir}/${capitalizedName}.js`, modelOutput);
} catch (error) {
errors.push({ name, error });
}
}
function writeComponents(subdir, names, options) {
names.forEach(n => writeComponent(subdir, n, options));
}
function writeStores(names, options) {
names.forEach(n => writeStore(n, options));
}
function writeModels(names, options) {
names.forEach(n => writeModel(n, options));
}
function main(cmd, names, options) {
switch (cmd) {
case 'store': {
writeStores(names, options);
break;
}
case 'component': {
writeComponents('components', names, options);
break;
}
case 'layout': {
writeComponents('layouts', names, options);
break;
}
case 'screen': {
writeComponents(
'screens',
names,
Object.assign(options, { container: true })
);
break;
}
case 'container': {
writeComponents(
'containers',
names,
Object.assign(options, { container: true })
);
break;
}
case 'model': {
writeModels(names, options);
break;
}
default:
console.error('Unknown command!');
process.exit(1);
}
if (errors.length > 0) {
const errorStr = `Failed to create the following ${cmd}s:
${errors.map(({ name, error }) => `${name}: ${error.message}`).join('\n')}`;
console.error(errorStr);
}
}
let cmdValue;
let namesArray;
program
.version('0.0.1')
.arguments('<cmd> [names...]')
.option('--stateful', 'create a stateful component (extends Component)')
.action(function(cmd, names) {
cmdValue = cmd;
namesArray = names;
})
.parse(process.argv);
if (typeof cmdValue === 'undefined') {
console.error('No command given!');
process.exit(1);
}
main(cmdValue, namesArray, {
stateful: program.stateful,
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment