feature/app-generator-cli
Hariom Balhara 2022-05-27 17:30:32 +05:30
parent 40ad51381e
commit 3a3c3a918b
14 changed files with 255 additions and 11 deletions

View File

@ -40,7 +40,8 @@
"embed-tests-quick": "turbo run embed-tests-quick",
"embed-tests": "turbo run embed-tests",
"test-e2e": "turbo run test-e2e --concurrency=1",
"type-check": "turbo run type-check"
"type-check": "turbo run type-check",
"app-store": "yarn workspace @calcom/app-store-cli cli"
},
"devDependencies": {
"dotenv-checker": "^1.1.5",

2
packages/app-store-cli/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
node_modules
dist

View File

@ -0,0 +1,55 @@
{
"name": "@calcom/app-store-cli",
"version": "0.0.0",
"license": "MIT",
"bin": "dist/cli.js",
"engines": {
"node": ">=10"
},
"scripts": {
"build:watch": "tsc --watch",
"build": "tsc && chmod +x dist/cli.js",
"start": "npm run build && dist/cli.js",
"pretest": "npm run build",
"test": "xo && ava",
"cli": "chmod +x dist/cli.js && dist/cli.js"
},
"files": [
"dist/cli.js"
],
"dependencies": {
"ink": "^3.2.0",
"ink-text-input": "^4.0.3",
"meow": "^9.0.0",
"react": "^17.0.2"
},
"ava": {
"typescript": {
"extensions": [
"tsx"
],
"rewritePaths": {
"source/": "dist/"
}
}
},
"xo": {
"extends": "xo-react",
"rules": {
"react/prop-types": "off"
}
},
"devDependencies": {
"@ava/typescript": "^3.0.1",
"@sindresorhus/tsconfig": "^2.0.0",
"@types/react": "^18.0.9",
"ava": "^4.2.0",
"chalk": "^4.1.2",
"eslint-config-xo-react": "^0.27.0",
"eslint-plugin-react": "^7.30.0",
"eslint-plugin-react-hooks": "^4.5.0",
"ink-testing-library": "^2.1.0",
"typescript": "^4.6.4",
"xo": "^0.39.1"
}
}

View File

@ -0,0 +1,16 @@
## Steps to create an app
- Create a folder in packages/app-store/{APP_NAME} = {APP}
- Fill it with a sample app
- Modify {APP}/_metadata.ts with the data provided
- ## package.json
Change name and description
**Variables**
**PREFIXES**:
CLI_BASE__
cli_base_

View File

@ -0,0 +1,25 @@
#!/usr/bin/env node
import React from 'react';
import {render} from 'ink';
import meow from 'meow';
import App from './ui';
const cli = meow(`
Usage
$ code
Options
--bname Your name
Examples
$ code --name=Jane
Hello, Jane
`, {
flags: {
name: {
type: 'string'
}
}
});
render(<App name={cli.flags.name}/>);

View File

@ -0,0 +1 @@
export const calRepoPrefix = "/Users/hariombalhara/www/cal.com/"

View File

@ -0,0 +1,17 @@
import React from 'react';
import chalk from 'chalk';
import test from 'ava';
import {render} from 'ink-testing-library';
import App from './ui';
test('greet unknown user', t => {
const {lastFrame} = render(<App/>);
t.is(lastFrame(), chalk`Hello, {green Stranger}`);
});
test('greet user with a name', t => {
const {lastFrame} = render(<App name="Jane"/>);
t.is(lastFrame(), chalk`Hello, {green Jane}`);
});

View File

@ -0,0 +1,104 @@
import child_process from "child_process";
import fs from "fs";
import { Box, Text, useApp, useInput, useStdin } from "ink";
import TextInput from "ink-text-input";
import React, { FC, useEffect, useRef, useState } from "react";
const InputAppDetails = () => {
// AppName
// Type of App - Other, Calendar, Video, Payment, Messaging, Web3
const [appInputData, setAppInputData] = useState({});
const [inputIndex, setInputIndex] = useState(0);
const fields = [
{ label: "App Name", name: "appName" },
{ label: "Type of App", name: "appType" },
];
const fieldLabel = fields[inputIndex]?.label || "";
const fieldName = fields[inputIndex]?.name || "";
const fieldValue = appInputData[fieldName] || "";
const appName = appInputData["appName"];
const appType = appInputData["appType"];
const [result, setResult] = useState("...");
useEffect(() => {
if (inputIndex === fields.length) {
let config = {
name: appName,
type: appType,
slug: appName,
};
const appDirPath = `packages/app-store/${appName}/`;
child_process.execSync(
`mkdir -p ${appDirPath} && cp -r packages/app-store/CLI_BASE__APP_NAME/* ${appDirPath}`
);
const packageJsonConfig = JSON.parse(fs.readFileSync(`${appDirPath}/package.json`).toString());
packageJsonConfig.name = `@calcom/${appName}`;
// packageJsonConfig.description = `@calcom/${appName}`;
fs.writeFileSync(`${appDirPath}/package.json`, JSON.stringify(packageJsonConfig, null, 2));
const baseConfig = JSON.parse(fs.readFileSync(`${appDirPath}/config.json`).toString());
config = {
...baseConfig,
...config,
};
fs.writeFileSync(`${appDirPath}/config.json`, JSON.stringify(config, null, 2));
const seedConfig = JSON.parse(fs.readFileSync(`packages/prisma/seed-app-store.config.json`).toString());
if (!seedConfig.find((app) => app.name === appName)) {
seedConfig.push({
name: appName,
dirName: appName,
categories: [appType],
type: `${appName}_${appType}`,
});
}
fs.writeFileSync(`packages/prisma/seed-app-store.config.json`, JSON.stringify(seedConfig, null, 2));
child_process.execSync(`yarn db-seed`);
child_process.execSync(`cd packages/app-store && node generate-apps.js`);
setResult("App Generated");
}
});
if (inputIndex === fields.length) {
return (
<>
<Text>
Creating app with name "{appName}" of type "{appType}"
</Text>
<Text>{result}</Text>
</>
);
}
return (
<Box>
<Text color="green">{`${fieldLabel}:`}</Text>
<TextInput
value={fieldValue}
onSubmit={(value) => {
if (!value) {
return;
}
setInputIndex((index) => {
return index + 1;
});
}}
onChange={(value) => {
if (value) {
value = value.replace(/-/g, "_");
}
setAppInputData((appInputData) => {
return {
...appInputData,
[fieldName]: value,
};
});
}}
/>
</Box>
);
};
const App: FC<{ name?: string }> = () => <InputAppDetails />;
module.exports = App;
export default App;

View File

@ -0,0 +1,11 @@
{
"extends": "@sindresorhus/tsconfig",
"compilerOptions": {
"module": "commonjs",
"jsx": "react",
"esModuleInterop": true,
"outDir": "dist",
"noEmitOnError": false
},
"include": ["source"]
}

View File

@ -1,5 +1,9 @@
import fs from "fs";
import path from "path";
import type { App } from "@calcom/types/App";
import config from "./config.json";
import _package from "./package.json";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
@ -21,6 +25,7 @@ export const metadata = {
variant: "CLI_BASE__APP_TYPE",
verified: true,
email: "CLI_BASE__PUBLISHER_EMAIL",
...config,
} as App;
export default metadata;

View File

@ -0,0 +1 @@
{}

View File

@ -0,0 +1,10 @@
[
{
"name": "zoom1app",
"dirName": "zoom1app",
"categories": [
"other"
],
"type": "zoom1app_other"
}
]

View File

@ -1,9 +0,0 @@
const generatedApps = [
{
name: "CLI_BASE__APP_NAME",
dirName: "CLI_BASE__APP_NAME",
categories: ["other"],
type: "CLI_BASE_APP_NAME_CLI_BASE_APP_TYPE",
},
];
export default generatedApps;

View File

@ -1,7 +1,8 @@
import { Prisma } from "@prisma/client";
import fs from "fs";
import path from "path";
import prisma from ".";
import generatedApps from "./seed-app-store.config";
require("dotenv").config({ path: "../../.env.appStore" });
@ -132,6 +133,10 @@ async function main() {
webhook_secret: process.env.STRIPE_WEBHOOK_SECRET,
});
}
const generatedApps = JSON.parse(
fs.readFileSync(path.join(__dirname, "seed-app-store.config.json"), "utf8")
);
for (let i = 0; i < generatedApps.length; i++) {
const generatedApp = generatedApps[i];
await createApp(generatedApp.name, generatedApp.dirName, generatedApp.categories, generatedApp.type);