/** * Create a new plugin as a git repo in the site owner's GitHub account. * * POST /_emdash/api/admin/plugins/marketplace/fork { id, name, description? } * * Delegates to the hosting control plane (which holds the owner's connected * GitHub token, the plugin starter template and the marketplace credentials): * it generates the repo from the starter, registers the marketplace listing * and gives the repo a publish token, so every push to the repo releases a * new version. Only on platform-provisioned instances (seeded with * `platform:api_url` + `credits:project_id`). */ import type { APIRoute } from "astro"; import { z } from "zod"; import { requirePerm } from "#api/authorize.js"; import { apiError, apiSuccess, handleError } from "#api/error.js"; import { isParseError, parseBody } from "#api/parse.js"; import { OptionsRepository } from "#db/repositories/options.js"; export const prerender = false; const BodySchema = z.object({ id: z .string() .trim() .min(2) .max(64) .regex(/^[a-z][a-z0-9-]*$/, "lowercase letters, numbers and hyphens, starting with a letter"), name: z.string().trim().min(1).max(100), description: z.string().trim().max(200).optional(), }); export const POST: APIRoute = async ({ locals, request }) => { const { emdash, user } = locals; if (!emdash?.db) return apiError("NOT_CONFIGURED", "EmDash is not initialized", 500); const denied = requirePerm(user, "plugins:manage"); if (denied) return denied; const body = await parseBody(request, BodySchema); if (isParseError(body)) return body; try { const options = new OptionsRepository(emdash.db); const map = await options.getMany(["platform:api_url", "credits:project_id"]); const apiBase = (map.get("platform:api_url") ?? "").replace(/\/$/, ""); const project = map.get("credits:project_id") ?? ""; if (!apiBase || !project) { return apiError( "NOT_MANAGED", "Creating plugins needs the hosting platform, which isn't configured for this instance.", 400, ); } const res = await fetch(`${apiBase}/pluginFork`, { method: "POST", headers: { "Content-Type": "application/json", "X-EmDash-Request": "1" }, body: JSON.stringify({ project, ...body }), }); let parsed: unknown = null; try { parsed = JSON.parse(await res.text()); } catch { parsed = null; } const inner = parsed && typeof parsed === "object" && "data" in parsed ? (parsed as { data?: unknown }).data : parsed; const result = (inner && typeof inner === "object" ? inner : {}) as { success?: boolean; error?: string; }; if (!res.ok || result.success === false) { return apiError( "PLUGIN_FORK_ERROR", result.error ?? "The hosting platform rejected the request.", 502, ); } return apiSuccess(result); } catch (error) { return handleError(error, "Could not create the plugin repo", "PLUGIN_FORK_ERROR"); } };