import { logger } from "../log"; import { brandOrganizationId } from "../organizations"; import { brandProjectId } from "../projects"; import { type Interface, LocalApplication, type LocalInterface, } from "./applications"; import { brandApplicationId } from "./brett-applications"; import type { CoreApp } from "./core-apps"; import type { Studio } from "./studios"; const LOCAL_TIMESTAMP = new Date(0).toISOString(); /** * Reads the dev server's payload. A running CLI is the only source of local * applications, and one it can't describe is dropped rather than failing the * whole list. * @public */ export function parseLocalApplications( applications: readonly unknown[], ): LocalApplication[] { return applications.flatMap((application) => { const parsed = LocalApplication.safeParse(application); if (parsed.success) return [parsed.data]; logger.warn("ignoring a local application the dev server sent", { issues: parsed.error.issues, }); return []; }); } function toBrettInterfaces( interfaces: LocalInterface[] | undefined, ): Interface[] { // A local interface already carries the id, moduleId, and metadata a deploy // would stamp, so it resolves like a deployed one — only the dev-only `src` // is dropped. return (interfaces ?? []).map( ({ src: _src, ...deployed }): Interface => deployed, ); } /** * Builds a `Studio` from a local CLI dev-server payload. The client manifest and * the forwarded interfaces are adapted into brett's deployment shape, so a local * studio reads workspaces, schema, version and interfaces through the same * channel a deployed one does. * @public */ export function createStudioApplicationFromLocal( local: Extract, ): Studio { const { manifest } = local; // Prefer the projectId from `sanity.cli.ts` (available synchronously at // dev-server startup) over the manifest's first workspace, so the studio's // primary project is resolvable from the first HMR event — no manifest race. // The host-port fallback should never fire in practice; it remains as a // last-resort safety net for older CLI versions that don't yet forward // `projectId`. const firstWorkspaceProjectId = manifest?.workspaces[0]?.projectId; const projectId = brandProjectId( local.projectId ?? firstWorkspaceProjectId ?? `${local.host}-${local.port}`, ); const id = brandApplicationId(local.id); const interfaces = toBrettInterfaces(local.interfaces); // host:port fallback keeps navigation recognisable before the manifest // finishes extracting. const title = manifest?.workspaces[0]?.title ?? `${local.host}:${local.port}`; // Identity is authored by the CLI and read straight off the dev-server entry. return { id, type: "studio", title, name: local.name, reference: local.reference, isSingleton: false, visibility: "default", slug: null, externalUrl: `http://${local.host}:${local.port}`, organizationId: brandOrganizationId("local"), createdAt: LOCAL_TIMESTAMP, updatedAt: LOCAL_TIMESTAMP, config: { studio: { projectId, autoUpdatingVersion: null, }, }, activeDeployment: manifest || interfaces.length ? { id: `${id}-local`, applicationId: id, size: null, version: manifest?.studioVersion ?? null, isAutoUpdating: null, isActiveDeployment: true, deployedBy: null, createdAt: LOCAL_TIMESTAMP, updatedAt: LOCAL_TIMESTAMP, interfaces, workspaces: manifest?.workspaces.map((workspace) => ({ id: `${id}-${workspace.name}`, name: workspace.name, title: workspace.title ?? null, subtitle: workspace.subtitle ?? null, projectId: workspace.projectId, dataset: workspace.dataset, schemaDescriptorId: workspace.schema ? `${id}-${workspace.name}` : null, basePath: workspace.basePath ?? null, icon: workspace.icon ?? null, })), } : null, }; } /** * Builds a `CoreApp` from a local CLI dev-server payload. The forwarded * interfaces are adapted into brett's deployment shape, and the manifest's * title and icon are lifted onto the application record brett would serve. * @public */ export function createCoreApplicationFromLocal( local: Extract, ): CoreApp { const { manifest } = local; const id = brandApplicationId(local.id); const interfaces = toBrettInterfaces(local.interfaces); // Identity is authored by the CLI and read straight off the dev-server entry. return { id, type: "coreApp", title: manifest?.title ?? `${local.host}:${local.port}`, name: local.name, reference: local.reference, icon: manifest?.icon ?? null, isSingleton: false, visibility: "default", slug: null, externalUrl: `http://${local.host}:${local.port}`, organizationId: brandOrganizationId("local"), createdAt: LOCAL_TIMESTAMP, updatedAt: LOCAL_TIMESTAMP, activeDeployment: interfaces.length ? { id: `${id}-local`, applicationId: id, size: null, version: null, isAutoUpdating: null, isActiveDeployment: true, deployedBy: null, createdAt: LOCAL_TIMESTAMP, updatedAt: LOCAL_TIMESTAMP, interfaces, } : null, manifest: manifest ?? null, }; }