2022-02-09 10:33:21 +01:00
|
|
|
#!/usr/bin/env node
|
2022-12-12 12:24:14 +01:00
|
|
|
const fs = require("fs/promises");
|
|
|
|
const path = require("path");
|
2022-02-09 10:33:21 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* 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.
|
2022-03-23 06:22:53 +01:00
|
|
|
*
|
2022-02-09 10:33:21 +01:00
|
|
|
* 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
|
2022-07-15 15:53:23 +02:00
|
|
|
* - res/css/components/toasts/_NewToast.pcss
|
2022-03-23 06:22:53 +01:00
|
|
|
*
|
2022-02-09 10:33:21 +01:00
|
|
|
*/
|
|
|
|
|
|
|
|
const TEMPLATES = {
|
|
|
|
COMPONENT: `
|
|
|
|
import React from 'react';
|
|
|
|
|
|
|
|
interface Props {}
|
|
|
|
|
|
|
|
const %%ComponentName%%: React.FC<Props> = () => {
|
|
|
|
return <div className='mx_%%ComponentName%%' />;
|
|
|
|
};
|
|
|
|
|
|
|
|
export default %%ComponentName%%;
|
|
|
|
`,
|
|
|
|
TEST: `
|
2023-03-06 13:13:17 +01:00
|
|
|
import React from "react";
|
2024-10-21 15:50:06 +02:00
|
|
|
import { render } from "jest-matrix-react";
|
2022-02-09 10:33:21 +01:00
|
|
|
|
|
|
|
import %%ComponentName%% from '%%RelativeComponentPath%%';
|
|
|
|
|
2023-03-06 13:13:17 +01:00
|
|
|
describe("<%%ComponentName%% />", () => {
|
2022-02-09 10:33:21 +01:00
|
|
|
const defaultProps = {};
|
|
|
|
const getComponent = (props = {}) =>
|
2023-03-06 13:13:17 +01:00
|
|
|
render(<%%ComponentName%% {...defaultProps} {...props} />);
|
2022-02-09 10:33:21 +01:00
|
|
|
|
2023-03-06 13:13:17 +01:00
|
|
|
it("matches snapshot", () => {
|
|
|
|
const { asFragment } = getComponent();
|
|
|
|
expect(asFragment()).toMatchSnapshot()();
|
2022-02-09 10:33:21 +01:00
|
|
|
});
|
|
|
|
});
|
|
|
|
`,
|
|
|
|
STYLE: `
|
|
|
|
.mx_%%ComponentName%% {
|
|
|
|
|
|
|
|
}
|
2022-12-12 12:24:14 +01:00
|
|
|
`,
|
|
|
|
};
|
2022-02-09 10:33:21 +01:00
|
|
|
|
|
|
|
const options = {
|
|
|
|
alias: {
|
2022-12-12 12:24:14 +01:00
|
|
|
filePath: "f",
|
|
|
|
withStyle: "s",
|
|
|
|
},
|
|
|
|
};
|
2022-02-09 10:33:21 +01:00
|
|
|
|
2022-12-12 12:24:14 +01:00
|
|
|
const args = require("minimist")(process.argv, options);
|
2022-02-09 10:33:21 +01:00
|
|
|
|
|
|
|
const ensureDirectoryExists = async (filePath) => {
|
|
|
|
const dirName = path.parse(filePath).dir;
|
|
|
|
|
|
|
|
try {
|
|
|
|
await fs.access(dirName);
|
|
|
|
return;
|
2022-12-12 12:24:14 +01:00
|
|
|
} catch (error) {}
|
2022-02-09 10:33:21 +01:00
|
|
|
|
2022-12-12 12:24:14 +01:00
|
|
|
await fs.mkdir(dirName, { recursive: true });
|
|
|
|
};
|
2022-02-09 10:33:21 +01:00
|
|
|
|
2022-12-12 12:24:14 +01:00
|
|
|
const makeFile = async ({ filePath, componentName, extension, base, template, prefix, componentFilePath }) => {
|
|
|
|
const newFilePath = path.join(
|
|
|
|
base,
|
|
|
|
path.dirname(filePath),
|
|
|
|
`${prefix || ""}${path.basename(filePath)}${extension}`,
|
|
|
|
);
|
2022-02-09 10:33:21 +01:00
|
|
|
await ensureDirectoryExists(newFilePath);
|
|
|
|
|
2022-12-12 12:24:14 +01:00
|
|
|
const relativePathToComponent = path.parse(path.relative(path.dirname(newFilePath), componentFilePath || ""));
|
2022-02-09 10:33:21 +01:00
|
|
|
const importComponentPath = path.join(relativePathToComponent.dir, relativePathToComponent.name);
|
|
|
|
|
|
|
|
try {
|
2022-12-12 12:24:14 +01:00
|
|
|
await fs.writeFile(newFilePath, fillTemplate(template, componentName, importComponentPath), { flag: "wx" });
|
2022-02-09 10:33:21 +01:00
|
|
|
console.log(`Created ${path.relative(process.cwd(), newFilePath)}`);
|
|
|
|
return newFilePath;
|
|
|
|
} catch (error) {
|
2022-12-12 12:24:14 +01:00
|
|
|
if (error.code === "EEXIST") {
|
2022-02-09 10:33:21 +01:00
|
|
|
console.log(`File already exists ${path.relative(process.cwd(), newFilePath)}`);
|
|
|
|
return newFilePath;
|
|
|
|
} else {
|
|
|
|
throw error;
|
|
|
|
}
|
|
|
|
}
|
2022-12-12 12:24:14 +01:00
|
|
|
};
|
2022-02-09 10:33:21 +01:00
|
|
|
|
|
|
|
const fillTemplate = (template, componentName, relativeComponentFilePath, skinnedSdkPath) =>
|
2022-12-12 12:24:14 +01:00
|
|
|
template
|
|
|
|
.replace(/%%ComponentName%%/g, componentName)
|
|
|
|
.replace(/%%RelativeComponentPath%%/g, relativeComponentFilePath);
|
2022-02-09 10:33:21 +01:00
|
|
|
|
|
|
|
const makeReactComponent = async () => {
|
|
|
|
const { filePath, withStyle } = args;
|
|
|
|
|
|
|
|
if (!filePath) {
|
2022-12-12 12:24:14 +01:00
|
|
|
throw new Error("No file path provided, did you forget -f?");
|
2022-02-09 10:33:21 +01:00
|
|
|
}
|
|
|
|
|
2022-12-12 12:24:14 +01:00
|
|
|
const componentName = filePath.split("/").slice(-1).pop();
|
2022-02-09 10:33:21 +01:00
|
|
|
|
2022-12-12 12:24:14 +01:00
|
|
|
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,
|
|
|
|
});
|
2022-02-09 10:33:21 +01:00
|
|
|
if (withStyle) {
|
2022-12-12 12:24:14 +01:00
|
|
|
await makeFile({
|
|
|
|
filePath,
|
|
|
|
componentName,
|
|
|
|
base: "res/css",
|
|
|
|
prefix: "_",
|
|
|
|
extension: ".pcss",
|
|
|
|
template: TEMPLATES.STYLE,
|
|
|
|
});
|
2022-02-09 10:33:21 +01:00
|
|
|
}
|
2022-12-12 12:24:14 +01:00
|
|
|
};
|
2022-02-09 10:33:21 +01:00
|
|
|
|
|
|
|
// Wrapper since await at the top level is not well supported yet
|
|
|
|
function run() {
|
|
|
|
(async function () {
|
|
|
|
await makeReactComponent();
|
|
|
|
})();
|
|
|
|
}
|
|
|
|
|
|
|
|
run();
|
|
|
|
return;
|