Add app-store:watch
parent
18c8057be7
commit
c563415795
|
@ -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": []
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 }) => {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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();
|
||||
}
|
|
@ -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.
|
|
@ -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;
|
|
@ -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" });
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
export { default as add } from "./add";
|
|
@ -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,
|
||||
})}
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -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>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
export { default as InstallAppButton } from "./InstallAppButton";
|
||||
export { default as Icon } from "./icon";
|
|
@ -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"
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
export * as api from "./api";
|
||||
export * as components from "./components";
|
||||
export { metadata } from "./_metadata";
|
|
@ -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": "*"
|
||||
}
|
||||
}
|
|
@ -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`");
|
|
@ -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);
|
||||
|
|
|
@ -1 +1,8 @@
|
|||
[]
|
||||
[
|
||||
{
|
||||
"name": "demo",
|
||||
"dirName": "demo",
|
||||
"categories": ["other"],
|
||||
"type": "demo_other"
|
||||
}
|
||||
]
|
||||
|
|
Loading…
Reference in New Issue