132 lines
		
	
	
		
			3.7 KiB
		
	
	
	
		
			JavaScript
		
	
	
		
			Executable File
		
	
			
		
		
	
	
			132 lines
		
	
	
		
			3.7 KiB
		
	
	
	
		
			JavaScript
		
	
	
		
			Executable File
		
	
#!/usr/bin/env node
 | 
						|
const fs = require('fs/promises');
 | 
						|
const path = require('path');
 | 
						|
 | 
						|
/**
 | 
						|
 * Unsophisticated script to create a styled, unit-tested react component.
 | 
						|
 * -filePath / -f : path to the component to be created, including new component name, excluding extension, relative to src
 | 
						|
 * -withStyle / -s : optional, flag to create a style file for the component. Defaults to false.
 | 
						|
 *
 | 
						|
 * eg:
 | 
						|
 * ```
 | 
						|
 * node srcipts/make-react-component.js -f components/toasts/NewToast -s
 | 
						|
 * ```
 | 
						|
 * creates files:
 | 
						|
 * - src/components/toasts/NewToast.tsx
 | 
						|
 * - test/components/toasts/NewToast-test.tsx
 | 
						|
 * - res/css/components/toasts/_NewToast.scss
 | 
						|
 *
 | 
						|
 */
 | 
						|
 | 
						|
const TEMPLATES = {
 | 
						|
    COMPONENT: `
 | 
						|
import React from 'react';
 | 
						|
 | 
						|
interface Props {}
 | 
						|
 | 
						|
const %%ComponentName%%: React.FC<Props> = () => {
 | 
						|
    return <div className='mx_%%ComponentName%%' />;
 | 
						|
};
 | 
						|
 | 
						|
export default %%ComponentName%%;
 | 
						|
`,
 | 
						|
    TEST: `
 | 
						|
import React from 'react';
 | 
						|
import { mount } from 'enzyme';
 | 
						|
 | 
						|
import %%ComponentName%% from '%%RelativeComponentPath%%';
 | 
						|
 | 
						|
describe('<%%ComponentName%% />', () => {
 | 
						|
    const defaultProps = {};
 | 
						|
    const getComponent = (props = {}) =>
 | 
						|
        mount(<%%ComponentName%% {...defaultProps} {...props} />);
 | 
						|
 | 
						|
    it('renders', () => {
 | 
						|
        const component = getComponent();
 | 
						|
        expect(component).toBeTruthy();
 | 
						|
    });
 | 
						|
});
 | 
						|
`,
 | 
						|
    STYLE: `
 | 
						|
.mx_%%ComponentName%% {
 | 
						|
 | 
						|
}
 | 
						|
`
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
const options = {
 | 
						|
    alias: {
 | 
						|
        filePath: 'f',
 | 
						|
        withStyle: 's'
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
const args = require('minimist')(process.argv, options);
 | 
						|
 | 
						|
const ensureDirectoryExists = async (filePath) => {
 | 
						|
    const dirName = path.parse(filePath).dir;
 | 
						|
 | 
						|
    try {
 | 
						|
        await fs.access(dirName);
 | 
						|
        return;
 | 
						|
    } catch (error) { }
 | 
						|
 | 
						|
    await fs.mkdir(dirName, { recursive: true })
 | 
						|
}
 | 
						|
 | 
						|
const makeFile = async ({
 | 
						|
    filePath, componentName, extension, base, template, prefix, componentFilePath
 | 
						|
}) => {
 | 
						|
    const newFilePath = path.join(base, path.dirname(filePath), `${prefix || ''}${path.basename(filePath)}${extension}`)
 | 
						|
    await ensureDirectoryExists(newFilePath);
 | 
						|
 | 
						|
    const relativePathToComponent = path.parse(path.relative(path.dirname(newFilePath), componentFilePath || ''));
 | 
						|
    const importComponentPath = path.join(relativePathToComponent.dir, relativePathToComponent.name);
 | 
						|
 | 
						|
    try {
 | 
						|
        await fs.writeFile(newFilePath, fillTemplate(template, componentName, importComponentPath), { flag: 'wx' });
 | 
						|
        console.log(`Created ${path.relative(process.cwd(), newFilePath)}`);
 | 
						|
        return newFilePath;
 | 
						|
    } catch (error) {
 | 
						|
        if (error.code === 'EEXIST') {
 | 
						|
            console.log(`File already exists ${path.relative(process.cwd(), newFilePath)}`);
 | 
						|
            return newFilePath;
 | 
						|
        } else {
 | 
						|
            throw error;
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
const fillTemplate = (template, componentName, relativeComponentFilePath, skinnedSdkPath) =>
 | 
						|
    template.replace(/%%ComponentName%%/g, componentName)
 | 
						|
        .replace(/%%RelativeComponentPath%%/g, relativeComponentFilePath)
 | 
						|
 | 
						|
 | 
						|
const makeReactComponent = async () => {
 | 
						|
    const { filePath, withStyle } = args;
 | 
						|
 | 
						|
    if (!filePath) {
 | 
						|
        throw new Error('No file path provided, did you forget -f?')
 | 
						|
    }
 | 
						|
 | 
						|
    const componentName = filePath.split('/').slice(-1).pop();
 | 
						|
 | 
						|
    const componentFilePath = await makeFile({ filePath, componentName, base: 'src', extension: '.tsx', template: TEMPLATES.COMPONENT });
 | 
						|
    await makeFile({ filePath, componentFilePath, componentName, base: 'test', extension: '-test.tsx', template: TEMPLATES.TEST, componentName });
 | 
						|
    if (withStyle) {
 | 
						|
        await makeFile({ filePath, componentName, base: 'res/css', prefix: '_', extension: '.scss', template: TEMPLATES.STYLE });
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
// Wrapper since await at the top level is not well supported yet
 | 
						|
function run() {
 | 
						|
    (async function () {
 | 
						|
        await makeReactComponent();
 | 
						|
    })();
 | 
						|
}
 | 
						|
 | 
						|
run();
 | 
						|
return;
 | 
						|
 |