riot-web/scripts/make-react-component.js

153 lines
4.0 KiB
JavaScript
Raw Normal View History

#!/usr/bin/env node
2022-12-12 12:24:14 +01:00
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.pcss
*
*/
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";
Update to React 18 (#24763) * Upgrade target to es2021 Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Upgrade target to es2021 Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Upgrade to es2022 Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Upgrade to es2022 Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Fix babel config Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Fix babel config Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Fix React contexts Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Fix types Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Fix React state Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Update to React 18 Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Update to React 18 Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Install @testing-library/dom Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Update lockfile Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Yarn lock update * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --------- Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2024-10-21 15:50:06 +02:00
import { render } from "jest-matrix-react";
import %%ComponentName%% from '%%RelativeComponentPath%%';
describe("<%%ComponentName%% />", () => {
const defaultProps = {};
const getComponent = (props = {}) =>
render(<%%ComponentName%% {...defaultProps} {...props} />);
it("matches snapshot", () => {
const { asFragment } = getComponent();
expect(asFragment()).toMatchSnapshot()();
});
});
`,
STYLE: `
.mx_%%ComponentName%% {
}
2022-12-12 12:24:14 +01:00
`,
};
const options = {
alias: {
2022-12-12 12:24:14 +01:00
filePath: "f",
withStyle: "s",
},
};
2022-12-12 12:24:14 +01:00
const args = require("minimist")(process.argv, options);
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-12-12 12:24:14 +01:00
await fs.mkdir(dirName, { recursive: true });
};
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}`,
);
await ensureDirectoryExists(newFilePath);
2022-12-12 12:24:14 +01:00
const relativePathToComponent = path.parse(path.relative(path.dirname(newFilePath), componentFilePath || ""));
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" });
console.log(`Created ${path.relative(process.cwd(), newFilePath)}`);
return newFilePath;
} catch (error) {
2022-12-12 12:24:14 +01:00
if (error.code === "EEXIST") {
console.log(`File already exists ${path.relative(process.cwd(), newFilePath)}`);
return newFilePath;
} else {
throw error;
}
}
2022-12-12 12:24:14 +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);
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-12-12 12:24:14 +01:00
const componentName = filePath.split("/").slice(-1).pop();
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,
});
if (withStyle) {
2022-12-12 12:24:14 +01:00
await makeFile({
filePath,
componentName,
base: "res/css",
prefix: "_",
extension: ".pcss",
template: TEMPLATES.STYLE,
});
}
2022-12-12 12:24:14 +01:00
};
// Wrapper since await at the top level is not well supported yet
function run() {
(async function () {
await makeReactComponent();
})();
}
run();
return;