import fs from "node:fs"; import path from "node:path"; import type { Plugin } from "vite"; import { type GeneratedArtifact } from "../../interfaces/artifact"; import { serviceArtifacts, type ServiceArtifact, } from "../../interfaces/services/artifact"; import { viewArtifacts, type InterfaceArtifact, } from "../../interfaces/views/artifact"; import { RUNTIME_DIR } from "../constants"; function relativeImport(fromFile: string, toFile: string): string { const rel = path .relative(path.dirname(fromFile), toFile) .split(path.sep) .join("/"); return rel.startsWith(".") ? rel : `./${rel}`; } /** Write each generated artifact into the runtime dir under the app root. */ function writeArtifacts( root: string, artifacts: readonly GeneratedArtifact[], ): void { for (const artifact of artifacts) { const artifactPath = path.resolve(root, RUNTIME_DIR, artifact.path); fs.mkdirSync(path.dirname(artifactPath), { recursive: true }); fs.writeFileSync( artifactPath, artifact.source({ resolveImport: (src) => relativeImport(artifactPath, path.resolve(root, src)), }), ); } } /** * Emits one render-contract artifact per view component into * `RUNTIME_DIR/views//.jsx`. The artifacts are exposed through * module federation (`./views//`) by the `federation` plugin, * so the workbench host can load and render each component as its own island. * Each service likewise emits a self-contained worker bundle plus a loader * module under `RUNTIME_DIR/services//`, exposed as `./services/`. * * The per-type knowledge lives in `interfaces/views` and `interfaces/services`; * this plugin only writes what those modules resolve. */ export function sanityExtensionArtifacts(options: { views: readonly InterfaceArtifact[]; services?: readonly ServiceArtifact[]; }): Plugin { return { name: "sanity/extension-artifacts", configResolved(config) { writeArtifacts(config.root, [ ...viewArtifacts(options.views), ...serviceArtifacts(options.services ?? []), ]); }, }; }