import { z } from 'zod'; import type { Release } from './release.ts'; const secretNameSchema = z .string() .regex(/^[a-zA-Z0-9._-]{1,64}$/, 'must match ^[a-zA-Z0-9._-]{1,64}$'); export const configSchema = z.strictObject({ email: z.boolean(), tokenSecretName: secretNameSchema.nullable(), }); export const configureSchema = configSchema.partial(); export const defaultConfig: Config = { email: false, tokenSecretName: null }; export const checkSchema = z.strictObject({ dryRun: z.boolean().default(false), }); export const cursorSchema = z .strictObject({ id: z.number().int().nonnegative().nullable(), publishedAt: z.iso.datetime().nullable(), etag: z.string().nullable(), baselineAt: z.iso.datetime(), }) .refine((value) => (value.id === null) === (value.publishedAt === null), { message: 'id and publishedAt must both be null or both be set', path: ['publishedAt'], }); export const githubReleaseSchema = z.object({ id: z.number().int().nonnegative(), tag_name: z.string(), name: z.string().nullable(), html_url: z.httpUrl(), published_at: z.iso.datetime().nullable(), body: z.string().nullable(), draft: z.boolean(), prerelease: z.boolean(), }); export const releasePageSchema = z.array(githubReleaseSchema); export type Config = z.infer; export type ConfigureInput = z.input; export type CheckInput = z.input; export type Cursor = z.infer; export type GithubRelease = z.infer; export type Mail = { subject: string; text: string; html: string }; export type StorageKey = 'config' | 'cursor'; export type Storage = { get: (key: StorageKey) => Promise; set: (key: StorageKey, value: Config | Cursor) => Promise; }; export type Timer = (milliseconds: number, callback: () => void) => () => void; export type Dependencies = { storage: Storage; fetch: typeof fetch; hook: (release: Release) => Promise; emailSend: (mail: Mail) => Promise; now?: () => number; timer?: Timer; }; export type CheckError = | `http_${number}` | 'timeout' | 'network' | 'on_release_failed' | 'on_release_timeout'; export type CheckResult = { status: 'ok' | 'rate_limited' | 'token_rejected'; dryRun: boolean; repo: string; baseline: boolean; notModified: boolean; newReleases: Array>; handled: number; deferred: number; truncated: boolean; error: CheckError | null; failedRelease: { id: number; tag: string; message: string } | null; emailed: boolean; emailError: 'email_failed' | null; rateLimitResetAt: string | null; }; export type Status = { config: Config; cursor: Cursor | null }; export type Tracker = { configure: (input?: unknown) => Promise; check: (input?: unknown) => Promise; status: () => Promise; job: () => Promise; }; export function validate( { schema, input, field }: { schema: S; input: unknown; field: string }, ): z.output { const parsed = schema.safeParse(input); if (!parsed.success) throw new Error(`${field}: ${parsed.error.message}`); return parsed.data; }