import { z } from "zod";
const hexColor = /^#[0-9A-Fa-f]{6}$/;
const languageSchema = z.object({
color: z.string().regex(hexColor),
percentage: z.number().min(0).max(100),
});
const imageUrl = z
.string()
.refine(
(u) =>
u.startsWith("https://") ||
u.startsWith("http://") ||
u.startsWith("data:image/"),
"Must be http, https or data URI",
);
export const repositoryCardSchema = z.object({
type: z.literal("repository"),
repoName: z.string().min(1).max(100),
ownerHandle: z.string().min(1).max(100),
stars: z.number().int().min(0).max(1000000).default(0),
pulls: z.number().int().min(0).max(100000).default(0),
issues: z.number().int().min(0).max(100000).default(0),
createdAt: z.string().max(100).optional().default(""),
avatarUrl: imageUrl.optional(),
languages: z.array(languageSchema).max(5).default([]),
});
export const issueCardSchema = z.object({
type: z.literal("issue"),
repoName: z.string().min(1).max(100),
ownerHandle: z.string().min(1).max(100),
authorHandle: z.string().min(1).max(100),
avatarUrl: imageUrl.optional(),
authorAvatarUrl: imageUrl.optional(),
title: z.string().min(1).max(500),
issueNumber: z.union([z.number().int().positive(), z.string().min(1).max(50)]),
status: z.enum(["open", "closed"]),
labels: z
.array(
z.object({
name: z.string().max(50),
color: z.string().regex(hexColor),
}),
)
.max(10)
.default([]),
commentCount: z.number().int().min(0).default(0),
reactionCount: z.number().int().min(0).default(0),
createdAt: z.string().optional().default(""),
});
export const pullRequestCardSchema = z.object({
type: z.literal("pullRequest"),
repoName: z.string().min(1).max(100),
ownerHandle: z.string().min(1).max(100),
authorHandle: z.string().min(1).max(100),
avatarUrl: imageUrl.optional(),
authorAvatarUrl: imageUrl.optional(),
title: z.string().min(1).max(500),
pullRequestNumber: z.union([z.number().int().positive(), z.string().min(1).max(50)]),
status: z.enum(["open", "closed", "merged"]),
filesChanged: z.number().int().min(0).optional(),
additions: z.number().int().min(0).optional(),
deletions: z.number().int().min(0).optional(),
rounds: z.number().int().min(1).default(1),
commentCount: z.number().int().min(0).default(0),
reactionCount: z.number().int().min(0).default(0),
createdAt: z.string().optional().default(""),
});
export const profileCardSchema = z.object({
type: z.literal("profile"),
handle: z.string().min(1).max(100),
avatarUrl: imageUrl.optional(),
description: z.string().max(500).optional(),
followers: z.number().int().min(0).default(0),
repos: z.number().int().min(0).default(0),
stars: z.number().int().min(0).default(0),
activity: z.array(z.number().int().min(0)).max(24).default([]),
});
export const stringCardSchema = z.object({
type: z.literal("string"),
ownerHandle: z.string().min(1).max(100),
avatarUrl: imageUrl.optional(),
filename: z.string().min(1).max(200),
description: z.string().max(500).optional(),
preview: z.array(z.string().max(300)).max(12).default([]),
lineCount: z.number().int().min(0).default(0),
createdAt: z.string().max(100).optional().default(""),
});
export const commitCardSchema = z.object({
type: z.literal("commit"),
repoName: z.string().min(1).max(100),
ownerHandle: z.string().min(1).max(100),
avatarUrl: imageUrl.optional(),
authorHandle: z.string().min(1).max(100),
authorAvatarUrl: imageUrl.optional(),
subject: z.string().min(1).max(500),
shortHash: z.string().min(1).max(40),
filesChanged: z.number().int().min(0).default(0),
additions: z.number().int().min(0).default(0),
deletions: z.number().int().min(0).default(0),
createdAt: z.string().max(100).optional().default(""),
});
export const fileCardSchema = z.object({
type: z.literal("file"),
repoName: z.string().min(1).max(100),
ownerHandle: z.string().min(1).max(100),
avatarUrl: imageUrl.optional(),
path: z.string().min(1).max(1000),
ref: z.string().min(1).max(200),
lineCount: z.number().int().min(0).default(0),
sizeLabel: z.string().max(32).optional(),
});
export const tagCardSchema = z.object({
type: z.literal("tag"),
repoName: z.string().min(1).max(100),
ownerHandle: z.string().min(1).max(100),
avatarUrl: imageUrl.optional(),
name: z.string().min(1).max(200),
message: z.string().max(2000).optional(),
taggerName: z.string().max(100).optional(),
createdAt: z.string().max(100).optional().default(""),
});
export const pipelineCardSchema = z.object({
type: z.literal("pipeline"),
repoName: z.string().min(1).max(100),
ownerHandle: z.string().min(1).max(100),
avatarUrl: imageUrl.optional(),
workflow: z.string().min(1).max(200),
status: z.enum(["pending", "running", "success", "failed", "timeout", "cancelled"]),
durationMs: z.number().int().min(0).default(0),
trigger: z.discriminatedUnion("kind", [
z.object({ kind: z.literal("push"), ref: z.string().min(1).max(200) }),
z.object({
kind: z.literal("pull_request"),
ref: z.string().min(1).max(200),
source: z.string().min(1).max(200),
}),
z.object({ kind: z.literal("manual") }),
]),
shortHash: z.string().min(1).max(40),
createdAt: z.string().max(100).optional().default(""),
});
export const cardPayloadSchema = z.discriminatedUnion("type", [
repositoryCardSchema,
issueCardSchema,
pullRequestCardSchema,
profileCardSchema,
stringCardSchema,
commitCardSchema,
fileCardSchema,
tagCardSchema,
pipelineCardSchema,
]);
export type Language = z.infer<typeof languageSchema>;
export type RepositoryCardData = z.infer<typeof repositoryCardSchema>;
export type IssueCardData = z.infer<typeof issueCardSchema>;
export type PullRequestCardData = z.infer<typeof pullRequestCardSchema>;
export type ProfileCardData = z.infer<typeof profileCardSchema>;
export type StringCardData = z.infer<typeof stringCardSchema>;
export type CommitCardData = z.infer<typeof commitCardSchema>;
export type FileCardData = z.infer<typeof fileCardSchema>;
export type TagCardData = z.infer<typeof tagCardSchema>;
export type PipelineCardData = z.infer<typeof pipelineCardSchema>;