Add app-store:watch

fix/hariom/remove-app-type
Hariom Balhara 2022-06-01 13:07:38 +05:30
parent 18c8057be7
commit c563415795
31 changed files with 273 additions and 71 deletions

12
.vscode/tasks.json vendored
View File

@ -16,8 +16,7 @@
"Embed Core(3100)",
"Embed React(3101)",
"Prisma Studio(5555)",
"Maildev(587)",
"AppStoreCli:Watch"
"Maildev(587)"
],
// Mark as the default build task so cmd/ctrl+shift+b will create them
"group": {
@ -76,11 +75,18 @@
"problemMatcher": []
},
{
"label": "AppStoreCli:Watch",
"label": "AppStoreCli-build:watch",
"type": "shell",
"command": "cd packages/app-store-cli && yarn build:watch",
"isBackground": false,
"problemMatcher": []
},
{
"label": "AppStoreWatch",
"type": "shell",
"command": "yarn app-store:watch",
"isBackground": false,
"problemMatcher": []
}
]
}

View File

@ -24,6 +24,7 @@
"docs-dev": "yarn predev && turbo run dev --scope=\"@calcom/docs\"",
"docs-build": "turbo run build --scope=\"@calcom/docs\" --include-dependencies",
"docs-start": "turbo run start --scope=\"@calcom/docs\"",
"app-store:watch": "node packages/app-store/app-store.js --watch",
"dx": "yarn predev && (git submodule update || true) && turbo run dx",
"format": "prettier --write \"**/*.{ts,tsx,md}\"",
"heroku-postbuild": "turbo run @calcom/web#build",
@ -49,7 +50,8 @@
"dotenv-checker": "^1.1.5",
"husky": "^8.0.1",
"lint-staged": "^12.4.1",
"prettier": "^2.5.1"
"prettier": "^2.5.1",
"chokidar": "^3.5.3"
},
"dependencies": {
"turbo": "1.2.9"

View File

@ -14,6 +14,9 @@ Change name and description
## TODO
- Put lowercase and - restriction only on App name
- Add space restriction as well for Appname. Maybe look for valid dirname or slug regex
- Merge app-store:watch and app-store commands, introduce app-store --watch
- Get strong confirmation for deletion of app. Get the name of the app from user that he wants to delete
- App Description Missing
- Select Box for App Type

View File

@ -36,7 +36,7 @@ const BaseAppFork = {
const appDirPath = getAppDirPath(appName);
yield "Forking base app";
execSync(`mkdir -p ${appDirPath}`);
execSync(`cp -r ${absolutePath("baseApp/*")} ${appDirPath}`);
execSync(`cp -r ${absolutePath("_baseApp/*")} ${appDirPath}`);
updatePackageJson({ appName, appDirPath });
let config = {
@ -94,7 +94,7 @@ const Seed = {
};
const generateAppFiles = () => {
execSync(`cd ${appStoreDir} && node generate-apps.js`);
execSync(`cd ${appStoreDir} && node app-store.js`);
};
const CreateApp = ({ noDbUpdate }) => {

View File

@ -1,8 +1,6 @@
import prisma from "@calcom/prisma";
import { App } from "@calcom/types/App";
import "./baseApp/_metadata";
export async function getAppWithMetadata(app: { dirName: string }) {
let appMetadata: App | null = null;
try {

View File

@ -0,0 +1,110 @@
const fs = require("fs");
const path = require("path");
const appDirs = [];
let isInWatchMode = false;
if (process.argv[2] === "--watch") {
isInWatchMode = true;
}
const chokidar = require("chokidar");
const { debounce } = require("lodash");
function getAppName(candidatePath) {
function isValidAppName(candidatePath) {
if (!candidatePath.startsWith("_") && !candidatePath.includes("/") && !candidatePath.includes("\\")) {
return candidatePath;
}
}
if (isValidAppName(candidatePath)) {
// Already a dirname of an app
return candidatePath;
}
// Get dirname of app from full path
const dirName = path.relative(__dirname, candidatePath);
return isValidAppName(dirName) ? dirName : null;
}
function generateFiles() {
fs.readdirSync(`${__dirname}`).forEach(function (dir) {
if (fs.statSync(`${__dirname}/${dir}`).isDirectory()) {
if (!getAppName(dir)) {
return;
}
appDirs.push(dir);
}
});
let clientOutput = [`import dynamic from "next/dynamic"`];
let serverOutput = [];
function forEachAppDir(callback) {
for (let i = 0; i < appDirs.length; i++) {
callback(appDirs[i]);
}
}
function getObjectExporter(objectName, { dirName, importBuilder, entryBuilder }) {
const output = [];
forEachAppDir((dirName) => {
output.push(importBuilder(dirName));
});
output.push(`export const ${objectName} = {`);
forEachAppDir((dirName) => {
output.push(entryBuilder(dirName));
});
output.push(`};`);
return output;
}
serverOutput.push(
...getObjectExporter("appStoreMetadata", {
importBuilder: (dirName) => `import { metadata as ${dirName}_meta } from "./${dirName}/_metadata";`,
entryBuilder: (dirName) => `${dirName}:${dirName}_meta,`,
})
);
serverOutput.push(
...getObjectExporter("apiHandlers", {
importBuilder: (dirName) => `const ${dirName}_api = import("./${dirName}/api");`,
entryBuilder: (dirName) => `${dirName}:${dirName}_api,`,
})
);
clientOutput.push(
...getObjectExporter("InstallAppButtonMap", {
importBuilder: (dirName) =>
`const ${dirName}_installAppButton = dynamic(() =>import("./${dirName}/components/InstallAppButton"));`,
entryBuilder: (dirName) => `${dirName}:${dirName}_installAppButton,`,
})
);
fs.writeFileSync(`${__dirname}/apps.generated.ts`, serverOutput.join("\n"));
fs.writeFileSync(`${__dirname}/apps.components.generated.tsx`, clientOutput.join("\n"));
console.log("Generated `apps.generated.ts` and `apps.components.generated.tsx`");
}
const debouncedGenerateFiles = debounce(generateFiles);
if (isInWatchMode) {
chokidar
.watch(__dirname)
.on("addDir", (dirPath) => {
const appName = getAppName(dirPath);
if (appName) {
console.log(`Added ${appName}`);
debouncedGenerateFiles();
}
})
.on("unlinkDir", (dirPath) => {
const appName = getAppName(dirPath);
if (appName) {
console.log(`Removed ${appName}`);
debouncedGenerateFiles();
}
});
} else {
generateFiles();
}

View File

@ -0,0 +1,9 @@
# Base App - App Store CLI
It contains the boiler plate code for a new app. There is a one time copying of files right now.
You can read details of how exactly the CLI uses this base app [here](../../app-store-cli/README.md).
## TODO
- Rename it _baseApp to convey very clearly that it is not an actual app.

View File

View File

@ -0,0 +1,19 @@
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
//@ts-ignore
export const metadata = {
description: _package.description,
category: "other",
rating: 0,
reviews: 0,
trending: true,
verified: true,
email: "CLI_BASE__PUBLISHER_EMAIL",
...config,
} as App;
export default metadata;

View File

@ -0,0 +1,40 @@
import type { NextApiRequest, NextApiResponse } from "next";
import prisma from "@calcom/prisma";
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (!req.session?.user?.id) {
return res.status(401).json({ message: "You must be logged in to do this" });
}
// TODO: Define appType once and import everywhere
const appType = "CLI_BASE__APP_NAME_CLI_BASE__APP_TYPE";
try {
const alreadyInstalled = await prisma.credential.findFirst({
where: {
type: appType,
userId: req.session.user.id,
},
});
if (alreadyInstalled) {
throw new Error("Already installed");
}
const installation = await prisma.credential.create({
data: {
type: appType,
key: {},
userId: req.session.user.id,
appId: "CLI_BASE__APP_NAME",
},
});
if (!installation) {
throw new Error("Unable to create user credential for CLI_BASE__APP_NAME");
}
} catch (error: unknown) {
if (error instanceof Error) {
return res.status(500).json({ message: error.message });
}
return res.status(500);
}
return res.status(200).json({ url: "/apps/zapier/setup" });
}

View File

@ -0,0 +1 @@
export { default as add } from "./add";

View File

@ -0,0 +1,20 @@
import type { InstallAppButtonProps } from "@calcom/app-store/types";
import useAddAppMutation from "../../_utils/useAddAppMutation";
export default function InstallAppButton(props: InstallAppButtonProps) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
const mutation = useAddAppMutation("CLI_BASE__APP_NAME_CLI_BASE__APP_TYPE");
return (
<>
{props.render({
onClick() {
mutation.mutate("");
},
loading: mutation.isLoading,
})}
</>
);
}

View File

@ -0,0 +1,15 @@
export default function Icon() {
return (
<svg
width="40"
height="40"
viewBox="0 0 256 256"
xmlns="http://www.w3.org/2000/svg"
preserveAspectRatio="xMidYMid">
<path
d="M159.999 128.056a76.55 76.55 0 0 1-4.915 27.024 76.745 76.745 0 0 1-27.032 4.923h-.108c-9.508-.012-18.618-1.75-27.024-4.919A76.557 76.557 0 0 1 96 128.056v-.112a76.598 76.598 0 0 1 4.91-27.02A76.492 76.492 0 0 1 127.945 96h.108a76.475 76.475 0 0 1 27.032 4.923 76.51 76.51 0 0 1 4.915 27.02v.112zm94.223-21.389h-74.716l52.829-52.833a128.518 128.518 0 0 0-13.828-16.349v-.004a129 129 0 0 0-16.345-13.816l-52.833 52.833V1.782A128.606 128.606 0 0 0 128.064 0h-.132c-7.248.004-14.347.62-21.265 1.782v74.716L53.834 23.665A127.82 127.82 0 0 0 37.497 37.49l-.028.02A128.803 128.803 0 0 0 23.66 53.834l52.837 52.833H1.782S0 120.7 0 127.956v.088c0 7.256.615 14.367 1.782 21.289h74.716l-52.837 52.833a128.91 128.91 0 0 0 30.173 30.173l52.833-52.837v74.72a129.3 129.3 0 0 0 21.24 1.778h.181a129.15 129.15 0 0 0 21.24-1.778v-74.72l52.838 52.837a128.994 128.994 0 0 0 16.341-13.82l.012-.012a129.245 129.245 0 0 0 13.816-16.341l-52.837-52.833h74.724c1.163-6.91 1.77-14 1.778-21.24v-.186c-.008-7.24-.615-14.33-1.778-21.24z"
fill="#FF4A00"
/>
</svg>
);
}

View File

@ -0,0 +1,2 @@
export { default as InstallAppButton } from "./InstallAppButton";
export { default as Icon } from "./icon";

View File

@ -0,0 +1,12 @@
{
"name": "demo",
"title": "it's a demo app",
"type": "other",
"slug": "demo",
"imageSrc": "/api/app-store/demo/icon.svg",
"logo": "/api/app-store/demo/icon.svg",
"url": "https://cal.com/apps/demo",
"variant": "other",
"publisher": "hariom",
"email": "hariombalhara@gmail.com"
}

View File

@ -0,0 +1,3 @@
export * as api from "./api";
export * as components from "./components";
export { metadata } from "./_metadata";

View File

@ -0,0 +1,14 @@
{
"$schema": "https://json.schemastore.org/package.json",
"private": true,
"name": "@calcom/demo",
"version": "0.0.0",
"main": "./index.ts",
"description": "Your app description goes here.",
"dependencies": {
"@calcom/lib": "*"
},
"devDependencies": {
"@calcom/types": "*"
}
}

View File

@ -1,61 +0,0 @@
const fs = require("fs");
const appDirs = [];
fs.readdirSync(`${__dirname}`).forEach(function (dir) {
if (fs.statSync(`${__dirname}/${dir}`).isDirectory()) {
if (dir.startsWith("_")) {
return;
}
appDirs.push(dir);
}
});
let clientOutput = [`import dynamic from "next/dynamic"`];
let serverOutput = [];
function forEachAppDir(callback) {
for (let i = 0; i < appDirs.length; i++) {
callback(appDirs[i]);
}
}
function getObjectExporter(objectName, { dirName, importBuilder, entryBuilder }) {
const output = [];
forEachAppDir((dirName) => {
output.push(importBuilder(dirName));
});
output.push(`export const ${objectName} = {`);
forEachAppDir((dirName) => {
output.push(entryBuilder(dirName));
});
output.push(`};`);
return output;
}
serverOutput.push(
...getObjectExporter("appStoreMetadata", {
importBuilder: (dirName) => `import { metadata as ${dirName}_meta } from "./${dirName}/_metadata";`,
entryBuilder: (dirName) => `${dirName}:${dirName}_meta,`,
})
);
serverOutput.push(
...getObjectExporter("apiHandlers", {
importBuilder: (dirName) => `const ${dirName}_api = import("./${dirName}/api");`,
entryBuilder: (dirName) => `${dirName}:${dirName}_api,`,
})
);
clientOutput.push(
...getObjectExporter("InstallAppButtonMap", {
importBuilder: (dirName) =>
`const ${dirName}_installAppButton = dynamic(() =>import("./${dirName}/components/InstallAppButton"));`,
entryBuilder: (dirName) => `${dirName}:${dirName}_installAppButton,`,
})
);
fs.writeFileSync(`${__dirname}/apps.generated.ts`, serverOutput.join("\n"));
fs.writeFileSync(`${__dirname}/apps.components.generated.tsx`, clientOutput.join("\n"));
console.log("Generated `apps.generated.ts` and `apps.components.generated.tsx`");

View File

@ -17,7 +17,9 @@ export { getCalendar };
export const getCalendarCredentials = (credentials: Array<Credential>, userId: number) => {
const calendarCredentials = getApps(credentials)
.filter((app) => app.type.endsWith("_calendar"))
.filter((app) => {
return app.type.endsWith("_calendar");
})
.flatMap((app) => {
const credentials = app.credentials.flatMap((credential) => {
const calendar = getCalendar(credential);

View File

@ -1 +1,8 @@
[]
[
{
"name": "demo",
"dirName": "demo",
"categories": ["other"],
"type": "demo_other"
}
]