{"version":3,"file":"install.mjs","names":[],"sources":["../../../../../../../src/astro/routes/api/admin/plugins/registry/install.ts"],"sourcesContent":["/**\n * Registry plugin install endpoint\n *\n * POST /_emdash/api/admin/plugins/registry/install\n *\n * Installs a plugin from the experimental decentralized plugin registry\n * (see RFC 0001). The browser resolves `(handle, slug) → (did, slug)`\n * via the aggregator before posting and sends the publisher DID\n * directly; the server skips the resolvePackage round-trip and looks\n * up the package by DID. Sending DID rather than handle means installs\n * work for publishers whose handle the aggregator couldn't resolve at\n * view time (handle is best-effort per the lexicon).\n */\n\nimport { hostEnvFromVersions } from \"@premium-cms/registry-client/env\";\nimport type { APIRoute } from \"astro\";\nimport { z } from \"zod\";\n\nimport { requirePerm } from \"#api/authorize.js\";\nimport { apiError, handleError, unwrapResult } from \"#api/error.js\";\nimport { handleRegistryInstall } from \"#api/index.js\";\nimport { checkMediaUsageActivationWriteFence } from \"#api/media-usage-write-fence.js\";\nimport { isParseError, parseBody } from \"#api/parse.js\";\n\nimport { VERSION } from \"../../../../../../version.js\";\n\nexport const prerender = false;\n\nconst installBodySchema = z.object({\n\t/**\n\t * Publisher DID. Required. Browser is expected to resolve\n\t * `(handle, slug) → did` against the aggregator before posting.\n\t */\n\tdid: z\n\t\t.string()\n\t\t.min(1)\n\t\t.max(2048)\n\t\t// Loose match -- atproto DID specs allow `did:plc:*` and\n\t\t// `did:web:*` plus future methods. Reject anything that\n\t\t// doesn't even start with `did:` rather than enumerating\n\t\t// methods here; downstream lexicon validation tightens.\n\t\t.regex(/^did:[a-z]+:/, \"Invalid DID\"),\n\t/** Package slug. */\n\tslug: z\n\t\t.string()\n\t\t.min(1)\n\t\t.max(64)\n\t\t// Mirrors the lexicon's slug grammar: ASCII letter followed by\n\t\t// letters / digits / `-` / `_`. Rejects anything that could\n\t\t// confuse the R2 prefix or the URL.\n\t\t.regex(/^[a-zA-Z][a-zA-Z0-9_-]*$/, \"Invalid slug\"),\n\t/** Optional explicit version. Defaults to the aggregator's latest. */\n\tversion: z.string().min(1).max(64).optional(),\n\t/**\n\t * Capabilities the admin acknowledged in the consent dialog, lifted\n\t * from the release record's declaredAccess block at browse time.\n\t * Compared against the bundle's manifest to detect drift between the\n\t * dialog and the install POST.\n\t */\n\tacknowledgedDeclaredAccess: z.unknown().optional(),\n\tacknowledgedMcpTools: z.unknown().optional(),\n});\n\nexport const POST: APIRoute = async ({ request, locals }) => {\n\ttry {\n\t\tconst { emdash, user } = locals;\n\n\t\tif (!emdash?.db) {\n\t\t\treturn apiError(\"NOT_CONFIGURED\", \"EmDash is not initialized\", 500);\n\t\t}\n\n\t\tconst denied = requirePerm(user, \"plugins:manage\");\n\t\tif (denied) return denied;\n\n\t\tconst activationFence = await checkMediaUsageActivationWriteFence(emdash.db);\n\t\tif (activationFence) return activationFence;\n\n\t\tconst body = await parseBody(request, installBodySchema);\n\t\tif (isParseError(body)) return body;\n\n\t\t// Block registry installs whose derived `pluginId` collides with\n\t\t// any build-time-reserved id: configured (in-process) plugins, and\n\t\t// sandboxed plugins declared in `config.sandboxed`. The runtime\n\t\t// caches sandboxed plugins by id; a registry install at the same\n\t\t// id would silently shadow or coexist with the build-time entry.\n\t\tconst reservedPluginIds = new Set<string>([\n\t\t\t...emdash.configuredPlugins.map((p: { id: string }) => p.id),\n\t\t\t...(emdash.config.sandboxed ?? []).map((p: { id: string }) => p.id),\n\t\t]);\n\n\t\tconst result = await handleRegistryInstall(\n\t\t\temdash.db,\n\t\t\temdash.storage,\n\t\t\temdash.getSandboxRunner(),\n\t\t\temdash.config.experimental?.registry,\n\t\t\t{\n\t\t\t\tdid: body.did,\n\t\t\t\tslug: body.slug,\n\t\t\t\tversion: body.version,\n\t\t\t\tacknowledgedDeclaredAccess: body.acknowledgedDeclaredAccess,\n\t\t\t\tacknowledgedMcpTools: body.acknowledgedMcpTools,\n\t\t\t},\n\t\t\t{\n\t\t\t\tconfiguredPluginIds: reservedPluginIds,\n\t\t\t\thostEnv: hostEnvFromVersions(VERSION, emdash.config.astroVersion),\n\t\t\t},\n\t\t);\n\n\t\tif (!result.success) return unwrapResult(result);\n\n\t\t// Sync runtime so the new plugin becomes active without a worker restart.\n\t\tawait emdash.syncRegistryPlugins();\n\n\t\treturn unwrapResult(result, 201);\n\t} catch (error) {\n\t\tconsole.error(\"[registry-install] Unhandled error:\", error);\n\t\treturn handleError(error, \"Failed to install plugin from registry\", \"INSTALL_FAILED\");\n\t}\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0BA,MAAa,YAAY;AAEzB,MAAM,oBAAoB,EAAE,OAAO;CAKlC,KAAK,EACH,QAAQ,CACR,IAAI,EAAE,CACN,IAAI,KAAK,CAKT,MAAM,gBAAgB,cAAc;CAEtC,MAAM,EACJ,QAAQ,CACR,IAAI,EAAE,CACN,IAAI,GAAG,CAIP,MAAM,4BAA4B,eAAe;CAEnD,SAAS,EAAE,QAAQ,CAAC,IAAI,EAAE,CAAC,IAAI,GAAG,CAAC,UAAU;CAO7C,4BAA4B,EAAE,SAAS,CAAC,UAAU;CAClD,sBAAsB,EAAE,SAAS,CAAC,UAAU;CAC5C,CAAC;AAEF,MAAa,OAAiB,OAAO,EAAE,SAAS,aAAa;AAC5D,KAAI;EACH,MAAM,EAAE,QAAQ,SAAS;AAEzB,MAAI,CAAC,QAAQ,GACZ,QAAO,SAAS,kBAAkB,6BAA6B,IAAI;EAGpE,MAAM,SAAS,YAAY,MAAM,iBAAiB;AAClD,MAAI,OAAQ,QAAO;EAEnB,MAAM,kBAAkB,MAAM,oCAAoC,OAAO,GAAG;AAC5E,MAAI,gBAAiB,QAAO;EAE5B,MAAM,OAAO,MAAM,UAAU,SAAS,kBAAkB;AACxD,MAAI,aAAa,KAAK,CAAE,QAAO;EAO/B,MAAM,oBAAoB,IAAI,IAAY,CACzC,GAAG,OAAO,kBAAkB,KAAK,MAAsB,EAAE,GAAG,EAC5D,IAAI,OAAO,OAAO,aAAa,EAAE,EAAE,KAAK,MAAsB,EAAE,GAAG,CACnE,CAAC;EAEF,MAAM,SAAS,MAAM,sBACpB,OAAO,IACP,OAAO,SACP,OAAO,kBAAkB,EACzB,OAAO,OAAO,cAAc,UAC5B;GACC,KAAK,KAAK;GACV,MAAM,KAAK;GACX,SAAS,KAAK;GACd,4BAA4B,KAAK;GACjC,sBAAsB,KAAK;GAC3B,EACD;GACC,qBAAqB;GACrB,SAAS,oBAAoB,SAAS,OAAO,OAAO,aAAa;GACjE,CACD;AAED,MAAI,CAAC,OAAO,QAAS,QAAO,aAAa,OAAO;AAGhD,QAAM,OAAO,qBAAqB;AAElC,SAAO,aAAa,QAAQ,IAAI;UACxB,OAAO;AACf,UAAQ,MAAM,uCAAuC,MAAM;AAC3D,SAAO,YAAY,OAAO,0CAA0C,iBAAiB"}