Skip to content

Commit 4eb884e

Browse files
Hemil36TusharBhatt1kart1kaDevanshusharma2005volnei
authored
feat: zod schemas for app metadata in config.json (calcom#21231)
* feat: add config metadata validation and update app store metadata parsing * fix: update config metadata import to use parseconfigMetadata * Create configType.ts * fix: update app store metadata parsing to use parseconfigMetadata * feat: enhance generateFiles to handle metadata imports * Update build.ts * Updated build file --------- Co-authored-by: Tushar Bhatt <95581504+TusharBhatt1@users.noreply.github.com> Co-authored-by: Kartik Saini <41051387+kart1ka@users.noreply.github.com> Co-authored-by: Devanshu Sharma <devanshusharma658@gmail.com> Co-authored-by: Volnei Munhoz <volnei.munhoz@gmail.com>
1 parent 3f8ce8f commit 4eb884e

2 files changed

Lines changed: 127 additions & 1 deletion

File tree

packages/app-store-cli/src/build.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import prettier from "prettier";
99
//@ts-ignore
1010
import prettierConfig from "@calcom/config/prettier-preset";
1111
import type { AppMeta } from "@calcom/types/App";
12+
import { AppMetaSchema } from "@calcom/types/AppMetaSchema";
1213

1314
import { APP_STORE_PATH } from "./constants";
1415
import { getAppName } from "./utils/getAppName";
@@ -72,7 +73,14 @@ function generateFiles() {
7273
let app;
7374

7475
if (fs.existsSync(configPath)) {
75-
app = JSON.parse(fs.readFileSync(configPath).toString());
76+
try {
77+
const rawConfig = fs.readFileSync(configPath, "utf8");
78+
const parsedConfig = JSON.parse(rawConfig);
79+
app = AppMetaSchema.parse(parsedConfig);
80+
} catch (error) {
81+
const prefix = `Config error in ${path.join(APP_STORE_PATH, appDirs[i].path, "config.json")}`;
82+
throw new Error(`${prefix}: ${error instanceof Error ? error.message : String(error)}`);
83+
}
7684
} else if (fs.existsSync(metadataPath)) {
7785
// eslint-disable-next-line @typescript-eslint/no-var-requires
7886
app = require(metadataPath).metadata;

packages/types/AppMetaSchema.ts

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import { z } from "zod";
2+
3+
const eventLocationTypeSchema = z.object({
4+
type: z.string(),
5+
label: z.string(),
6+
messageForOrganizer: z.string().optional(),
7+
iconUrl: z.string().optional(),
8+
variable: z.literal("locationLink").optional(),
9+
defaultValueVariable: z.literal("link").optional(),
10+
attendeeInputType: z.null().optional(),
11+
attendeeInputPlaceholder: z.null().optional(),
12+
linkType: z.enum(["static", "dynamic"]),
13+
urlRegExp: z.string().optional(),
14+
organizerInputPlaceholder: z.string().optional(),
15+
organizerInputType: z.enum(["text", "phone"]).optional(),
16+
default: z.boolean().optional(),
17+
});
18+
19+
const appDataSchema = z
20+
.object({
21+
location: eventLocationTypeSchema.optional(),
22+
tag: z
23+
.object({
24+
scripts: z
25+
.array(
26+
z
27+
.object({
28+
src: z.string().optional(),
29+
content: z.string().optional(),
30+
attrs: z.record(z.any()).optional(),
31+
})
32+
.passthrough()
33+
)
34+
.optional(),
35+
})
36+
.passthrough()
37+
.optional(),
38+
})
39+
.passthrough()
40+
.nullable()
41+
.optional();
42+
43+
const paidAppDataSchema = z.object({
44+
priceInUsd: z.number(),
45+
priceId: z.string().optional(),
46+
trial: z.number().optional(),
47+
mode: z.enum(["subscription", "one_time"]).optional(),
48+
});
49+
50+
export const AppMetaSchema = z
51+
.object({
52+
name: z.string(),
53+
description: z.string(),
54+
type: z.string(),
55+
variant: z.enum([
56+
"calendar",
57+
"payment",
58+
"conferencing",
59+
"video",
60+
"other",
61+
"other_calendar",
62+
"automation",
63+
"crm",
64+
"analytics",
65+
"messaging",
66+
]),
67+
slug: z.string(),
68+
categories: z.array(z.string()),
69+
logo: z.string(),
70+
publisher: z.string(),
71+
url: z.string().optional(),
72+
email: z.string(),
73+
installed: z.boolean().optional(),
74+
title: z.string().optional(),
75+
category: z.string().optional(),
76+
extendsFeature: z.enum(["EventType", "User"]).optional(),
77+
docsUrl: z.string().optional(),
78+
verified: z.boolean().optional(),
79+
trending: z.boolean().optional(),
80+
rating: z.number().optional(),
81+
reviews: z.number().optional(),
82+
isGlobal: z.boolean().optional(),
83+
simplePath: z.string().optional(),
84+
key: z.any().optional(),
85+
feeType: z.enum(["monthly", "usage-based", "one-time", "free"]).optional(),
86+
price: z.number().optional(),
87+
commission: z.number().optional(),
88+
licenseRequired: z.boolean().optional(),
89+
teamsPlanRequired: z
90+
.object({
91+
upgradeUrl: z.string(),
92+
})
93+
.passthrough()
94+
.optional(),
95+
appData: appDataSchema,
96+
paid: paidAppDataSchema.optional(),
97+
dirName: z.string().optional(),
98+
isTemplate: z.boolean().optional(),
99+
__template: z.string().optional(),
100+
dependencies: z.array(z.string()).optional(),
101+
concurrentMeetings: z.boolean().optional(),
102+
createdAt: z.string().optional(),
103+
isOAuth: z.boolean().optional(),
104+
isAuth: z.boolean().optional(),
105+
delegationCredential: z
106+
.object({
107+
workspacePlatformSlug: z.string(),
108+
})
109+
.passthrough()
110+
.optional(),
111+
__createdUsingCli: z.boolean().optional(),
112+
imageSrc: z.string().optional(),
113+
label: z.string().optional(),
114+
linkType: z.string().optional(),
115+
})
116+
.passthrough();
117+
118+
export type AppMetaType = z.infer<typeof AppMetaSchema>;

0 commit comments

Comments
 (0)