Created
January 25, 2018 15:43
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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