import { z } from "zod"; import type { IconDescriptor } from "./applications"; import { BrettApplication, ExternalApplication, InternalApplication, } from "./brett-applications"; import type { DockGroup } from "./interfaces"; /** * @public */ export const LocalCoreApplicationManifest = z.object({ version: z.string(), icon: z.string().optional(), title: z.string().optional(), slug: z.string(), }); /** * @public */ export type LocalCoreApplicationManifest = z.output< typeof LocalCoreApplicationManifest >; const internalCoreApplication = InternalApplication.extend({ type: z.literal("coreApp"), title: z.string(), config: z.object({ mfManifest: z.unknown().optional() }).optional(), manifest: LocalCoreApplicationManifest.nullable().optional(), }); const externalCoreApplication = ExternalApplication.extend({ type: z.literal("coreApp"), title: z.string(), config: z.object({ mfManifest: z.unknown().optional() }).optional(), manifest: LocalCoreApplicationManifest.nullable().optional(), }); /** * A Sanity-hosted SDK app (`slug` set, `externalUrl: null`) — the only kind * brett hosts. * @public */ export type InternalCoreApplication = z.output; /** * An externally served SDK app (`externalUrl` set, `slug: null`). Never on * the wire — brett doesn't host external SDK apps — but a local CLI dev * server serves its app from `http://localhost`. * @public */ export type ExternalCoreApplication = z.output; /** * An SDK app from `GET /applications?type=coreApp`, brett's shape verbatim. * `manifest` is the one addition: the channel a local CLI dev server forwards * its extracted manifest on — never present on the wire. * @public */ export type CoreApp = InternalCoreApplication | ExternalCoreApplication; /** * Validates and parses a raw API response into a branded * CoreApp. Parses the internal variant only — brett doesn't host * external SDK apps. * @public */ export function parseCoreApplication(data: unknown): CoreApp { return internalCoreApplication.parse(data); } /** * @public */ export class CoreApplication extends BrettApplication { readonly activeDeployment: CoreApp["activeDeployment"]; constructor( application: CoreApp, options: { isLocal?: boolean; remoteApplication?: CoreApplication | null; } = {}, ) { super(application, "coreApp", options); this.activeDeployment = application.activeDeployment; } get hasAppView(): boolean { return this.interfaces("app").length > 0; } /** * An SDK app that declares no `app` view is panel-only, so it gets no dock * entry and 404s on direct visit (US5). */ get href(): string | null { if (!this.hasAppView) return null; return this.isLocal ? `/local/${this.id}` : `/application/${this.application.id}`; } get icon(): IconDescriptor { const svg = this.application.icon; if (svg) { return { variant: "image", svg }; } return { variant: "avatar", initials: this.initials, color: this.avatarColor, }; } /** * Read off the `app` interface for local and deployed apps alike, since a * local app's manifest value is adapted onto that interface upstream. */ get group(): DockGroup | undefined { return this.interfaces("app")[0]?.metadata?.group as DockGroup | undefined; } get priority(): number | undefined { return this.interfaces("app")[0]?.metadata?.priority; } get title() { return this.manifest?.title ?? this.application.title; } /** * The manifest a local CLI dev server forwards. Deployed apps carry none — * their dock metadata rides the `app` interface (see {@link group} and * {@link priority}) and their icon rides the application-level `icon` field. */ get manifest(): LocalCoreApplicationManifest | null { return this.application.manifest ?? null; } get subtitle() { return undefined; } get updatedAt() { return this.application.updatedAt; } get(attr: TKey): CoreApp[TKey] { if (!(attr in this.application)) { throw new Error( `Attribute ${attr.toString()} does not exist on application ${this.application.id}`, ); } return this.application[attr]; } }