@aglyn/plugins-marketplace 1.0.0-beta.207 → 1.0.0-beta.211

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aglyn/plugins-marketplace",
3
- "version": "1.0.0-beta.207",
3
+ "version": "1.0.0-beta.211",
4
4
  "license": "Apache-2.0",
5
5
  "homepage": "https://aglyn.com",
6
6
  "repository": {
@@ -25,19 +25,19 @@
25
25
  "./package.json": "./package.json"
26
26
  },
27
27
  "dependencies": {
28
- "@aglyn/aglyn": "1.0.0-beta.207",
29
- "@aglyn/aglyn-markdown-editor": "1.0.0-beta.207",
30
- "@aglyn/shared-data-enums": "1.0.0-beta.207",
31
- "@aglyn/shared-data-mdi": "1.0.0-beta.207",
32
- "@aglyn/shared-ui-jsx": "1.0.0-beta.207",
33
- "@aglyn/shared-ui-next": "1.0.0-beta.207",
34
- "@aglyn/shared-ui-snackstack": "1.0.0-beta.207",
35
- "@aglyn/shared-util-email": "1.0.0-beta.207",
36
- "@aglyn/shared-util-http": "1.0.0-beta.207",
37
- "@aglyn/shared-util-timestamp": "1.0.0-beta.207",
38
- "@aglyn/tenant-data-admin": "1.0.0-beta.207",
39
- "@aglyn/tenant-feature-instance": "1.0.0-beta.207",
40
- "@aglyn/tenant-runtime": "1.0.0-beta.207",
28
+ "@aglyn/aglyn": "1.0.0-beta.211",
29
+ "@aglyn/aglyn-markdown-editor": "1.0.0-beta.211",
30
+ "@aglyn/shared-data-enums": "1.0.0-beta.211",
31
+ "@aglyn/shared-data-mdi": "1.0.0-beta.211",
32
+ "@aglyn/shared-ui-jsx": "1.0.0-beta.211",
33
+ "@aglyn/shared-ui-next": "1.0.0-beta.211",
34
+ "@aglyn/shared-ui-snackstack": "1.0.0-beta.211",
35
+ "@aglyn/shared-util-email": "1.0.0-beta.211",
36
+ "@aglyn/shared-util-http": "1.0.0-beta.211",
37
+ "@aglyn/shared-util-timestamp": "1.0.0-beta.211",
38
+ "@aglyn/tenant-data-admin": "1.0.0-beta.211",
39
+ "@aglyn/tenant-feature-instance": "1.0.0-beta.211",
40
+ "@aglyn/tenant-runtime": "1.0.0-beta.211",
41
41
  "@swc/helpers": "0.5.23"
42
42
  },
43
43
  "peerDependencies": {
@@ -20,10 +20,11 @@ import { buildRoute, PLUGIN_COMPONENT_ID, pluginDocsHelp, Route } from "@aglyn/a
20
20
  import { AppLink, CardDisplay, useConfirmationContext } from "@aglyn/shared-ui-jsx";
21
21
  import { useSnackbar } from "@aglyn/shared-ui-snackstack";
22
22
  import { Alert, Button, Chip, Stack, Tooltip, Typography } from "@mui/material";
23
- import { collection, deleteDoc, doc, documentId, limit, query, where } from "firebase/firestore";
23
+ import { collection, doc, documentId, limit, query, where } from "firebase/firestore";
24
24
  import { useCallback, useMemo, useState } from "react";
25
25
  import { compareArtifactVersions, isPluginRevoked, lockdownRefusalText, offeredPluginVersion, parseLockdownRefusal } from "@aglyn/aglyn";
26
26
  import { useFirestore, useFirestoreCollection, useHostOrgId, useUser } from "@aglyn/tenant-feature-instance";
27
+ import { writeSiteWideChange } from "@aglyn/tenant-feature-instance/hooks/helpers/site-wide-change";
27
28
  import { authorizedFetch } from "@aglyn/shared-util-http/authorized-token";
28
29
  /**
29
30
  * Installed marketplace plugins (AGL-45): lists the host's pinned plugin
@@ -262,7 +263,12 @@ import { authorizedFetch } from "@aglyn/shared-util-http/authorized-token";
262
263
  scope: 'org'
263
264
  });
264
265
  if (!payload) return;
265
- await deleteDoc(doc(firestore, 'hosts', hostId, 'installs', install.$id));
266
+ await writeSiteWideChange({
267
+ firestore,
268
+ user,
269
+ hostId,
270
+ write: (batch)=>batch.delete(doc(firestore, 'hosts', hostId, 'installs', install.$id))
271
+ });
266
272
  enqueueSnackbar('Plugin now installed for the whole organization', {
267
273
  variant: 'success',
268
274
  persist: false
@@ -274,6 +280,7 @@ import { authorizedFetch } from "@aglyn/shared-util-http/authorized-token";
274
280
  requestPluginApi,
275
281
  firestore,
276
282
  hostId,
283
+ user,
277
284
  enqueueSnackbar
278
285
  ]);
279
286
  const handleOrgUpgrade = useCallback((install)=>async ()=>{
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/components/host-plugins-card.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport {\n buildRoute,\n PLUGIN_COMPONENT_ID,\n pluginDocsHelp,\n Route,\n} from '@aglyn/aglyn'\nimport {\n AppLink,\n CardDisplay,\n useConfirmationContext,\n} from '@aglyn/shared-ui-jsx'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport { Alert, Button, Chip, Stack, Tooltip, Typography } from '@mui/material'\nimport {\n collection,\n deleteDoc,\n doc,\n documentId,\n limit,\n query,\n where,\n} from 'firebase/firestore'\nimport { useCallback, useMemo, useState } from 'react'\nimport {\n compareArtifactVersions,\n isPluginRevoked,\n lockdownRefusalText,\n offeredPluginVersion,\n parseLockdownRefusal,\n} from '@aglyn/aglyn'\nimport {\n useFirestore,\n useFirestoreCollection,\n useHostOrgId,\n useUser,\n} from '@aglyn/tenant-feature-instance'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\n\nexport interface HostPluginsCardProps {\n hostId: string\n /**\n * Org slug from the URL, when this card is rendered at org scope. With it,\n * each row links through to that installation's own page (AGL-1007);\n * without it the rows stay plain text rather than linking to nowhere.\n */\n orgSlug?: string\n}\n\n/**\n * Installed marketplace plugins (AGL-45): lists the host's pinned plugin\n * installs with their capabilities, flags available upgrades (latest\n * listing version ≠ pinned) and platform kill switches (public\n * `revocations` read), and offers explicit upgrade/uninstall. Upgrades go\n * through `/api/marketplace/install-plugin` so the new pin is validated\n * server-side; uninstall removes the host install doc. Place the installed\n * plugin on a screen with the Plugin element (listing id).\n */\nexport function HostPluginsCard(props: HostPluginsCardProps) {\n const { hostId, orgSlug } = props\n // One row → one installation page (AGL-1007). The name is the link, so a\n // row stays clickable without swallowing its own action buttons.\n const installationHref = (listingId: string) =>\n orgSlug\n ? buildRoute(Route.ORG_PLUGIN_INSTALLATION, {\n orgSlug,\n pluginRef: listingId,\n })\n : undefined\n const firestore = useFirestore()\n const { data: user } = useUser()\n const { enqueueSnackbar } = useSnackbar()\n const { confirm } = useConfirmationContext()\n const [busy, setBusy] = useState<string | null>(null)\n\n const orgId = useHostOrgId(hostId)\n const { data: installDocs } = useFirestoreCollection<any>(\n () => query(collection(firestore, 'hosts', hostId, 'installs'), limit(50)),\n [firestore, hostId],\n { idField: '$id' },\n )\n // Org-tier pins (AGL-237): apply to every host in the org; a host pin\n // of the same listing shadows the org one. Held at null while\n // `useHostOrgId` is in flight, never `orgs/-pending-` (AGL-1440):\n // installs are member-gated, so the sentinel was a guaranteed-denied\n // listen on every mount of this card.\n const { data: orgInstallDocs } = useFirestoreCollection<any>(\n () =>\n orgId\n ? query(collection(firestore, 'orgs', orgId, 'installs'), limit(50))\n : null,\n [firestore, orgId],\n { idField: '$id' },\n )\n const orgInstalls = useMemo(\n () => [...((orgInstallDocs as any[]) ?? [])],\n [orgInstallDocs],\n )\n const installs = useMemo(\n () => [...((installDocs as any[]) ?? [])],\n [installDocs],\n )\n const listingIds = useMemo(\n () =>\n [\n ...new Set([\n ...installs.map((install) => install.$id),\n ...orgInstalls.map((install) => install.$id),\n ]),\n ].slice(0, 10),\n [installs, orgInstalls],\n )\n\n // Latest published version per listing (upgrade detection) — public read.\n const { data: listingDocs } = useFirestoreCollection<any>(\n () =>\n query(\n collection(firestore, 'marketplaceListings'),\n where(documentId(), 'in', listingIds.length ? listingIds : ['-none-']),\n ),\n [firestore, listingIds.join(',')],\n { idField: '$id' },\n )\n // Kill switches (public revocations read) for the installed listings.\n const { data: revocationDocs } = useFirestoreCollection<any>(\n () =>\n query(\n collection(firestore, 'revocations'),\n where(documentId(), 'in', listingIds.length ? listingIds : ['-none-']),\n ),\n [firestore, listingIds.join(',')],\n { idField: '$id' },\n )\n const revokedByListing = useMemo(() => {\n const map: Record<string, boolean> = {}\n for (const install of installs) {\n const revocation = ((revocationDocs as any[]) ?? []).find(\n (doc) => doc.$id === install.$id,\n )\n if (!revocation) continue\n // THE SHARED PREDICATE, not a fourth copy of it (AGL-2305). This card\n // carried a byte-for-byte duplicate of `isPluginRevoked`'s body, which\n // is precisely what AGL-1085 collapsed three inline copies into one to\n // stop: a reader that disagrees with the writer about this document's\n // shape is how a kill switch silently stops killing. It happened to be\n // correct; being correct today is not the property that matters.\n map[install.$id] = isPluginRevoked(revocation, install.version)\n }\n return map\n }, [installs, revocationDocs])\n\n /*==========================================\n * WHY IT WAS DISABLED (AGL-2328).\n *\n * Staff are REQUIRED to type a reason to revoke a plugin version — the\n * route refuses without one — and until now the customer whose site the\n * plugin stopped working on was shown a bare \"disabled\" chip and a generic\n * paragraph. The reason was stored, in the same document this card already\n * reads, one field away from the flag it renders. Every consumer of\n * `revocations` reads `versions` and nothing else; the type's own comment\n * calls `versions` \"the ONLY field any reader consults\", which was true and\n * is the defect.\n *\n * Only the REASON and the DATE are surfaced. `revokedBy` is a staff uid and\n * belongs in the audit log, not on a customer's screen.\n *=========================================*/\n const revocationDetailByListing = useMemo(() => {\n const map: Record<string, { reason: string | null; atMs: number | null }> =\n {}\n for (const doc of ((revocationDocs as any[]) ?? [])) {\n const at = doc.revokedAt\n map[doc.$id] = {\n reason:\n typeof doc.reason === 'string' && doc.reason.trim()\n ? doc.reason.trim()\n : null,\n atMs:\n at?.toMillis?.() ?? (typeof at?.seconds === 'number' ? at.seconds * 1000 : null),\n }\n }\n return map\n }, [revocationDocs])\n\n // The newest INSTALLABLE version, not merely the newest approved one\n // (AGL-1016, corrected by AGL-2368).\n //\n // Offering `latestVersion` here put an Upgrade button in front of every\n // workspace the moment a publisher uploaded, and the install route then\n // refused the click — AGL-966 only ever hands out reviewed bytes. The\n // mirror fixed that and then reproduced it one state over: nothing cleared\n // it when a version was revoked, so the Upgrade button pointed at bytes the\n // kill switch had stopped. Every writer of the mirror now derives it from\n // `newestInstallableVersion`, and this card holds the revocation document\n // ALREADY — it is right there, feeding the disabled chip below — so it\n // cross-checks rather than trusting a cache written by a separate,\n // non-transactional write it can observe mid-flight.\n const latestByListing = useMemo(() => {\n const map: Record<string, string> = {}\n for (const listing of (listingDocs as any[]) ?? []) {\n const revocation = ((revocationDocs as any[]) ?? []).find(\n (doc) => doc.$id === listing.$id,\n )\n map[listing.$id] = offeredPluginVersion(listing, revocation) ?? ''\n }\n return map\n }, [listingDocs, revocationDocs])\n\n const handleUpgrade = useCallback(\n (install: any) => async () => {\n setBusy(install.$id)\n try {\n const response = await authorizedFetch(\n user,\n '/api/marketplace/install-plugin',\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ hostId, listingId: install.$id }),\n },\n )\n const payload = await response.json().catch(() => ({}))\n if (response.ok) {\n enqueueSnackbar(`Upgraded to ${payload.version}`, {\n variant: 'success',\n persist: false,\n })\n } else {\n // An installs lockdown refuses the upgrade route too (AGL-1532).\n const locked = parseLockdownRefusal(response.status, payload)\n if (locked) {\n enqueueSnackbar(lockdownRefusalText(locked), {\n variant: 'warning',\n persist: true,\n })\n } else {\n enqueueSnackbar(payload?.error ?? 'Upgrade failed', {\n variant: 'error',\n allowDuplicate: true,\n })\n }\n }\n } finally {\n setBusy(null)\n }\n },\n [hostId, user, enqueueSnackbar],\n )\n\n const requestPluginApi = useCallback(\n async (body: Record<string, unknown>) => {\n const response = await authorizedFetch(\n user,\n '/api/marketplace/install-plugin',\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ hostId, ...body }),\n },\n )\n const payload = await response.json().catch(() => ({}))\n if (!response.ok) {\n // Every install-class action in this card funnels through here\n // (share, org upgrade, uninstall at both scopes) — so does the\n // pause notice (AGL-1532).\n const locked = parseLockdownRefusal(response.status, payload)\n if (locked) {\n enqueueSnackbar(lockdownRefusalText(locked), {\n variant: 'warning',\n persist: true,\n })\n return null\n }\n enqueueSnackbar(payload?.error ?? 'Plugin operation failed', {\n variant: 'error',\n allowDuplicate: true,\n })\n return null\n }\n return payload\n },\n [hostId, user, enqueueSnackbar],\n )\n\n // Promote a host pin to the org tier (AGL-237): org pin first, then the\n // host pin is removed — host pins shadow org ones in the loader.\n const handleShareWithOrg = useCallback(\n (install: any) => async () => {\n setBusy(install.$id)\n try {\n const payload = await requestPluginApi({\n listingId: install.$id,\n scope: 'org',\n })\n if (!payload) return\n await deleteDoc(\n doc(firestore, 'hosts', hostId, 'installs', install.$id),\n )\n enqueueSnackbar('Plugin now installed for the whole organization', {\n variant: 'success',\n persist: false,\n })\n } finally {\n setBusy(null)\n }\n },\n [requestPluginApi, firestore, hostId, enqueueSnackbar],\n )\n\n const handleOrgUpgrade = useCallback(\n (install: any) => async () => {\n setBusy(install.$id)\n try {\n const payload = await requestPluginApi({\n listingId: install.$id,\n scope: 'org',\n })\n if (payload) {\n enqueueSnackbar(`Upgraded to ${payload.version}`, {\n variant: 'success',\n persist: false,\n })\n }\n } finally {\n setBusy(null)\n }\n },\n [requestPluginApi, enqueueSnackbar],\n )\n\n const handleOrgUninstall = useCallback(\n (install: any) => async () => {\n const confirmed = await confirm({\n title: `Uninstall \"${install.displayName ?? install.$id}\" org-wide?`,\n description:\n 'Every site in the organization loses this plugin unless it has ' +\n 'its own host-level pin. Data the plugin created stays untouched.',\n confirmationText: 'Uninstall',\n confirmationButtonProps: { color: 'error' },\n })\n .then(() => true)\n .catch(() => false)\n if (!confirmed) return\n const payload = await requestPluginApi({\n listingId: install.$id,\n scope: 'org',\n action: 'uninstall',\n })\n if (payload) {\n enqueueSnackbar('Plugin uninstalled org-wide', {\n variant: 'success',\n persist: false,\n })\n }\n },\n [confirm, requestPluginApi, enqueueSnackbar],\n )\n\n const handleUninstall = useCallback(\n (install: any) => async () => {\n const confirmed = await confirm({\n title: `Uninstall \"${install.displayName ?? install.$id}\"?`,\n description:\n 'Plugin elements pointing at it will show a placeholder until ' +\n 'reinstalled. Data the plugin created stays untouched.',\n confirmationText: 'Uninstall',\n confirmationButtonProps: { color: 'error' },\n })\n .then(() => true)\n .catch(() => false)\n if (!confirmed) return\n // Through the API (AGL-424) so the org enablement switchboard stays\n // in step with the removed pin.\n const payload = await requestPluginApi({\n listingId: install.$id,\n action: 'uninstall',\n })\n if (payload) {\n enqueueSnackbar('Plugin uninstalled', {\n variant: 'success',\n persist: false,\n })\n }\n },\n [confirm, requestPluginApi, enqueueSnackbar],\n )\n\n return (\n <CardDisplay\n header=\"Marketplace installs\"\n help={pluginDocsHelp('plugins', { anchor: '#install--upgrade' })}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={1.5}>\n {/* First-party plugins used to be re-listed here for context; that\n moved out (AGL-802) now that the switchboard sits directly above\n this card in Marketplace › Installed. This card is only the\n marketplace install pins. */}\n {/* \"Nothing installed\" has to mean nothing at either scope. This\n asked only about HOST pins, so an org-wide install printed the\n empty-state sentence directly above the row it was denying\n (AGL-1011) — most visible at org scope, which is where installs\n usually land org-wide. */}\n {installs.length === 0 && orgInstalls.length === 0 ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'No marketplace plugins installed. Install one from the ' +\n 'marketplace, then add a Plugin element to a screen and set ' +\n 'its listing id.'}\n </Typography>\n ) : (\n installs.map((install) => {\n const latest = latestByListing[install.$id]\n const canUpgrade =\n (compareArtifactVersions(install.version, latest) ?? 0) < 0\n const revoked = revokedByListing[install.$id]\n const capabilities = install.manifest?.capabilities ?? {}\n return (\n <Stack\n key={install.$id}\n spacing={0.5}\n sx={{\n border: '1px solid',\n borderColor: 'divider',\n borderRadius: 1,\n p: 1.5,\n }}\n >\n <Stack\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center' }}\n >\n <Typography variant=\"body2\" sx={{ flex: 1 }} noWrap>\n {installationHref(install.$id) ? (\n <AppLink\n href={installationHref(install.$id)}\n color=\"inherit\"\n underline=\"hover\"\n >\n {install.displayName ?? install.pluginId ?? install.$id}\n </AppLink>\n ) : (\n (install.displayName ?? install.pluginId ?? install.$id)\n )}\n </Typography>\n <Chip size=\"small\" label={`v${install.version}`} />\n {revoked ? (\n <Chip size=\"small\" color=\"error\" label=\"disabled\" />\n ) : null}\n {canUpgrade ? (\n <Button\n size=\"small\"\n disabled={busy === install.$id}\n onClick={handleUpgrade(install)}\n >\n {`Upgrade to v${latest}`}\n </Button>\n ) : null}\n {orgId &&\n !orgInstalls.some((entry) => entry.$id === install.$id) ? (\n <Tooltip title=\"Install for every site in the organization and remove this site's own pin\">\n <Button\n size=\"small\"\n disabled={busy === install.$id}\n onClick={handleShareWithOrg(install)}\n >\n {'Share with org'}\n </Button>\n </Tooltip>\n ) : null}\n <Button\n size=\"small\"\n color=\"error\"\n onClick={handleUninstall(install)}\n >\n {'Uninstall'}\n </Button>\n </Stack>\n <Typography variant=\"caption\" color=\"text.secondary\" noWrap>\n {`id: ${install.$id}`}\n </Typography>\n {(capabilities.network?.length ||\n capabilities.events?.length) && (\n <Tooltip title=\"Declared in the plugin manifest, enforced by the sandbox\">\n <Typography variant=\"caption\" color=\"text.secondary\">\n {[\n capabilities.network?.length\n ? `network: ${capabilities.network.join(', ')}`\n : null,\n capabilities.events?.length\n ? `events: ${capabilities.events.join(', ')}`\n : null,\n ]\n .filter(Boolean)\n .join(' · ')}\n </Typography>\n </Tooltip>\n )}\n {revoked ? (\n <Alert severity=\"error\" sx={{ mt: 0.5 }}>\n {'The platform disabled this plugin version. It renders ' +\n 'a placeholder on your site until you upgrade or ' +\n 'uninstall it.'}\n {/*\n The reason staff were required to type, shown to the\n person it happened to (AGL-2328). Rendered only when one\n was recorded — a revocation written before the reason\n was required says so, rather than showing an empty\n quotation that reads as \"no reason given\".\n */}\n {revocationDetailByListing[install.$id]?.reason ? (\n <Typography variant=\"body2\" sx={{ mt: 1 }}>\n {`Reason: ${revocationDetailByListing[install.$id]?.reason}`}\n </Typography>\n ) : null}\n {revocationDetailByListing[install.$id]?.atMs ? (\n <Typography variant=\"caption\" sx={{ display: 'block', mt: 0.5 }}>\n {`Disabled ${new Date(\n revocationDetailByListing[install.$id]\n ?.atMs as number,\n ).toLocaleDateString()}`}\n </Typography>\n ) : null}\n </Alert>\n ) : null}\n </Stack>\n )\n })\n )}\n {orgInstalls.map((install) => {\n const latest = latestByListing[install.$id]\n const canUpgrade =\n (compareArtifactVersions(install.version, latest) ?? 0) < 0\n const shadowed = installs.some((entry) => entry.$id === install.$id)\n return (\n <Stack\n key={`org-${install.$id}`}\n spacing={0.5}\n sx={{\n border: '1px solid',\n borderColor: 'divider',\n borderRadius: 1,\n p: 1.5,\n }}\n >\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Typography variant=\"body2\" sx={{ flex: 1 }} noWrap>\n {installationHref(install.$id) ? (\n <AppLink\n href={installationHref(install.$id)}\n color=\"inherit\"\n underline=\"hover\"\n >\n {install.displayName ?? install.pluginId ?? install.$id}\n </AppLink>\n ) : (\n (install.displayName ?? install.pluginId ?? install.$id)\n )}\n </Typography>\n <Chip size=\"small\" color=\"primary\" label=\"Organization\" />\n <Chip size=\"small\" label={`v${install.version}`} />\n {shadowed ? (\n <Tooltip title=\"This site has its own pin, which takes precedence here\">\n <Chip size=\"small\" variant=\"outlined\" label=\"shadowed\" />\n </Tooltip>\n ) : null}\n {canUpgrade ? (\n <Button\n size=\"small\"\n disabled={busy === install.$id}\n onClick={handleOrgUpgrade(install)}\n >\n {`Upgrade to v${latest}`}\n </Button>\n ) : null}\n <Button\n size=\"small\"\n color=\"error\"\n onClick={handleOrgUninstall(install)}\n >\n {'Uninstall'}\n </Button>\n </Stack>\n <Typography variant=\"caption\" color=\"text.secondary\" noWrap>\n {`id: ${install.$id} · shared with every site in the org`}\n </Typography>\n </Stack>\n )\n })}\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`Place a plugin on a screen with the Plugin element (component id ` +\n `\"${PLUGIN_COMPONENT_ID}\"), set to the listing id above.`}\n </Typography>\n </Stack>\n </CardDisplay>\n )\n}\nHostPluginsCard.displayName = 'HostPluginsCard'\n\nexport default HostPluginsCard\n"],"names":["buildRoute","PLUGIN_COMPONENT_ID","pluginDocsHelp","Route","AppLink","CardDisplay","useConfirmationContext","useSnackbar","Alert","Button","Chip","Stack","Tooltip","Typography","collection","deleteDoc","doc","documentId","limit","query","where","useCallback","useMemo","useState","compareArtifactVersions","isPluginRevoked","lockdownRefusalText","offeredPluginVersion","parseLockdownRefusal","useFirestore","useFirestoreCollection","useHostOrgId","useUser","authorizedFetch","HostPluginsCard","props","hostId","orgSlug","installationHref","listingId","ORG_PLUGIN_INSTALLATION","pluginRef","undefined","firestore","data","user","enqueueSnackbar","confirm","busy","setBusy","orgId","installDocs","idField","orgInstallDocs","orgInstalls","installs","listingIds","Set","map","install","$id","slice","listingDocs","length","join","revocationDocs","revokedByListing","revocation","find","version","revocationDetailByListing","at","revokedAt","reason","trim","atMs","toMillis","seconds","latestByListing","listing","handleUpgrade","response","method","headers","body","JSON","stringify","payload","json","catch","ok","variant","persist","locked","status","error","allowDuplicate","requestPluginApi","handleShareWithOrg","scope","handleOrgUpgrade","handleOrgUninstall","confirmed","title","displayName","description","confirmationText","confirmationButtonProps","color","then","action","handleUninstall","header","help","anchor","contentGutterX","contentGutterY","spacing","capabilities","latest","canUpgrade","revoked","manifest","sx","border","borderColor","borderRadius","p","direction","alignItems","flex","noWrap","href","underline","pluginId","size","label","disabled","onClick","some","entry","network","events","filter","Boolean","severity","mt","display","Date","toLocaleDateString","shadowed"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/components/host-plugins-card.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport {\n buildRoute,\n PLUGIN_COMPONENT_ID,\n pluginDocsHelp,\n Route,\n} from '@aglyn/aglyn'\nimport {\n AppLink,\n CardDisplay,\n useConfirmationContext,\n} from '@aglyn/shared-ui-jsx'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport { Alert, Button, Chip, Stack, Tooltip, Typography } from '@mui/material'\nimport {\n collection,\n doc,\n documentId,\n limit,\n query,\n where,\n} from 'firebase/firestore'\nimport { useCallback, useMemo, useState } from 'react'\nimport {\n compareArtifactVersions,\n isPluginRevoked,\n lockdownRefusalText,\n offeredPluginVersion,\n parseLockdownRefusal,\n} from '@aglyn/aglyn'\nimport {\n useFirestore,\n useFirestoreCollection,\n useHostOrgId,\n useUser,\n} from '@aglyn/tenant-feature-instance'\nimport { writeSiteWideChange } from '@aglyn/tenant-feature-instance/hooks/helpers/site-wide-change'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\n\nexport interface HostPluginsCardProps {\n hostId: string\n /**\n * Org slug from the URL, when this card is rendered at org scope. With it,\n * each row links through to that installation's own page (AGL-1007);\n * without it the rows stay plain text rather than linking to nowhere.\n */\n orgSlug?: string\n}\n\n/**\n * Installed marketplace plugins (AGL-45): lists the host's pinned plugin\n * installs with their capabilities, flags available upgrades (latest\n * listing version ≠ pinned) and platform kill switches (public\n * `revocations` read), and offers explicit upgrade/uninstall. Upgrades go\n * through `/api/marketplace/install-plugin` so the new pin is validated\n * server-side; uninstall removes the host install doc. Place the installed\n * plugin on a screen with the Plugin element (listing id).\n */\nexport function HostPluginsCard(props: HostPluginsCardProps) {\n const { hostId, orgSlug } = props\n // One row → one installation page (AGL-1007). The name is the link, so a\n // row stays clickable without swallowing its own action buttons.\n const installationHref = (listingId: string) =>\n orgSlug\n ? buildRoute(Route.ORG_PLUGIN_INSTALLATION, {\n orgSlug,\n pluginRef: listingId,\n })\n : undefined\n const firestore = useFirestore()\n const { data: user } = useUser()\n const { enqueueSnackbar } = useSnackbar()\n const { confirm } = useConfirmationContext()\n const [busy, setBusy] = useState<string | null>(null)\n\n const orgId = useHostOrgId(hostId)\n const { data: installDocs } = useFirestoreCollection<any>(\n () => query(collection(firestore, 'hosts', hostId, 'installs'), limit(50)),\n [firestore, hostId],\n { idField: '$id' },\n )\n // Org-tier pins (AGL-237): apply to every host in the org; a host pin\n // of the same listing shadows the org one. Held at null while\n // `useHostOrgId` is in flight, never `orgs/-pending-` (AGL-1440):\n // installs are member-gated, so the sentinel was a guaranteed-denied\n // listen on every mount of this card.\n const { data: orgInstallDocs } = useFirestoreCollection<any>(\n () =>\n orgId\n ? query(collection(firestore, 'orgs', orgId, 'installs'), limit(50))\n : null,\n [firestore, orgId],\n { idField: '$id' },\n )\n const orgInstalls = useMemo(\n () => [...((orgInstallDocs as any[]) ?? [])],\n [orgInstallDocs],\n )\n const installs = useMemo(\n () => [...((installDocs as any[]) ?? [])],\n [installDocs],\n )\n const listingIds = useMemo(\n () =>\n [\n ...new Set([\n ...installs.map((install) => install.$id),\n ...orgInstalls.map((install) => install.$id),\n ]),\n ].slice(0, 10),\n [installs, orgInstalls],\n )\n\n // Latest published version per listing (upgrade detection) — public read.\n const { data: listingDocs } = useFirestoreCollection<any>(\n () =>\n query(\n collection(firestore, 'marketplaceListings'),\n where(documentId(), 'in', listingIds.length ? listingIds : ['-none-']),\n ),\n [firestore, listingIds.join(',')],\n { idField: '$id' },\n )\n // Kill switches (public revocations read) for the installed listings.\n const { data: revocationDocs } = useFirestoreCollection<any>(\n () =>\n query(\n collection(firestore, 'revocations'),\n where(documentId(), 'in', listingIds.length ? listingIds : ['-none-']),\n ),\n [firestore, listingIds.join(',')],\n { idField: '$id' },\n )\n const revokedByListing = useMemo(() => {\n const map: Record<string, boolean> = {}\n for (const install of installs) {\n const revocation = ((revocationDocs as any[]) ?? []).find(\n (doc) => doc.$id === install.$id,\n )\n if (!revocation) continue\n // THE SHARED PREDICATE, not a fourth copy of it (AGL-2305). This card\n // carried a byte-for-byte duplicate of `isPluginRevoked`'s body, which\n // is precisely what AGL-1085 collapsed three inline copies into one to\n // stop: a reader that disagrees with the writer about this document's\n // shape is how a kill switch silently stops killing. It happened to be\n // correct; being correct today is not the property that matters.\n map[install.$id] = isPluginRevoked(revocation, install.version)\n }\n return map\n }, [installs, revocationDocs])\n\n /*==========================================\n * WHY IT WAS DISABLED (AGL-2328).\n *\n * Staff are REQUIRED to type a reason to revoke a plugin version — the\n * route refuses without one — and until now the customer whose site the\n * plugin stopped working on was shown a bare \"disabled\" chip and a generic\n * paragraph. The reason was stored, in the same document this card already\n * reads, one field away from the flag it renders. Every consumer of\n * `revocations` reads `versions` and nothing else; the type's own comment\n * calls `versions` \"the ONLY field any reader consults\", which was true and\n * is the defect.\n *\n * Only the REASON and the DATE are surfaced. `revokedBy` is a staff uid and\n * belongs in the audit log, not on a customer's screen.\n *=========================================*/\n const revocationDetailByListing = useMemo(() => {\n const map: Record<string, { reason: string | null; atMs: number | null }> =\n {}\n for (const doc of ((revocationDocs as any[]) ?? [])) {\n const at = doc.revokedAt\n map[doc.$id] = {\n reason:\n typeof doc.reason === 'string' && doc.reason.trim()\n ? doc.reason.trim()\n : null,\n atMs:\n at?.toMillis?.() ?? (typeof at?.seconds === 'number' ? at.seconds * 1000 : null),\n }\n }\n return map\n }, [revocationDocs])\n\n // The newest INSTALLABLE version, not merely the newest approved one\n // (AGL-1016, corrected by AGL-2368).\n //\n // Offering `latestVersion` here put an Upgrade button in front of every\n // workspace the moment a publisher uploaded, and the install route then\n // refused the click — AGL-966 only ever hands out reviewed bytes. The\n // mirror fixed that and then reproduced it one state over: nothing cleared\n // it when a version was revoked, so the Upgrade button pointed at bytes the\n // kill switch had stopped. Every writer of the mirror now derives it from\n // `newestInstallableVersion`, and this card holds the revocation document\n // ALREADY — it is right there, feeding the disabled chip below — so it\n // cross-checks rather than trusting a cache written by a separate,\n // non-transactional write it can observe mid-flight.\n const latestByListing = useMemo(() => {\n const map: Record<string, string> = {}\n for (const listing of (listingDocs as any[]) ?? []) {\n const revocation = ((revocationDocs as any[]) ?? []).find(\n (doc) => doc.$id === listing.$id,\n )\n map[listing.$id] = offeredPluginVersion(listing, revocation) ?? ''\n }\n return map\n }, [listingDocs, revocationDocs])\n\n const handleUpgrade = useCallback(\n (install: any) => async () => {\n setBusy(install.$id)\n try {\n const response = await authorizedFetch(\n user,\n '/api/marketplace/install-plugin',\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ hostId, listingId: install.$id }),\n },\n )\n const payload = await response.json().catch(() => ({}))\n if (response.ok) {\n enqueueSnackbar(`Upgraded to ${payload.version}`, {\n variant: 'success',\n persist: false,\n })\n } else {\n // An installs lockdown refuses the upgrade route too (AGL-1532).\n const locked = parseLockdownRefusal(response.status, payload)\n if (locked) {\n enqueueSnackbar(lockdownRefusalText(locked), {\n variant: 'warning',\n persist: true,\n })\n } else {\n enqueueSnackbar(payload?.error ?? 'Upgrade failed', {\n variant: 'error',\n allowDuplicate: true,\n })\n }\n }\n } finally {\n setBusy(null)\n }\n },\n [hostId, user, enqueueSnackbar],\n )\n\n const requestPluginApi = useCallback(\n async (body: Record<string, unknown>) => {\n const response = await authorizedFetch(\n user,\n '/api/marketplace/install-plugin',\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ hostId, ...body }),\n },\n )\n const payload = await response.json().catch(() => ({}))\n if (!response.ok) {\n // Every install-class action in this card funnels through here\n // (share, org upgrade, uninstall at both scopes) — so does the\n // pause notice (AGL-1532).\n const locked = parseLockdownRefusal(response.status, payload)\n if (locked) {\n enqueueSnackbar(lockdownRefusalText(locked), {\n variant: 'warning',\n persist: true,\n })\n return null\n }\n enqueueSnackbar(payload?.error ?? 'Plugin operation failed', {\n variant: 'error',\n allowDuplicate: true,\n })\n return null\n }\n return payload\n },\n [hostId, user, enqueueSnackbar],\n )\n\n // Promote a host pin to the org tier (AGL-237): org pin first, then the\n // host pin is removed — host pins shadow org ones in the loader.\n const handleShareWithOrg = useCallback(\n (install: any) => async () => {\n setBusy(install.$id)\n try {\n const payload = await requestPluginApi({\n listingId: install.$id,\n scope: 'org',\n })\n if (!payload) return\n await writeSiteWideChange({\n firestore,\n user,\n hostId,\n write: (batch) =>\n batch.delete(\n doc(firestore, 'hosts', hostId, 'installs', install.$id),\n ),\n })\n enqueueSnackbar('Plugin now installed for the whole organization', {\n variant: 'success',\n persist: false,\n })\n } finally {\n setBusy(null)\n }\n },\n [requestPluginApi, firestore, hostId, user, enqueueSnackbar],\n )\n\n const handleOrgUpgrade = useCallback(\n (install: any) => async () => {\n setBusy(install.$id)\n try {\n const payload = await requestPluginApi({\n listingId: install.$id,\n scope: 'org',\n })\n if (payload) {\n enqueueSnackbar(`Upgraded to ${payload.version}`, {\n variant: 'success',\n persist: false,\n })\n }\n } finally {\n setBusy(null)\n }\n },\n [requestPluginApi, enqueueSnackbar],\n )\n\n const handleOrgUninstall = useCallback(\n (install: any) => async () => {\n const confirmed = await confirm({\n title: `Uninstall \"${install.displayName ?? install.$id}\" org-wide?`,\n description:\n 'Every site in the organization loses this plugin unless it has ' +\n 'its own host-level pin. Data the plugin created stays untouched.',\n confirmationText: 'Uninstall',\n confirmationButtonProps: { color: 'error' },\n })\n .then(() => true)\n .catch(() => false)\n if (!confirmed) return\n const payload = await requestPluginApi({\n listingId: install.$id,\n scope: 'org',\n action: 'uninstall',\n })\n if (payload) {\n enqueueSnackbar('Plugin uninstalled org-wide', {\n variant: 'success',\n persist: false,\n })\n }\n },\n [confirm, requestPluginApi, enqueueSnackbar],\n )\n\n const handleUninstall = useCallback(\n (install: any) => async () => {\n const confirmed = await confirm({\n title: `Uninstall \"${install.displayName ?? install.$id}\"?`,\n description:\n 'Plugin elements pointing at it will show a placeholder until ' +\n 'reinstalled. Data the plugin created stays untouched.',\n confirmationText: 'Uninstall',\n confirmationButtonProps: { color: 'error' },\n })\n .then(() => true)\n .catch(() => false)\n if (!confirmed) return\n // Through the API (AGL-424) so the org enablement switchboard stays\n // in step with the removed pin.\n const payload = await requestPluginApi({\n listingId: install.$id,\n action: 'uninstall',\n })\n if (payload) {\n enqueueSnackbar('Plugin uninstalled', {\n variant: 'success',\n persist: false,\n })\n }\n },\n [confirm, requestPluginApi, enqueueSnackbar],\n )\n\n return (\n <CardDisplay\n header=\"Marketplace installs\"\n help={pluginDocsHelp('plugins', { anchor: '#install--upgrade' })}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={1.5}>\n {/* First-party plugins used to be re-listed here for context; that\n moved out (AGL-802) now that the switchboard sits directly above\n this card in Marketplace › Installed. This card is only the\n marketplace install pins. */}\n {/* \"Nothing installed\" has to mean nothing at either scope. This\n asked only about HOST pins, so an org-wide install printed the\n empty-state sentence directly above the row it was denying\n (AGL-1011) — most visible at org scope, which is where installs\n usually land org-wide. */}\n {installs.length === 0 && orgInstalls.length === 0 ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'No marketplace plugins installed. Install one from the ' +\n 'marketplace, then add a Plugin element to a screen and set ' +\n 'its listing id.'}\n </Typography>\n ) : (\n installs.map((install) => {\n const latest = latestByListing[install.$id]\n const canUpgrade =\n (compareArtifactVersions(install.version, latest) ?? 0) < 0\n const revoked = revokedByListing[install.$id]\n const capabilities = install.manifest?.capabilities ?? {}\n return (\n <Stack\n key={install.$id}\n spacing={0.5}\n sx={{\n border: '1px solid',\n borderColor: 'divider',\n borderRadius: 1,\n p: 1.5,\n }}\n >\n <Stack\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center' }}\n >\n <Typography variant=\"body2\" sx={{ flex: 1 }} noWrap>\n {installationHref(install.$id) ? (\n <AppLink\n href={installationHref(install.$id)}\n color=\"inherit\"\n underline=\"hover\"\n >\n {install.displayName ?? install.pluginId ?? install.$id}\n </AppLink>\n ) : (\n (install.displayName ?? install.pluginId ?? install.$id)\n )}\n </Typography>\n <Chip size=\"small\" label={`v${install.version}`} />\n {revoked ? (\n <Chip size=\"small\" color=\"error\" label=\"disabled\" />\n ) : null}\n {canUpgrade ? (\n <Button\n size=\"small\"\n disabled={busy === install.$id}\n onClick={handleUpgrade(install)}\n >\n {`Upgrade to v${latest}`}\n </Button>\n ) : null}\n {orgId &&\n !orgInstalls.some((entry) => entry.$id === install.$id) ? (\n <Tooltip title=\"Install for every site in the organization and remove this site's own pin\">\n <Button\n size=\"small\"\n disabled={busy === install.$id}\n onClick={handleShareWithOrg(install)}\n >\n {'Share with org'}\n </Button>\n </Tooltip>\n ) : null}\n <Button\n size=\"small\"\n color=\"error\"\n onClick={handleUninstall(install)}\n >\n {'Uninstall'}\n </Button>\n </Stack>\n <Typography variant=\"caption\" color=\"text.secondary\" noWrap>\n {`id: ${install.$id}`}\n </Typography>\n {(capabilities.network?.length ||\n capabilities.events?.length) && (\n <Tooltip title=\"Declared in the plugin manifest, enforced by the sandbox\">\n <Typography variant=\"caption\" color=\"text.secondary\">\n {[\n capabilities.network?.length\n ? `network: ${capabilities.network.join(', ')}`\n : null,\n capabilities.events?.length\n ? `events: ${capabilities.events.join(', ')}`\n : null,\n ]\n .filter(Boolean)\n .join(' · ')}\n </Typography>\n </Tooltip>\n )}\n {revoked ? (\n <Alert severity=\"error\" sx={{ mt: 0.5 }}>\n {'The platform disabled this plugin version. It renders ' +\n 'a placeholder on your site until you upgrade or ' +\n 'uninstall it.'}\n {/*\n The reason staff were required to type, shown to the\n person it happened to (AGL-2328). Rendered only when one\n was recorded — a revocation written before the reason\n was required says so, rather than showing an empty\n quotation that reads as \"no reason given\".\n */}\n {revocationDetailByListing[install.$id]?.reason ? (\n <Typography variant=\"body2\" sx={{ mt: 1 }}>\n {`Reason: ${revocationDetailByListing[install.$id]?.reason}`}\n </Typography>\n ) : null}\n {revocationDetailByListing[install.$id]?.atMs ? (\n <Typography variant=\"caption\" sx={{ display: 'block', mt: 0.5 }}>\n {`Disabled ${new Date(\n revocationDetailByListing[install.$id]\n ?.atMs as number,\n ).toLocaleDateString()}`}\n </Typography>\n ) : null}\n </Alert>\n ) : null}\n </Stack>\n )\n })\n )}\n {orgInstalls.map((install) => {\n const latest = latestByListing[install.$id]\n const canUpgrade =\n (compareArtifactVersions(install.version, latest) ?? 0) < 0\n const shadowed = installs.some((entry) => entry.$id === install.$id)\n return (\n <Stack\n key={`org-${install.$id}`}\n spacing={0.5}\n sx={{\n border: '1px solid',\n borderColor: 'divider',\n borderRadius: 1,\n p: 1.5,\n }}\n >\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Typography variant=\"body2\" sx={{ flex: 1 }} noWrap>\n {installationHref(install.$id) ? (\n <AppLink\n href={installationHref(install.$id)}\n color=\"inherit\"\n underline=\"hover\"\n >\n {install.displayName ?? install.pluginId ?? install.$id}\n </AppLink>\n ) : (\n (install.displayName ?? install.pluginId ?? install.$id)\n )}\n </Typography>\n <Chip size=\"small\" color=\"primary\" label=\"Organization\" />\n <Chip size=\"small\" label={`v${install.version}`} />\n {shadowed ? (\n <Tooltip title=\"This site has its own pin, which takes precedence here\">\n <Chip size=\"small\" variant=\"outlined\" label=\"shadowed\" />\n </Tooltip>\n ) : null}\n {canUpgrade ? (\n <Button\n size=\"small\"\n disabled={busy === install.$id}\n onClick={handleOrgUpgrade(install)}\n >\n {`Upgrade to v${latest}`}\n </Button>\n ) : null}\n <Button\n size=\"small\"\n color=\"error\"\n onClick={handleOrgUninstall(install)}\n >\n {'Uninstall'}\n </Button>\n </Stack>\n <Typography variant=\"caption\" color=\"text.secondary\" noWrap>\n {`id: ${install.$id} · shared with every site in the org`}\n </Typography>\n </Stack>\n )\n })}\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`Place a plugin on a screen with the Plugin element (component id ` +\n `\"${PLUGIN_COMPONENT_ID}\"), set to the listing id above.`}\n </Typography>\n </Stack>\n </CardDisplay>\n )\n}\nHostPluginsCard.displayName = 'HostPluginsCard'\n\nexport default HostPluginsCard\n"],"names":["buildRoute","PLUGIN_COMPONENT_ID","pluginDocsHelp","Route","AppLink","CardDisplay","useConfirmationContext","useSnackbar","Alert","Button","Chip","Stack","Tooltip","Typography","collection","doc","documentId","limit","query","where","useCallback","useMemo","useState","compareArtifactVersions","isPluginRevoked","lockdownRefusalText","offeredPluginVersion","parseLockdownRefusal","useFirestore","useFirestoreCollection","useHostOrgId","useUser","writeSiteWideChange","authorizedFetch","HostPluginsCard","props","hostId","orgSlug","installationHref","listingId","ORG_PLUGIN_INSTALLATION","pluginRef","undefined","firestore","data","user","enqueueSnackbar","confirm","busy","setBusy","orgId","installDocs","idField","orgInstallDocs","orgInstalls","installs","listingIds","Set","map","install","$id","slice","listingDocs","length","join","revocationDocs","revokedByListing","revocation","find","version","revocationDetailByListing","at","revokedAt","reason","trim","atMs","toMillis","seconds","latestByListing","listing","handleUpgrade","response","method","headers","body","JSON","stringify","payload","json","catch","ok","variant","persist","locked","status","error","allowDuplicate","requestPluginApi","handleShareWithOrg","scope","write","batch","delete","handleOrgUpgrade","handleOrgUninstall","confirmed","title","displayName","description","confirmationText","confirmationButtonProps","color","then","action","handleUninstall","header","help","anchor","contentGutterX","contentGutterY","spacing","capabilities","latest","canUpgrade","revoked","manifest","sx","border","borderColor","borderRadius","p","direction","alignItems","flex","noWrap","href","underline","pluginId","size","label","disabled","onClick","some","entry","network","events","filter","Boolean","severity","mt","display","Date","toLocaleDateString","shadowed"],"mappings":"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"}
@@ -37,6 +37,10 @@ import { type PluginApiHandler } from '@aglyn/aglyn/server';
37
37
  *
38
38
  * `preview` changes nothing and returns what the swap would do, so the
39
39
  * confirmation can name it rather than showing a JSON blob.
40
+ *
41
+ * Every action that writes the theme then drops the site's cached pages
42
+ * (AGL-3386). The theme styles every page and nothing publishes it, so
43
+ * without the drop "the site repaints" meant "within the hour".
40
44
  */
41
45
  export declare const installThemeHandler: PluginApiHandler;
42
46
  export default installThemeHandler;
@@ -15,6 +15,7 @@
15
15
  * limitations under the License.
16
16
  */ import { _ as _extends } from "@swc/helpers/_/_extends";
17
17
  import { describeTheme } from "@aglyn/aglyn/app-utils/marketplace-theme";
18
+ import { dropPluginSiteCache } from "@aglyn/aglyn/plugin-manager/plugin-site-cache";
18
19
  import { firebaseAdmin } from "@aglyn/tenant-data-admin";
19
20
  import { resolveOrgPermissions } from "@aglyn/tenant-runtime/org-permissions";
20
21
  import { isPrivateListing, listingArtifactType } from "../model/marketplace.js";
@@ -45,6 +46,10 @@ import { isPublisherSecurityLocked } from "./sale-risk.js";
45
46
  *
46
47
  * `preview` changes nothing and returns what the swap would do, so the
47
48
  * confirmation can name it rather than showing a JSON blob.
49
+ *
50
+ * Every action that writes the theme then drops the site's cached pages
51
+ * (AGL-3386). The theme styles every page and nothing publishes it, so
52
+ * without the drop "the site repaints" meant "within the hour".
48
53
  */ export const installThemeHandler = async (req, res)=>{
49
54
  var _ref, _ref1, _req_headers_authorization;
50
55
  var _req_body, _req_body1, _req_body2;
@@ -99,6 +104,15 @@ import { isPublisherSecurityLocked } from "./sale-risk.js";
99
104
  }
100
105
  const now = firebaseAdmin.firestore.FieldValue.serverTimestamp();
101
106
  const currentTheme = (_hostSnapshot_get1 = hostSnapshot.get('theme')) != null ? _hostSnapshot_get1 : null;
107
+ // Awaited before the response, because a serverless function may be
108
+ // frozen once it has answered. Never throws: a drop that fails leaves the
109
+ // theme saved and the hour-long cache as the backstop.
110
+ const repaintLiveSite = (action)=>dropPluginSiteCache({
111
+ hostIds: [
112
+ hostId
113
+ ],
114
+ reason: `marketplace theme ${action}`
115
+ });
102
116
  // ---- reset: back to the platform default ----
103
117
  if (action === 'reset') {
104
118
  await hostRef.set({
@@ -116,6 +130,7 @@ import { isPublisherSecurityLocked } from "./sale-risk.js";
116
130
  }, {
117
131
  merge: true
118
132
  });
133
+ await repaintLiveSite('reset');
119
134
  return res.status(200).json({
120
135
  reset: true
121
136
  });
@@ -131,6 +146,7 @@ import { isPublisherSecurityLocked } from "./sale-risk.js";
131
146
  }, {
132
147
  merge: true
133
148
  });
149
+ await repaintLiveSite('overrides cleared');
134
150
  return res.status(200).json({
135
151
  cleared: true
136
152
  });
@@ -160,6 +176,7 @@ import { isPublisherSecurityLocked } from "./sale-risk.js";
160
176
  }), {
161
177
  merge: true
162
178
  });
179
+ await repaintLiveSite('reverted');
163
180
  return res.status(200).json({
164
181
  reverted: true
165
182
  });
@@ -287,6 +304,7 @@ import { isPublisherSecurityLocked } from "./sale-risk.js";
287
304
  }, {
288
305
  merge: true
289
306
  });
307
+ await repaintLiveSite('installed');
290
308
  await recordVersionMove({
291
309
  firestore,
292
310
  listingRef,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/server/install-theme.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { type PluginApiHandler } from '@aglyn/aglyn/server'\nimport { describeTheme } from '@aglyn/aglyn/app-utils/marketplace-theme'\nimport { firebaseAdmin } from '@aglyn/tenant-data-admin'\nimport { resolveOrgPermissions } from '@aglyn/tenant-runtime/org-permissions'\nimport {\n isPrivateListing,\n listingArtifactType,\n} from '../model/marketplace'\nimport { canActAsPublisher } from './publisher-profile'\nimport { requirePurchase } from './purchase-entitlement'\nimport { hasDivergedFromBase, recordInstallProvenance } from './provenance'\nimport { recordVersionMove } from './version-stats'\nimport { isPublisherSecurityLocked } from './sale-risk'\n\n/**\n * Installs a marketplace theme onto a site (AGL-1020).\n *\n * Themes are the exception to \"installing never changes a running site\". A\n * template or a layout can land inert in a library because it is one thing\n * among many; a theme IS the site's appearance, and installing one into a\n * library where it does nothing would be a control that appears to work and\n * does not. So this writes `hosts/{hostId}.theme` and the site repaints.\n *\n * Which makes reversibility the requirement, not a nicety. Two things provide\n * it, and neither is a confirmation dialog:\n *\n * * The theme being replaced is kept verbatim in `themeReplaced`, so `revert`\n * restores exactly what was there — including a hand-built theme that was\n * never a marketplace artifact and exists nowhere else.\n * * `reset` clears the theme entirely, which is the \"use the default theme\"\n * the issue asks to keep first-class. It is a distinct action from revert\n * because \"back to how it was\" and \"back to stock\" are different intents and\n * guessing between them is how people lose work.\n *\n * `preview` changes nothing and returns what the swap would do, so the\n * confirmation can name it rather than showing a JSON blob.\n */\nexport const installThemeHandler: PluginApiHandler = async (req, res) => {\n if (req.method !== 'POST') {\n return res.status(405).json({ error: 'Method not allowed' })\n }\n const hostId = String(req.body?.hostId ?? '')\n const action = ['revert', 'reset', 'preview', 'clear-overrides'].includes(\n req.body?.action,\n )\n ? (req.body.action as\n | 'revert'\n | 'reset'\n | 'preview'\n | 'clear-overrides')\n : 'install'\n const listingId = String(req.body?.listingId ?? '')\n if (!hostId || (action === 'install' && !listingId)) {\n return res.status(400).json({ error: 'Missing listingId or hostId' })\n }\n const authorization = String(req.headers.authorization ?? '')\n const idToken = authorization.startsWith('Bearer ')\n ? authorization.slice('Bearer '.length)\n : undefined\n if (!idToken) return res.status(401).json({ error: 'Unauthenticated' })\n\n try {\n const decoded = await firebaseAdmin.app().auth().verifyIdToken(idToken)\n const membership = await resolveOrgPermissions(decoded.uid, { hostId })\n if (!membership.permissions.installPlugins) {\n return res.status(403).json({\n error:\n 'Your organization role does not allow installing from the marketplace',\n })\n }\n const firestore = firebaseAdmin.app().firestore()\n const hostRef = firestore.collection('hosts').doc(hostId)\n const hostSnapshot = await hostRef.get()\n if (!hostSnapshot.exists) {\n return res.status(404).json({ error: 'Unknown site' })\n }\n const memberRole = (hostSnapshot.get('memberRoles') ?? {})[decoded.uid]\n if (memberRole !== 'admin' && memberRole !== 'editor') {\n return res.status(403).json({ error: 'Not a site admin or editor' })\n }\n const now = firebaseAdmin.firestore.FieldValue.serverTimestamp()\n const currentTheme = hostSnapshot.get('theme') ?? null\n\n // ---- reset: back to the platform default ----\n if (action === 'reset') {\n await hostRef.set(\n {\n // Kept, not dropped: resetting is reversible for the same reason\n // installing is, and someone who resets by mistake has otherwise\n // lost a theme that existed in no other document.\n themeReplaced: { theme: currentTheme, replacedAt: now },\n theme: firebaseAdmin.firestore.FieldValue.delete(),\n themeInstalledFrom: firebaseAdmin.firestore.FieldValue.delete(),\n themeOverride: firebaseAdmin.firestore.FieldValue.delete(),\n updatedAt: now,\n },\n { merge: true },\n )\n return res.status(200).json({ reset: true })\n }\n\n // ---- clear-overrides: drop this site's changes, keep the theme ----\n // The third way back, and the narrowest: \"reset to the publisher's\n // version\" is deleting the patch — the whole point of owning the patch\n // rather than the copy (AGL-1019). The theme itself is untouched.\n if (action === 'clear-overrides') {\n await hostRef.set(\n {\n themeOverride: firebaseAdmin.firestore.FieldValue.delete(),\n updatedAt: now,\n },\n { merge: true },\n )\n return res.status(200).json({ cleared: true })\n }\n\n // ---- revert: back to whatever this site had before the last swap ----\n if (action === 'revert') {\n const replaced = hostSnapshot.get('themeReplaced')\n if (!replaced) {\n return res.status(409).json({\n error: 'There is no previous theme to go back to on this site.',\n })\n }\n await hostRef.set(\n {\n theme: replaced.theme ?? firebaseAdmin.firestore.FieldValue.delete(),\n ...(replaced.installedFrom\n ? { themeInstalledFrom: replaced.installedFrom }\n : { themeInstalledFrom: firebaseAdmin.firestore.FieldValue.delete() }),\n // The overrides that were live alongside that theme come back with\n // it. Reverting the base and keeping a patch authored against the\n // theme being reverted FROM is the one combination nobody asked for.\n ...(replaced.override\n ? { themeOverride: replaced.override }\n : { themeOverride: firebaseAdmin.firestore.FieldValue.delete() }),\n themeReplaced: firebaseAdmin.firestore.FieldValue.delete(),\n updatedAt: now,\n },\n { merge: true },\n )\n return res.status(200).json({ reverted: true })\n }\n\n const listingRef = firestore.collection('marketplaceListings').doc(listingId)\n const listingSnapshot = await listingRef.get()\n const listing = listingSnapshot.data() as any\n if (\n !listing ||\n listing.deletedAt ||\n // Staff takedown blocks new installs on EVERY artifact type\n // (AGL-2290). AGL-948 extended takedown past plugins in the browse\n // predicate and in `resolveMarketplacePluginVersion`, but the gate that\n // decides whether content is HANDED OVER was only ever added to\n // `install-plugin.ts`. So a component, theme, template, layout, email\n // template or dataset schema that staff had taken down stayed\n // installable by anyone holding its listing id — which makes takedown a\n // suggestion for six of the seven artifact types.\n //\n // No owner exemption, matching `install-plugin.ts`: a takedown is a\n // moderation decision about the artifact, not about who is asking.\n listing.hiddenAt ||\n // A publisher under a SECURITY lock hands nothing over (AGL-3365).\n (await isPublisherSecurityLocked(firestore, listing.profileId)) ||\n listingArtifactType(listing) !== 'theme'\n ) {\n return res.status(404).json({ error: 'Unknown theme' })\n }\n\n const priceUsd = Number(listing.priceUsd ?? 0)\n const ownsListing = await canActAsPublisher(\n firestore,\n decoded.uid,\n listing.profileId,\n )\n // Private listings install ONLY for the owning org (AGL-2290).\n //\n // `install-plugin.ts` has carried this since AGL-968; the other six never\n // did, so a private component, theme, template, layout, email template or\n // dataset schema was installable by anyone who knew its listing id. Browse\n // hides them and the detail page 404s, but neither is a control — the\n // route is.\n if (isPrivateListing(listing) && !ownsListing) {\n return res.status(404).json({ error: 'Unknown listing' })\n }\n // A FULLY refunded purchase stops entitling (AGL-1546), and until\n // AGL-1699 only the component route knew that: this one asked whether a\n // purchase doc EXISTED, so buy/install/refund kept the artifact. The\n // predicate lives in one place now so the next route cannot miss it.\n const unpaid = await requirePurchase({\n firestore,\n buyerUid: decoded.uid,\n // THE ORG THE LICENCE HAS TO COVER (AGL-2331). `membership.orgId` is\n // resolved server-side from the caller's own membership by the\n // permission gate above — never a request-body field — so this is the\n // workspace the install actually lands in, and the only one a purchase\n // can entitle here.\n buyerOrgId: membership.orgId ?? '',\n listingId,\n priceUsd,\n ownsListing,\n })\n if (unpaid) return res.status(402).json(unpaid)\n\n const versionSnapshot = await listingRef\n .collection('versions')\n .doc(String(listing.latestVersion))\n .get()\n const theme = versionSnapshot.get('theme')\n if (!theme || !Object.keys(theme).length) {\n return res.status(500).json({ error: 'Theme version missing' })\n }\n\n // ---- preview: say what the swap changes, change nothing ----\n if (action === 'preview') {\n return res.status(200).json({\n incoming: describeTheme(theme),\n current: describeTheme(currentTheme),\n // A site with no theme is on the platform default, so there is nothing\n // to lose and the confirmation should not imply there is.\n replaces: Boolean(currentTheme && Object.keys(currentTheme).length),\n version: listing.latestVersion ?? null,\n })\n }\n\n // ---- install ----\n // Replacing a theme the workspace has edited destroys those edits (AGL-1018).\n // Unlike a component this is not recoverable by looking in a library — the\n // theme lives on one field on one document — so the refusal matters more.\n const diverged = await hasDivergedFromBase({\n firestore,\n sha256: hostSnapshot.get('themeInstalledFrom.sha256'),\n current: currentTheme,\n })\n if (diverged && req.body?.mode !== 'replace') {\n return res.status(409).json({\n error:\n 'This site’s theme has been edited since it was installed. Review ' +\n 'the update to see what would change.',\n diverged: true,\n })\n }\n\n const provenance = await recordInstallProvenance({\n firestore,\n listingId,\n listing,\n version: listing.latestVersion,\n artifactType: 'theme',\n content: theme,\n })\n const previousVersion =\n hostSnapshot.get('themeInstalledFrom.version') ?? null\n\n await hostRef.set(\n {\n theme,\n themeInstalledFrom: provenance.installedFrom,\n // Everything needed to put this site back exactly as it was, captured\n // BEFORE the write that changes it.\n themeReplaced: {\n theme: currentTheme,\n ...(hostSnapshot.get('themeInstalledFrom')\n ? { installedFrom: hostSnapshot.get('themeInstalledFrom') }\n : {}),\n ...(hostSnapshot.get('themeOverride')\n ? { override: hostSnapshot.get('themeOverride') }\n : {}),\n replacedAt: now,\n },\n // Overrides SURVIVE a theme swap (AGL-1021). A site that set its brand\n // colour still means it under a new theme, and silently discarding\n // that on install would be the more surprising of the two options.\n //\n // What must not happen is surviving *silently*: the override keeps the\n // `baseSha256` it was authored against, which no longer matches the\n // theme now installed, so `isOverrideForCurrentTheme` is false and the\n // editor says so with an offer to clear them. Rewriting that hash here\n // would launder a patch written for another theme into one that claims\n // to belong to this one.\n updatedAt: now,\n },\n { merge: true },\n )\n\n await recordVersionMove({\n firestore,\n listingRef,\n artifactType: 'theme',\n from: previousVersion,\n to: listing.latestVersion,\n })\n await listingRef\n .update({\n installCount: firebaseAdmin.firestore.FieldValue.increment(1),\n })\n .catch(() => undefined)\n\n return res.status(200).json({\n installed: true,\n version: listing.latestVersion ?? null,\n baseStored: provenance.baseStored,\n applied: describeTheme(theme),\n })\n } catch (error) {\n console.error(error)\n return res.status(500).json({ error: 'Theme install failed' })\n }\n}\n\nexport default installThemeHandler\n"],"names":["describeTheme","firebaseAdmin","resolveOrgPermissions","isPrivateListing","listingArtifactType","canActAsPublisher","requirePurchase","hasDivergedFromBase","recordInstallProvenance","recordVersionMove","isPublisherSecurityLocked","installThemeHandler","req","res","method","status","json","error","hostId","String","body","action","includes","listingId","authorization","headers","idToken","startsWith","slice","length","undefined","hostSnapshot","listing","membership","decoded","app","auth","verifyIdToken","uid","permissions","installPlugins","firestore","hostRef","collection","doc","get","exists","memberRole","now","FieldValue","serverTimestamp","currentTheme","set","themeReplaced","theme","replacedAt","delete","themeInstalledFrom","themeOverride","updatedAt","merge","reset","cleared","replaced","installedFrom","override","reverted","listingRef","listingSnapshot","data","deletedAt","hiddenAt","profileId","priceUsd","Number","ownsListing","unpaid","buyerUid","buyerOrgId","orgId","versionSnapshot","latestVersion","Object","keys","incoming","current","replaces","Boolean","version","diverged","sha256","mode","provenance","artifactType","content","previousVersion","from","to","update","installCount","increment","catch","installed","baseStored","applied","console"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/server/install-theme.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { type PluginApiHandler } from '@aglyn/aglyn/server'\nimport { describeTheme } from '@aglyn/aglyn/app-utils/marketplace-theme'\nimport { dropPluginSiteCache } from '@aglyn/aglyn/plugin-manager/plugin-site-cache'\nimport { firebaseAdmin } from '@aglyn/tenant-data-admin'\nimport { resolveOrgPermissions } from '@aglyn/tenant-runtime/org-permissions'\nimport {\n isPrivateListing,\n listingArtifactType,\n} from '../model/marketplace'\nimport { canActAsPublisher } from './publisher-profile'\nimport { requirePurchase } from './purchase-entitlement'\nimport { hasDivergedFromBase, recordInstallProvenance } from './provenance'\nimport { recordVersionMove } from './version-stats'\nimport { isPublisherSecurityLocked } from './sale-risk'\n\n/**\n * Installs a marketplace theme onto a site (AGL-1020).\n *\n * Themes are the exception to \"installing never changes a running site\". A\n * template or a layout can land inert in a library because it is one thing\n * among many; a theme IS the site's appearance, and installing one into a\n * library where it does nothing would be a control that appears to work and\n * does not. So this writes `hosts/{hostId}.theme` and the site repaints.\n *\n * Which makes reversibility the requirement, not a nicety. Two things provide\n * it, and neither is a confirmation dialog:\n *\n * * The theme being replaced is kept verbatim in `themeReplaced`, so `revert`\n * restores exactly what was there — including a hand-built theme that was\n * never a marketplace artifact and exists nowhere else.\n * * `reset` clears the theme entirely, which is the \"use the default theme\"\n * the issue asks to keep first-class. It is a distinct action from revert\n * because \"back to how it was\" and \"back to stock\" are different intents and\n * guessing between them is how people lose work.\n *\n * `preview` changes nothing and returns what the swap would do, so the\n * confirmation can name it rather than showing a JSON blob.\n *\n * Every action that writes the theme then drops the site's cached pages\n * (AGL-3386). The theme styles every page and nothing publishes it, so\n * without the drop \"the site repaints\" meant \"within the hour\".\n */\nexport const installThemeHandler: PluginApiHandler = async (req, res) => {\n if (req.method !== 'POST') {\n return res.status(405).json({ error: 'Method not allowed' })\n }\n const hostId = String(req.body?.hostId ?? '')\n const action = ['revert', 'reset', 'preview', 'clear-overrides'].includes(\n req.body?.action,\n )\n ? (req.body.action as\n | 'revert'\n | 'reset'\n | 'preview'\n | 'clear-overrides')\n : 'install'\n const listingId = String(req.body?.listingId ?? '')\n if (!hostId || (action === 'install' && !listingId)) {\n return res.status(400).json({ error: 'Missing listingId or hostId' })\n }\n const authorization = String(req.headers.authorization ?? '')\n const idToken = authorization.startsWith('Bearer ')\n ? authorization.slice('Bearer '.length)\n : undefined\n if (!idToken) return res.status(401).json({ error: 'Unauthenticated' })\n\n try {\n const decoded = await firebaseAdmin.app().auth().verifyIdToken(idToken)\n const membership = await resolveOrgPermissions(decoded.uid, { hostId })\n if (!membership.permissions.installPlugins) {\n return res.status(403).json({\n error:\n 'Your organization role does not allow installing from the marketplace',\n })\n }\n const firestore = firebaseAdmin.app().firestore()\n const hostRef = firestore.collection('hosts').doc(hostId)\n const hostSnapshot = await hostRef.get()\n if (!hostSnapshot.exists) {\n return res.status(404).json({ error: 'Unknown site' })\n }\n const memberRole = (hostSnapshot.get('memberRoles') ?? {})[decoded.uid]\n if (memberRole !== 'admin' && memberRole !== 'editor') {\n return res.status(403).json({ error: 'Not a site admin or editor' })\n }\n const now = firebaseAdmin.firestore.FieldValue.serverTimestamp()\n const currentTheme = hostSnapshot.get('theme') ?? null\n // Awaited before the response, because a serverless function may be\n // frozen once it has answered. Never throws: a drop that fails leaves the\n // theme saved and the hour-long cache as the backstop.\n const repaintLiveSite = (action: string) =>\n dropPluginSiteCache({\n hostIds: [hostId],\n reason: `marketplace theme ${action}`,\n })\n\n // ---- reset: back to the platform default ----\n if (action === 'reset') {\n await hostRef.set(\n {\n // Kept, not dropped: resetting is reversible for the same reason\n // installing is, and someone who resets by mistake has otherwise\n // lost a theme that existed in no other document.\n themeReplaced: { theme: currentTheme, replacedAt: now },\n theme: firebaseAdmin.firestore.FieldValue.delete(),\n themeInstalledFrom: firebaseAdmin.firestore.FieldValue.delete(),\n themeOverride: firebaseAdmin.firestore.FieldValue.delete(),\n updatedAt: now,\n },\n { merge: true },\n )\n await repaintLiveSite('reset')\n return res.status(200).json({ reset: true })\n }\n\n // ---- clear-overrides: drop this site's changes, keep the theme ----\n // The third way back, and the narrowest: \"reset to the publisher's\n // version\" is deleting the patch — the whole point of owning the patch\n // rather than the copy (AGL-1019). The theme itself is untouched.\n if (action === 'clear-overrides') {\n await hostRef.set(\n {\n themeOverride: firebaseAdmin.firestore.FieldValue.delete(),\n updatedAt: now,\n },\n { merge: true },\n )\n await repaintLiveSite('overrides cleared')\n return res.status(200).json({ cleared: true })\n }\n\n // ---- revert: back to whatever this site had before the last swap ----\n if (action === 'revert') {\n const replaced = hostSnapshot.get('themeReplaced')\n if (!replaced) {\n return res.status(409).json({\n error: 'There is no previous theme to go back to on this site.',\n })\n }\n await hostRef.set(\n {\n theme: replaced.theme ?? firebaseAdmin.firestore.FieldValue.delete(),\n ...(replaced.installedFrom\n ? { themeInstalledFrom: replaced.installedFrom }\n : { themeInstalledFrom: firebaseAdmin.firestore.FieldValue.delete() }),\n // The overrides that were live alongside that theme come back with\n // it. Reverting the base and keeping a patch authored against the\n // theme being reverted FROM is the one combination nobody asked for.\n ...(replaced.override\n ? { themeOverride: replaced.override }\n : { themeOverride: firebaseAdmin.firestore.FieldValue.delete() }),\n themeReplaced: firebaseAdmin.firestore.FieldValue.delete(),\n updatedAt: now,\n },\n { merge: true },\n )\n await repaintLiveSite('reverted')\n return res.status(200).json({ reverted: true })\n }\n\n const listingRef = firestore.collection('marketplaceListings').doc(listingId)\n const listingSnapshot = await listingRef.get()\n const listing = listingSnapshot.data() as any\n if (\n !listing ||\n listing.deletedAt ||\n // Staff takedown blocks new installs on EVERY artifact type\n // (AGL-2290). AGL-948 extended takedown past plugins in the browse\n // predicate and in `resolveMarketplacePluginVersion`, but the gate that\n // decides whether content is HANDED OVER was only ever added to\n // `install-plugin.ts`. So a component, theme, template, layout, email\n // template or dataset schema that staff had taken down stayed\n // installable by anyone holding its listing id — which makes takedown a\n // suggestion for six of the seven artifact types.\n //\n // No owner exemption, matching `install-plugin.ts`: a takedown is a\n // moderation decision about the artifact, not about who is asking.\n listing.hiddenAt ||\n // A publisher under a SECURITY lock hands nothing over (AGL-3365).\n (await isPublisherSecurityLocked(firestore, listing.profileId)) ||\n listingArtifactType(listing) !== 'theme'\n ) {\n return res.status(404).json({ error: 'Unknown theme' })\n }\n\n const priceUsd = Number(listing.priceUsd ?? 0)\n const ownsListing = await canActAsPublisher(\n firestore,\n decoded.uid,\n listing.profileId,\n )\n // Private listings install ONLY for the owning org (AGL-2290).\n //\n // `install-plugin.ts` has carried this since AGL-968; the other six never\n // did, so a private component, theme, template, layout, email template or\n // dataset schema was installable by anyone who knew its listing id. Browse\n // hides them and the detail page 404s, but neither is a control — the\n // route is.\n if (isPrivateListing(listing) && !ownsListing) {\n return res.status(404).json({ error: 'Unknown listing' })\n }\n // A FULLY refunded purchase stops entitling (AGL-1546), and until\n // AGL-1699 only the component route knew that: this one asked whether a\n // purchase doc EXISTED, so buy/install/refund kept the artifact. The\n // predicate lives in one place now so the next route cannot miss it.\n const unpaid = await requirePurchase({\n firestore,\n buyerUid: decoded.uid,\n // THE ORG THE LICENCE HAS TO COVER (AGL-2331). `membership.orgId` is\n // resolved server-side from the caller's own membership by the\n // permission gate above — never a request-body field — so this is the\n // workspace the install actually lands in, and the only one a purchase\n // can entitle here.\n buyerOrgId: membership.orgId ?? '',\n listingId,\n priceUsd,\n ownsListing,\n })\n if (unpaid) return res.status(402).json(unpaid)\n\n const versionSnapshot = await listingRef\n .collection('versions')\n .doc(String(listing.latestVersion))\n .get()\n const theme = versionSnapshot.get('theme')\n if (!theme || !Object.keys(theme).length) {\n return res.status(500).json({ error: 'Theme version missing' })\n }\n\n // ---- preview: say what the swap changes, change nothing ----\n if (action === 'preview') {\n return res.status(200).json({\n incoming: describeTheme(theme),\n current: describeTheme(currentTheme),\n // A site with no theme is on the platform default, so there is nothing\n // to lose and the confirmation should not imply there is.\n replaces: Boolean(currentTheme && Object.keys(currentTheme).length),\n version: listing.latestVersion ?? null,\n })\n }\n\n // ---- install ----\n // Replacing a theme the workspace has edited destroys those edits (AGL-1018).\n // Unlike a component this is not recoverable by looking in a library — the\n // theme lives on one field on one document — so the refusal matters more.\n const diverged = await hasDivergedFromBase({\n firestore,\n sha256: hostSnapshot.get('themeInstalledFrom.sha256'),\n current: currentTheme,\n })\n if (diverged && req.body?.mode !== 'replace') {\n return res.status(409).json({\n error:\n 'This site’s theme has been edited since it was installed. Review ' +\n 'the update to see what would change.',\n diverged: true,\n })\n }\n\n const provenance = await recordInstallProvenance({\n firestore,\n listingId,\n listing,\n version: listing.latestVersion,\n artifactType: 'theme',\n content: theme,\n })\n const previousVersion =\n hostSnapshot.get('themeInstalledFrom.version') ?? null\n\n await hostRef.set(\n {\n theme,\n themeInstalledFrom: provenance.installedFrom,\n // Everything needed to put this site back exactly as it was, captured\n // BEFORE the write that changes it.\n themeReplaced: {\n theme: currentTheme,\n ...(hostSnapshot.get('themeInstalledFrom')\n ? { installedFrom: hostSnapshot.get('themeInstalledFrom') }\n : {}),\n ...(hostSnapshot.get('themeOverride')\n ? { override: hostSnapshot.get('themeOverride') }\n : {}),\n replacedAt: now,\n },\n // Overrides SURVIVE a theme swap (AGL-1021). A site that set its brand\n // colour still means it under a new theme, and silently discarding\n // that on install would be the more surprising of the two options.\n //\n // What must not happen is surviving *silently*: the override keeps the\n // `baseSha256` it was authored against, which no longer matches the\n // theme now installed, so `isOverrideForCurrentTheme` is false and the\n // editor says so with an offer to clear them. Rewriting that hash here\n // would launder a patch written for another theme into one that claims\n // to belong to this one.\n updatedAt: now,\n },\n { merge: true },\n )\n\n await repaintLiveSite('installed')\n\n await recordVersionMove({\n firestore,\n listingRef,\n artifactType: 'theme',\n from: previousVersion,\n to: listing.latestVersion,\n })\n await listingRef\n .update({\n installCount: firebaseAdmin.firestore.FieldValue.increment(1),\n })\n .catch(() => undefined)\n\n return res.status(200).json({\n installed: true,\n version: listing.latestVersion ?? null,\n baseStored: provenance.baseStored,\n applied: describeTheme(theme),\n })\n } catch (error) {\n console.error(error)\n return res.status(500).json({ error: 'Theme install failed' })\n }\n}\n\nexport default installThemeHandler\n"],"names":["describeTheme","dropPluginSiteCache","firebaseAdmin","resolveOrgPermissions","isPrivateListing","listingArtifactType","canActAsPublisher","requirePurchase","hasDivergedFromBase","recordInstallProvenance","recordVersionMove","isPublisherSecurityLocked","installThemeHandler","req","res","method","status","json","error","hostId","String","body","action","includes","listingId","authorization","headers","idToken","startsWith","slice","length","undefined","hostSnapshot","listing","membership","decoded","app","auth","verifyIdToken","uid","permissions","installPlugins","firestore","hostRef","collection","doc","get","exists","memberRole","now","FieldValue","serverTimestamp","currentTheme","repaintLiveSite","hostIds","reason","set","themeReplaced","theme","replacedAt","delete","themeInstalledFrom","themeOverride","updatedAt","merge","reset","cleared","replaced","installedFrom","override","reverted","listingRef","listingSnapshot","data","deletedAt","hiddenAt","profileId","priceUsd","Number","ownsListing","unpaid","buyerUid","buyerOrgId","orgId","versionSnapshot","latestVersion","Object","keys","incoming","current","replaces","Boolean","version","diverged","sha256","mode","provenance","artifactType","content","previousVersion","from","to","update","installCount","increment","catch","installed","baseStored","applied","console"],"mappings":"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"}
@@ -21,6 +21,7 @@ import { artifactCreateListKeys } from "@aglyn/aglyn/app-utils/artifact-list-key
21
21
  import { createResourceUid, decodeStoredNodes, encodeStoredNodes, newResourceScopeFields, ORG_SCOPE_TOKEN } from "@aglyn/aglyn/server";
22
22
  import { applyArtifactUpdate, planArtifactUpdate, summarizeSchemaChange } from "../model/artifact-merge.js";
23
23
  import { ARTIFACT_BASE_COLLECTION } from "@aglyn/aglyn/app-utils/marketplace-provenance";
24
+ import { dropPluginSiteCache } from "@aglyn/aglyn/plugin-manager/plugin-site-cache";
24
25
  import { firebaseAdmin, getOrgForHost } from "@aglyn/tenant-data-admin";
25
26
  import { resolveOrgPermissions } from "@aglyn/tenant-runtime/org-permissions";
26
27
  import { listingArtifactType, resolveInstalledDatasetSchema } from "../model/marketplace.js";
@@ -456,6 +457,18 @@ export const updateArtifactHandler = async (req, res)=>{
456
457
  }), {
457
458
  merge: true
458
459
  });
460
+ // A merged THEME repaints every page of the site the moment it lands,
461
+ // and nothing publishes it — so the site's cached pages go now rather
462
+ // than within the hour (AGL-3386). Awaited before the response, and it
463
+ // never throws: a failed drop leaves the update applied.
464
+ if (artifactType === 'theme') {
465
+ await dropPluginSiteCache({
466
+ hostIds: [
467
+ hostId
468
+ ],
469
+ reason: 'marketplace theme updated'
470
+ });
471
+ }
459
472
  // Per-version tally (AGL-1036): this install just moved versions, which
460
473
  // is the whole reason the per-version count exists.
461
474
  await recordVersionMove({
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/server/update-artifact.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// The leaf module, not the server barrel: the list keys every artifact\n// create stamps (AGL-3321), which the install specs' closed-world barrel\n// mocks have no reason to stage.\nimport { artifactCreateListKeys } from '@aglyn/aglyn/app-utils/artifact-list-keys'\nimport {\n createResourceUid,\n decodeStoredNodes,\n encodeStoredNodes,\n newResourceScopeFields,\n ORG_SCOPE_TOKEN,\n type PluginApiHandler,\n type ScopeToken,\n} from '@aglyn/aglyn/server'\nimport {\n applyArtifactUpdate,\n planArtifactUpdate,\n summarizeSchemaChange,\n type ArtifactChange,\n type ArtifactUpdatePlan,\n} from '../model/artifact-merge'\nimport { ARTIFACT_BASE_COLLECTION } from '@aglyn/aglyn/app-utils/marketplace-provenance'\nimport { firebaseAdmin, getOrgForHost } from '@aglyn/tenant-data-admin'\nimport { resolveOrgPermissions } from '@aglyn/tenant-runtime/org-permissions'\nimport {\n listingArtifactType,\n resolveInstalledDatasetSchema,\n} from '../model/marketplace'\nimport { readPublishedProps } from '../model/marketplace-props'\nimport { recordInstallProvenance } from './provenance'\nimport { canActAsPublisher } from './publisher-profile'\nimport { requirePurchase } from './purchase-entitlement'\nimport { recordVersionMove } from './version-stats'\nimport { isPublisherSecurityLocked } from './sale-risk'\n\n/**\n * Updating a COPIED marketplace artifact (AGL-1018).\n *\n * The install routes update by overwriting: a component re-install writes the\n * publisher's tree straight over the workspace's copy, and a template or layout\n * re-install soft-deletes the previous bundle. Both are silent, and both throw\n * away work the customer did after installing.\n *\n * This route never overwrites without saying what will be lost. `preview`\n * classifies every field against AGL-1015's base snapshot and returns the plan;\n * `apply` writes only what the plan (and, for conflicts, the caller) chose.\n * There are two ways to take a new version and both are safe:\n *\n * * **merge** — take the publisher's changes to fields nobody here edited, keep\n * this workspace's edits, and overwrite a contested field only when it was\n * explicitly picked.\n * * **copy** — install the new version fresh and DETACH the customised copy,\n * which keeps it as an ordinary workspace artifact rather than deleting it.\n *\n * Without a base snapshot only `copy` is offered. Inventing an origin to diff\n * against is how a merge becomes confidently wrong.\n */\n\ntype Mode = 'merge' | 'copy'\n\n/** What a preview tells the client, and what the dialog renders. */\ninterface UpdatePreview {\n artifactType: string\n installedVersion: string | null\n availableVersion: string | null\n /** A three-way merge is possible: a base snapshot exists and the type supports it. */\n mergeable: boolean\n /** Why not, when it isn't. */\n reason?: string\n safe: ArtifactChange[]\n kept: ArtifactChange[]\n conflicts: ArtifactChange[]\n unchanged: number\n identical: boolean\n /** Dataset schemas only: what the change does to existing records. */\n schema?: {\n added: string[]\n removed: string[]\n retyped: string[]\n additiveOnly: boolean\n recordCount: number\n }\n}\n\n/** Everything the route needs to diff and write one copied artifact. */\ninterface Target {\n ref: FirebaseFirestore.DocumentReference\n /** The copy as it is now, in the same shape the base snapshot holds. */\n current: unknown\n /** Fields to write alongside merged content. */\n write: (content: any) => Record<string, unknown>\n /** Fields that detach this copy from its listing, for `copy` mode. */\n detach?: Record<string, unknown>\n /**\n * The AGL-1037 scope the FORKED copy is created with, or `null` when the\n * destination collection is not scoped (AGL-1478).\n *\n * Required, and required on purpose. `copy` mode writes a brand-new\n * document into `target.ref.parent` — whatever collection that turns out\n * to be — from a spread of carried fields, and a spread cannot carry a\n * field the source never had. For `orgs/{orgId}/datasets` that is the\n * AGL-1466 bug exactly: an unstamped dataset is not \"unrestricted\", it is\n * matched by no `array-contains-any` reader in the product, so it renders\n * on no site and is missing from the workflows and reference-health cards\n * even for an org-wide member.\n *\n * Making it optional with a sensible default would have been the smaller\n * diff and is the thing that fails: the two `mediaFolders` creators that\n * each forgot `visibleTo` were both written by people who had the field\n * in front of them. A sixth artifact type cannot be added here without\n * answering this, which is the only guarantee that survives a stranger.\n *\n * `null` is a real answer, not an escape hatch: components live in\n * `hosts/{hostId}/components` and layouts in `hosts/{hostId}/templates`,\n * both private to their host by construction and neither carrying a scope.\n */\n forkScope: readonly ScopeToken[] | null\n /**\n * The declared properties the new version carries (AGL-2933), for a\n * component or layout, sanitized; `undefined` when the version was published\n * before properties were carried, or the type declares none.\n *\n * Not part of the merged content. Every base snapshot recorded before this\n * field existed holds only the tree, so diffing properties three ways would\n * read each installed copy's declarations as a local edit to keep. They are\n * the publisher's contract with the tree instead: whatever the merge takes\n * of the nodes binds to them, so the incoming list replaces the installed\n * one, in both modes.\n */\n props?: ReturnType<typeof readPublishedProps>\n}\n\nconst trimChanges = (changes: ArtifactChange[]): ArtifactChange[] =>\n // The dialog lists changes; a 400-node tree that changed wholesale would\n // otherwise ship its entire content twice through a JSON response.\n changes.slice(0, 200)\n\nexport const updateArtifactHandler: PluginApiHandler = async (req, res) => {\n if (req.method !== 'POST') {\n return res.status(405).json({ error: 'Method not allowed' })\n }\n const listingId = String(req.body?.listingId ?? '')\n const hostId = String(req.body?.hostId ?? '')\n const action = req.body?.action === 'apply' ? 'apply' : 'preview'\n const mode: Mode = req.body?.mode === 'copy' ? 'copy' : 'merge'\n const takePaths: string[] = Array.isArray(req.body?.takePaths)\n ? req.body.takePaths.map((path: unknown) => String(path))\n : []\n const confirmDestructive = Boolean(req.body?.confirmDestructive)\n if (!listingId || !hostId) {\n return res.status(400).json({ error: 'Missing listingId or hostId' })\n }\n const authorization = String(req.headers.authorization ?? '')\n const idToken = authorization.startsWith('Bearer ')\n ? authorization.slice('Bearer '.length)\n : undefined\n if (!idToken) return res.status(401).json({ error: 'Unauthenticated' })\n\n try {\n const decoded = await firebaseAdmin.app().auth().verifyIdToken(idToken)\n // The same gate installing uses: taking a new version of something is\n // installing it, and a role that cannot install must not be able to\n // rewrite an installed artifact through a different door.\n const membership = await resolveOrgPermissions(decoded.uid, { hostId })\n if (!membership.permissions.installPlugins) {\n return res.status(403).json({\n error: 'Your organization role does not allow installing from the marketplace',\n })\n }\n const firestore = firebaseAdmin.app().firestore()\n const hostRef = firestore.collection('hosts').doc(hostId)\n const hostSnapshot = await hostRef.get()\n if (!hostSnapshot.exists) {\n return res.status(404).json({ error: 'Unknown site' })\n }\n\n const listingRef = firestore.collection('marketplaceListings').doc(listingId)\n const listingSnapshot = await listingRef.get()\n const listing = listingSnapshot.data() as any\n if (\n !listing ||\n listing.deletedAt ||\n // Staff takedown blocks new installs on EVERY artifact type\n // (AGL-2290). AGL-948 extended takedown past plugins in the browse\n // predicate and in `resolveMarketplacePluginVersion`, but the gate that\n // decides whether content is HANDED OVER was only ever added to\n // `install-plugin.ts`. So a component, theme, template, layout, email\n // template or dataset schema that staff had taken down stayed\n // installable by anyone holding its listing id — which makes takedown a\n // suggestion for six of the seven artifact types.\n //\n // No owner exemption, matching `install-plugin.ts`: a takedown is a\n // moderation decision about the artifact, not about who is asking.\n listing.hiddenAt ||\n // A publisher under a SECURITY lock hands nothing over (AGL-3365).\n (await isPublisherSecurityLocked(firestore, listing.profileId))\n ) {\n return res.status(404).json({ error: 'Unknown listing' })\n }\n // Taking a NEW version of a paid listing is buying content, and this\n // route never asked whether the caller still owned it (AGL-1699). The\n // install routes gate; this one only checked the org ROLE, so a buyer\n // who was refunded — and even in `copy` mode, which installs the new\n // version fresh — kept pulling every future paid release through here.\n //\n // Deliberately not a regression for anyone legitimate: a live purchase,\n // a free listing, or the publisher's own listing all pass, and nobody\n // without one of those could have had an installed copy to update.\n // The `priceUsd > 0` test is out here rather than left to the helper so\n // a free listing — most of them — does not pay for a publisher lookup.\n const priceUsd = Number(listing.priceUsd ?? 0)\n if (priceUsd > 0) {\n const unpaid = await requirePurchase({\n firestore,\n buyerUid: decoded.uid,\n // THE ORG THE LICENCE HAS TO COVER (AGL-2331). `membership.orgId` is\n // resolved server-side from the caller's own membership by the\n // permission gate above — never a request-body field — so this is the\n // workspace the install actually lands in, and the only one a purchase\n // can entitle here.\n buyerOrgId: membership.orgId ?? '',\n listingId,\n priceUsd,\n ownsListing: await canActAsPublisher(\n firestore,\n decoded.uid,\n listing.profileId,\n ),\n })\n if (unpaid) return res.status(402).json(unpaid)\n }\n\n const artifactType = listingArtifactType(listing)\n if (artifactType === 'plugin') {\n // Plugins are pinned, not copied: an update moves a pointer at immutable\n // bytes (AGL-1017) and has nothing to merge.\n return res.status(400).json({\n error: 'Plugins update by re-pinning — use the install route',\n })\n }\n\n const versionSnapshot = await listingRef\n .collection('versions')\n .doc(String(listing.latestVersion))\n .get()\n if (!versionSnapshot.exists) {\n return res.status(404).json({ error: 'Listing version missing' })\n }\n const availableVersion =\n listing.latestVersion == null ? null : String(listing.latestVersion)\n\n const orgId = (await getOrgForHost(hostId))?.orgId ?? ''\n\n /** The installed copy, the base it was installed from, and the offer. */\n let target: Target | null = null\n let incoming: unknown = null\n let installedVersion: string | null = null\n let baseSha: string | null = null\n let notMergeable: string | undefined\n let schemaSummary: UpdatePreview['schema']\n\n if (artifactType === 'component') {\n const existing = await hostRef\n .collection('components')\n .where('marketplace.listingId', '==', listingId)\n .limit(1)\n .get()\n const doc = existing.docs.find((entry) => !entry.get('deletedAt'))\n if (!doc) return res.status(404).json({ error: 'Not installed on this site' })\n const version = versionSnapshot.data() as any\n incoming = { rootId: version.rootId, nodes: version.nodes }\n installedVersion = String(\n doc.get('installedFrom.version') ?? doc.get('marketplace.version') ?? '',\n ) || null\n baseSha = doc.get('installedFrom.sha256') ?? null\n target = {\n ref: doc.ref,\n current: {\n rootId: doc.get('rootId'),\n // DECODED, because everything downstream compares by VALUE: the\n // divergence hash, and `planArtifactUpdate`'s three-way diff over\n // dotted paths like `nodes.abc123.props.title`. A stored `Bytes`\n // has no such paths, so the merge would see the whole tree as\n // removed and write an empty one back (AGL-1151).\n nodes: decodeStoredNodes(doc.get('nodes')),\n },\n write: (content) => ({\n rootId: content?.rootId ?? null,\n // Compressed at rest, like every other writer of this document.\n nodes: Buffer.from(encodeStoredNodes(content?.nodes ?? {})!),\n }),\n // Detaching keeps the customised component as an ordinary one: it stays\n // on every page that uses it, and only stops being tracked against the\n // listing — which is what makes \"install as a new copy\" non-destructive\n // without leaving two documents claiming to be the same install.\n detach: {\n marketplace: firebaseAdmin.firestore.FieldValue.delete(),\n detachedFrom: { listingId, version: installedVersion },\n },\n // `hosts/{hostId}/components` is private to its host; no scope.\n forkScope: null,\n props: readPublishedProps(version.props),\n }\n } else if (artifactType === 'layout') {\n const existing = await hostRef\n .collection('templates')\n .where('source.listingId', '==', listingId)\n .get()\n const doc = existing.docs.find(\n (entry) => !entry.get('deletedAt') && entry.get('kind') === 'layout',\n )\n if (!doc) return res.status(404).json({ error: 'Not installed on this site' })\n const layout = versionSnapshot.get('layout') as any\n incoming = { rootId: layout?.rootId, nodes: layout?.nodes }\n installedVersion = String(\n doc.get('installedFrom.version') ?? doc.get('source.version') ?? '',\n ) || null\n baseSha = doc.get('installedFrom.sha256') ?? null\n target = {\n ref: doc.ref,\n current: {\n rootId: doc.get('rootId'),\n // DECODED, because everything downstream compares by VALUE: the\n // divergence hash, and `planArtifactUpdate`'s three-way diff over\n // dotted paths like `nodes.abc123.props.title`. A stored `Bytes`\n // has no such paths, so the merge would see the whole tree as\n // removed and write an empty one back (AGL-1151).\n nodes: decodeStoredNodes(doc.get('nodes')),\n },\n write: (content) => ({\n rootId: content?.rootId ?? null,\n // Compressed at rest, like every other writer of this document.\n nodes: Buffer.from(encodeStoredNodes(content?.nodes ?? {})!),\n }),\n detach: {\n // The customer's own from here on: `authored`, the provenance the\n // templates library's Source filter calls \"Saved here\" (AGL-3321).\n source: { type: 'authored' },\n detachedFrom: { listingId, version: installedVersion },\n },\n // `hosts/{hostId}/templates` is private to its host; no scope.\n forkScope: null,\n props: readPublishedProps(layout?.props),\n }\n } else if (artifactType === 'datasetSchema') {\n if (!orgId) {\n return res.status(404).json({ error: 'Site has no owning organization' })\n }\n const orgRef = firestore.collection('orgs').doc(orgId)\n const datasets = await orgRef.collection('datasets').get()\n const doc = datasets.docs.find(\n (entry) =>\n entry.get('source.listingId') === listingId && !entry.get('deletedAt'),\n )\n if (!doc) {\n return res.status(404).json({ error: 'Not installed in this organization' })\n }\n // Relink the incoming schema onto THIS org's datasets exactly as install\n // does — the base holds the relinked shape, so diffing the published one\n // would report every reference field as a user edit.\n const byLabel: Record<string, string> = {}\n for (const entry of datasets.docs) {\n const label = String(entry.get('displayName') ?? '').toLowerCase()\n if (label && !byLabel[label]) byLabel[label] = entry.id\n }\n const { schema } = resolveInstalledDatasetSchema(\n versionSnapshot.get('datasetSchema') as any,\n byLabel,\n )\n incoming = schema\n installedVersion = String(\n doc.get('installedFrom.version') ?? doc.get('source.version') ?? '',\n ) || null\n baseSha = doc.get('installedFrom.sha256') ?? null\n const current = doc.get('model') ?? { order: doc.get('fields') ?? [], fields: {} }\n const summary = summarizeSchemaChange(current as any, schema as any)\n // The count is read for the preview, not estimated: \"3 fields will be\n // removed\" means nothing without \"from 1,240 records\".\n const records = await doc.ref\n .collection('records')\n .count()\n .get()\n .catch(() => null)\n schemaSummary = {\n added: summary.added,\n removed: summary.removed,\n retyped: summary.retyped,\n additiveOnly: summary.additiveOnly,\n recordCount: records?.data().count ?? 0,\n }\n const installedScope = doc.get('visibleTo')\n target = {\n ref: doc.ref,\n current,\n write: (content) => ({\n model: content,\n // The v1 flat list the older editor still reads, kept in step.\n fields: content?.order ?? [],\n }),\n // A fork INHERITS the copy it forked from, never a fresh default: a\n // dataset restricted to one client's site must not reappear org-wide\n // because somebody took a publisher update as a separate copy. An\n // absent scope reads as org-wide everywhere else (`visibleToHost`),\n // so it does here. A stored empty array — \"visible to nobody\" — is\n // passed through and throws at the write rather than being widened,\n // which is the right direction to fail in.\n forkScope: Array.isArray(installedScope)\n ? (installedScope as ScopeToken[])\n : [ORG_SCOPE_TOKEN],\n }\n } else if (artifactType === 'theme') {\n // A theme lives on the host document itself, so there is no artifact doc\n // to look up and no \"not installed on this site\" — the site either runs\n // this listing's theme or it does not.\n if (hostSnapshot.get('themeInstalledFrom.listingId') !== listingId) {\n return res.status(404).json({ error: 'Not installed on this site' })\n }\n incoming = versionSnapshot.get('theme') ?? {}\n installedVersion =\n String(hostSnapshot.get('themeInstalledFrom.version') ?? '') || null\n baseSha = hostSnapshot.get('themeInstalledFrom.sha256') ?? null\n target = {\n ref: hostRef,\n current: hostSnapshot.get('theme') ?? {},\n write: (content) => ({ theme: content ?? {} }),\n // No `detach`: a site has exactly one theme, so \"install as a separate\n // copy\" has nowhere to put the second one. Merge is the only safe way\n // to take a theme update, and the preview says so.\n //\n // A theme is a FIELD on the host document, so there is no document to\n // scope even if it could fork.\n forkScope: null,\n }\n } else if (artifactType === 'template') {\n // A published template bundle's screens carry no stable identity — only a\n // displayName and a slug — so there is nothing to pair an old screen to a\n // new one by. Any merge would be a guess dressed up as a diff.\n notMergeable =\n 'Template bundles have no stable per-screen identity, so their screens ' +\n 'cannot be matched across versions. Installing the new version replaces ' +\n 'this bundle in your Templates library — pages you already created from ' +\n 'the old templates are ordinary screens and are untouched.'\n } else if (artifactType === 'emailTemplate') {\n // Already safe by construction: an email template installs as an inactive\n // draft version, so taking a new one changes nothing that is being sent.\n notMergeable =\n 'Email templates install as a draft version and never replace what your ' +\n 'site is sending. Add the new version as another draft, then activate ' +\n 'it when you are ready.'\n }\n\n if (notMergeable) {\n const preview: UpdatePreview = {\n artifactType,\n installedVersion: null,\n availableVersion,\n mergeable: false,\n reason: notMergeable,\n safe: [],\n kept: [],\n conflicts: [],\n unchanged: 0,\n identical: false,\n }\n return res.status(200).json({ preview })\n }\n if (!target) {\n return res.status(404).json({ error: 'Nothing installed to update' })\n }\n\n const baseSnapshot = baseSha\n ? await firestore\n .collection(ARTIFACT_BASE_COLLECTION)\n .doc(baseSha)\n .get()\n .catch(() => null)\n : null\n const base = baseSnapshot?.exists ? baseSnapshot.get('content') : undefined\n const mergeable = base !== undefined\n\n let plan: ArtifactUpdatePlan | null = null\n if (mergeable) plan = planArtifactUpdate(base, target.current, incoming)\n\n if (action === 'preview') {\n const preview: UpdatePreview = {\n artifactType,\n installedVersion,\n availableVersion,\n mergeable,\n ...(mergeable\n ? {}\n : {\n reason:\n 'This was installed before update tracking recorded an ' +\n 'original, so there is no way to tell your changes from the ' +\n 'publisher’s. Installing the new version as a separate copy ' +\n 'keeps yours intact.',\n }),\n safe: trimChanges(plan?.safe ?? []),\n kept: trimChanges(plan?.kept ?? []),\n conflicts: trimChanges(plan?.conflicts ?? []),\n unchanged: plan?.unchanged ?? 0,\n identical: plan?.identical ?? false,\n ...(schemaSummary ? { schema: schemaSummary } : {}),\n }\n return res.status(200).json({ preview })\n }\n\n // ---- apply ----\n const now = firebaseAdmin.firestore.FieldValue.serverTimestamp()\n\n if (mode === 'merge') {\n if (!plan) {\n return res.status(409).json({\n error:\n 'No original snapshot for this install — it can only be taken as a new copy',\n })\n }\n // A schema change that removes or retypes a field reinterprets rows that\n // already exist. It is applicable, but never without the caller having\n // been told the count and said yes to it specifically.\n if (schemaSummary && !schemaSummary.additiveOnly && !confirmDestructive) {\n return res.status(409).json({\n error:\n `This update removes or retypes ${\n schemaSummary.removed.length + schemaSummary.retyped.length\n } field(s) on a dataset holding ${schemaSummary.recordCount} record(s).`,\n needsConfirmation: true,\n schema: schemaSummary,\n })\n }\n const merged = applyArtifactUpdate(plan, target.current, { takePaths })\n const provenance = await recordInstallProvenance({\n firestore,\n listingId,\n listing,\n version: listing.latestVersion,\n artifactType: artifactType as never,\n // The base for NEXT time is the publisher's version, not the merged\n // result: the base answers \"what did the publisher ship\", and seeding\n // it with the merge would quietly launder this update's kept edits into\n // the publisher's column, where the next update could take them away.\n content: incoming,\n })\n await target.ref.set(\n {\n ...target.write(merged.content),\n ...(target.props && { props: target.props }),\n // A theme is a FIELD on the host document, not a document of its own,\n // so it namespaces its provenance (`themeInstalledFrom`) and has no\n // legacy `source`/`marketplace` pair — writing those here would put a\n // stray marketplace stamp on the host itself, where `resolveProvenance`\n // would later read it as the host being an installed artifact.\n ...(artifactType === 'theme'\n ? { themeInstalledFrom: provenance.installedFrom }\n : {\n installedFrom: provenance.installedFrom,\n // The legacy per-type version fields the console still reads.\n ...(artifactType === 'component'\n ? {\n marketplace: {\n listingId,\n profileId: listing.profileId ?? null,\n version: listing.latestVersion ?? null,\n },\n }\n : {\n source: {\n type: 'marketplace' as const,\n listingId,\n version: listing.latestVersion ?? null,\n },\n }),\n }),\n updatedAt: now,\n },\n { merge: true },\n )\n // Per-version tally (AGL-1036): this install just moved versions, which\n // is the whole reason the per-version count exists.\n await recordVersionMove({\n firestore,\n listingRef,\n artifactType,\n from: installedVersion,\n to: availableVersion,\n })\n return res.status(200).json({\n updated: true,\n mode,\n version: availableVersion,\n applied: merged.applied.length,\n keptLocal: plan.kept.length,\n skipped: merged.skipped,\n baseStored: provenance.baseStored,\n })\n }\n\n // ---- copy ----\n if (!target.detach) {\n // Only types with a detachable link get a copy from here. A dataset's\n // \"new copy\" is a whole new dataset, which consumes org quota and lands\n // empty — exactly what the install route already does, with the quota\n // check this one does not have.\n //\n // This return is also, today, the only thing keeping the fork below\n // from creating an unstamped `orgs/{orgId}/datasets` document\n // (AGL-1478): a dataset never reaches it because it has no `detach`.\n // That is a guard by accident, three statements away from the write\n // and phrased as a quota argument — give `datasetSchema` a `detach`\n // for any reason and the scope hole opens with it. `Target.forkScope`\n // is the guard that stays true whichever way this branch goes.\n return res.status(400).json({\n error:\n artifactType === 'theme'\n ? // A site has one theme, so there is no second place to put a\n // copy. Merging keeps this site's edits; the way back to the\n // previous theme is the install route's `revert`.\n 'A site has only one theme, so this update can only be merged. ' +\n 'Your edits to fields the publisher did not touch are kept.'\n : 'Install a fresh copy of this from the listing instead',\n })\n }\n // The customised copy is detached rather than deleted or overwritten: it\n // keeps working everywhere it is used, and only stops being the document\n // this listing updates. Without the detach two documents would claim the\n // same install and the next update would pick between them arbitrarily.\n const provenance = await recordInstallProvenance({\n firestore,\n listingId,\n listing,\n version: listing.latestVersion,\n artifactType: artifactType as never,\n content: incoming,\n })\n const batch = firestore.batch()\n if (target.detach) {\n batch.set(target.ref, { ...target.detach, updatedAt: now }, { merge: true })\n }\n const collectionRef = target.ref.parent\n const freshRef = collectionRef.doc(createResourceUid())\n const carried = (await target.ref.get()).data() ?? {}\n const freshName = String(listing.displayName ?? carried.displayName ?? 'Untitled')\n batch.set(freshRef, {\n // Everything that is not content travels from the copy being replaced —\n // the display name, description and kind — so the new one lands in the\n // same place in the same library rather than as an unrecognisable stub.\n ...(carried.kind ? { kind: carried.kind } : {}),\n displayName: freshName,\n ...(listing.description ? { description: listing.description } : {}),\n // Not part of the `carried` spread above, deliberately: a spread\n // cannot carry a field the source is missing, and \"the source had one\"\n // is not the question a scoped collection asks. `Target.forkScope`\n // makes every artifact type answer it (AGL-1478), and this throws\n // rather than storing a scope nobody can see through.\n ...newResourceScopeFields(target.forkScope),\n ...target.write(incoming),\n ...(target.props\n ? { props: target.props }\n : Array.isArray(carried.props)\n ? { props: carried.props }\n : {}),\n ...(artifactType === 'component'\n ? {\n marketplace: {\n listingId,\n profileId: listing.profileId ?? null,\n version: listing.latestVersion ?? null,\n },\n }\n : {\n source: {\n type: 'marketplace' as const,\n listingId,\n version: listing.latestVersion ?? null,\n },\n }),\n installedFrom: provenance.installedFrom,\n deletedAt: null,\n // The keys the fresh copy's list finds it by (AGL-3321), for whichever\n // artifact collection it lands in.\n ...artifactCreateListKeys(collectionRef.id, {\n kind: carried.kind,\n displayName: freshName,\n source: { type: 'marketplace' },\n }),\n createdAt: now,\n updatedAt: now,\n })\n await batch.commit()\n // Per-version tally (AGL-1036). Still a move, not a second install: the\n // detached copy stops being tracked against the listing, so the version it\n // held is genuinely vacated.\n await recordVersionMove({\n firestore,\n listingRef,\n artifactType,\n from: installedVersion,\n to: availableVersion,\n })\n return res.status(200).json({\n updated: true,\n mode,\n version: availableVersion,\n newId: freshRef.id,\n detached: Boolean(target.detach),\n baseStored: provenance.baseStored,\n })\n } catch (error) {\n console.error(error)\n return res.status(500).json({ error: 'Update failed' })\n }\n}\n\nexport default updateArtifactHandler\n"],"names":["artifactCreateListKeys","createResourceUid","decodeStoredNodes","encodeStoredNodes","newResourceScopeFields","ORG_SCOPE_TOKEN","applyArtifactUpdate","planArtifactUpdate","summarizeSchemaChange","ARTIFACT_BASE_COLLECTION","firebaseAdmin","getOrgForHost","resolveOrgPermissions","listingArtifactType","resolveInstalledDatasetSchema","readPublishedProps","recordInstallProvenance","canActAsPublisher","requirePurchase","recordVersionMove","isPublisherSecurityLocked","trimChanges","changes","slice","updateArtifactHandler","req","res","method","status","json","error","listingId","String","body","hostId","action","mode","takePaths","Array","isArray","map","path","confirmDestructive","Boolean","authorization","headers","idToken","startsWith","length","undefined","listing","decoded","app","auth","verifyIdToken","membership","uid","permissions","installPlugins","firestore","hostRef","collection","doc","hostSnapshot","get","exists","listingRef","listingSnapshot","data","deletedAt","hiddenAt","profileId","priceUsd","Number","unpaid","buyerUid","buyerOrgId","orgId","ownsListing","artifactType","versionSnapshot","latestVersion","availableVersion","target","incoming","installedVersion","baseSha","notMergeable","schemaSummary","existing","where","limit","docs","find","entry","version","rootId","nodes","ref","current","write","content","Buffer","from","detach","marketplace","FieldValue","delete","detachedFrom","forkScope","props","layout","source","type","orgRef","datasets","byLabel","label","toLowerCase","id","schema","order","fields","summary","records","count","catch","added","removed","retyped","additiveOnly","recordCount","installedScope","model","theme","preview","mergeable","reason","safe","kept","conflicts","unchanged","identical","baseSnapshot","base","plan","now","serverTimestamp","needsConfirmation","merged","provenance","set","themeInstalledFrom","installedFrom","updatedAt","merge","to","updated","applied","keptLocal","skipped","baseStored","batch","collectionRef","parent","freshRef","carried","freshName","displayName","kind","description","createdAt","commit","newId","detached","console"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/server/update-artifact.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// The leaf module, not the server barrel: the list keys every artifact\n// create stamps (AGL-3321), which the install specs' closed-world barrel\n// mocks have no reason to stage.\nimport { artifactCreateListKeys } from '@aglyn/aglyn/app-utils/artifact-list-keys'\nimport {\n createResourceUid,\n decodeStoredNodes,\n encodeStoredNodes,\n newResourceScopeFields,\n ORG_SCOPE_TOKEN,\n type PluginApiHandler,\n type ScopeToken,\n} from '@aglyn/aglyn/server'\nimport {\n applyArtifactUpdate,\n planArtifactUpdate,\n summarizeSchemaChange,\n type ArtifactChange,\n type ArtifactUpdatePlan,\n} from '../model/artifact-merge'\nimport { ARTIFACT_BASE_COLLECTION } from '@aglyn/aglyn/app-utils/marketplace-provenance'\nimport { dropPluginSiteCache } from '@aglyn/aglyn/plugin-manager/plugin-site-cache'\nimport { firebaseAdmin, getOrgForHost } from '@aglyn/tenant-data-admin'\nimport { resolveOrgPermissions } from '@aglyn/tenant-runtime/org-permissions'\nimport {\n listingArtifactType,\n resolveInstalledDatasetSchema,\n} from '../model/marketplace'\nimport { readPublishedProps } from '../model/marketplace-props'\nimport { recordInstallProvenance } from './provenance'\nimport { canActAsPublisher } from './publisher-profile'\nimport { requirePurchase } from './purchase-entitlement'\nimport { recordVersionMove } from './version-stats'\nimport { isPublisherSecurityLocked } from './sale-risk'\n\n/**\n * Updating a COPIED marketplace artifact (AGL-1018).\n *\n * The install routes update by overwriting: a component re-install writes the\n * publisher's tree straight over the workspace's copy, and a template or layout\n * re-install soft-deletes the previous bundle. Both are silent, and both throw\n * away work the customer did after installing.\n *\n * This route never overwrites without saying what will be lost. `preview`\n * classifies every field against AGL-1015's base snapshot and returns the plan;\n * `apply` writes only what the plan (and, for conflicts, the caller) chose.\n * There are two ways to take a new version and both are safe:\n *\n * * **merge** — take the publisher's changes to fields nobody here edited, keep\n * this workspace's edits, and overwrite a contested field only when it was\n * explicitly picked.\n * * **copy** — install the new version fresh and DETACH the customised copy,\n * which keeps it as an ordinary workspace artifact rather than deleting it.\n *\n * Without a base snapshot only `copy` is offered. Inventing an origin to diff\n * against is how a merge becomes confidently wrong.\n */\n\ntype Mode = 'merge' | 'copy'\n\n/** What a preview tells the client, and what the dialog renders. */\ninterface UpdatePreview {\n artifactType: string\n installedVersion: string | null\n availableVersion: string | null\n /** A three-way merge is possible: a base snapshot exists and the type supports it. */\n mergeable: boolean\n /** Why not, when it isn't. */\n reason?: string\n safe: ArtifactChange[]\n kept: ArtifactChange[]\n conflicts: ArtifactChange[]\n unchanged: number\n identical: boolean\n /** Dataset schemas only: what the change does to existing records. */\n schema?: {\n added: string[]\n removed: string[]\n retyped: string[]\n additiveOnly: boolean\n recordCount: number\n }\n}\n\n/** Everything the route needs to diff and write one copied artifact. */\ninterface Target {\n ref: FirebaseFirestore.DocumentReference\n /** The copy as it is now, in the same shape the base snapshot holds. */\n current: unknown\n /** Fields to write alongside merged content. */\n write: (content: any) => Record<string, unknown>\n /** Fields that detach this copy from its listing, for `copy` mode. */\n detach?: Record<string, unknown>\n /**\n * The AGL-1037 scope the FORKED copy is created with, or `null` when the\n * destination collection is not scoped (AGL-1478).\n *\n * Required, and required on purpose. `copy` mode writes a brand-new\n * document into `target.ref.parent` — whatever collection that turns out\n * to be — from a spread of carried fields, and a spread cannot carry a\n * field the source never had. For `orgs/{orgId}/datasets` that is the\n * AGL-1466 bug exactly: an unstamped dataset is not \"unrestricted\", it is\n * matched by no `array-contains-any` reader in the product, so it renders\n * on no site and is missing from the workflows and reference-health cards\n * even for an org-wide member.\n *\n * Making it optional with a sensible default would have been the smaller\n * diff and is the thing that fails: the two `mediaFolders` creators that\n * each forgot `visibleTo` were both written by people who had the field\n * in front of them. A sixth artifact type cannot be added here without\n * answering this, which is the only guarantee that survives a stranger.\n *\n * `null` is a real answer, not an escape hatch: components live in\n * `hosts/{hostId}/components` and layouts in `hosts/{hostId}/templates`,\n * both private to their host by construction and neither carrying a scope.\n */\n forkScope: readonly ScopeToken[] | null\n /**\n * The declared properties the new version carries (AGL-2933), for a\n * component or layout, sanitized; `undefined` when the version was published\n * before properties were carried, or the type declares none.\n *\n * Not part of the merged content. Every base snapshot recorded before this\n * field existed holds only the tree, so diffing properties three ways would\n * read each installed copy's declarations as a local edit to keep. They are\n * the publisher's contract with the tree instead: whatever the merge takes\n * of the nodes binds to them, so the incoming list replaces the installed\n * one, in both modes.\n */\n props?: ReturnType<typeof readPublishedProps>\n}\n\nconst trimChanges = (changes: ArtifactChange[]): ArtifactChange[] =>\n // The dialog lists changes; a 400-node tree that changed wholesale would\n // otherwise ship its entire content twice through a JSON response.\n changes.slice(0, 200)\n\nexport const updateArtifactHandler: PluginApiHandler = async (req, res) => {\n if (req.method !== 'POST') {\n return res.status(405).json({ error: 'Method not allowed' })\n }\n const listingId = String(req.body?.listingId ?? '')\n const hostId = String(req.body?.hostId ?? '')\n const action = req.body?.action === 'apply' ? 'apply' : 'preview'\n const mode: Mode = req.body?.mode === 'copy' ? 'copy' : 'merge'\n const takePaths: string[] = Array.isArray(req.body?.takePaths)\n ? req.body.takePaths.map((path: unknown) => String(path))\n : []\n const confirmDestructive = Boolean(req.body?.confirmDestructive)\n if (!listingId || !hostId) {\n return res.status(400).json({ error: 'Missing listingId or hostId' })\n }\n const authorization = String(req.headers.authorization ?? '')\n const idToken = authorization.startsWith('Bearer ')\n ? authorization.slice('Bearer '.length)\n : undefined\n if (!idToken) return res.status(401).json({ error: 'Unauthenticated' })\n\n try {\n const decoded = await firebaseAdmin.app().auth().verifyIdToken(idToken)\n // The same gate installing uses: taking a new version of something is\n // installing it, and a role that cannot install must not be able to\n // rewrite an installed artifact through a different door.\n const membership = await resolveOrgPermissions(decoded.uid, { hostId })\n if (!membership.permissions.installPlugins) {\n return res.status(403).json({\n error: 'Your organization role does not allow installing from the marketplace',\n })\n }\n const firestore = firebaseAdmin.app().firestore()\n const hostRef = firestore.collection('hosts').doc(hostId)\n const hostSnapshot = await hostRef.get()\n if (!hostSnapshot.exists) {\n return res.status(404).json({ error: 'Unknown site' })\n }\n\n const listingRef = firestore.collection('marketplaceListings').doc(listingId)\n const listingSnapshot = await listingRef.get()\n const listing = listingSnapshot.data() as any\n if (\n !listing ||\n listing.deletedAt ||\n // Staff takedown blocks new installs on EVERY artifact type\n // (AGL-2290). AGL-948 extended takedown past plugins in the browse\n // predicate and in `resolveMarketplacePluginVersion`, but the gate that\n // decides whether content is HANDED OVER was only ever added to\n // `install-plugin.ts`. So a component, theme, template, layout, email\n // template or dataset schema that staff had taken down stayed\n // installable by anyone holding its listing id — which makes takedown a\n // suggestion for six of the seven artifact types.\n //\n // No owner exemption, matching `install-plugin.ts`: a takedown is a\n // moderation decision about the artifact, not about who is asking.\n listing.hiddenAt ||\n // A publisher under a SECURITY lock hands nothing over (AGL-3365).\n (await isPublisherSecurityLocked(firestore, listing.profileId))\n ) {\n return res.status(404).json({ error: 'Unknown listing' })\n }\n // Taking a NEW version of a paid listing is buying content, and this\n // route never asked whether the caller still owned it (AGL-1699). The\n // install routes gate; this one only checked the org ROLE, so a buyer\n // who was refunded — and even in `copy` mode, which installs the new\n // version fresh — kept pulling every future paid release through here.\n //\n // Deliberately not a regression for anyone legitimate: a live purchase,\n // a free listing, or the publisher's own listing all pass, and nobody\n // without one of those could have had an installed copy to update.\n // The `priceUsd > 0` test is out here rather than left to the helper so\n // a free listing — most of them — does not pay for a publisher lookup.\n const priceUsd = Number(listing.priceUsd ?? 0)\n if (priceUsd > 0) {\n const unpaid = await requirePurchase({\n firestore,\n buyerUid: decoded.uid,\n // THE ORG THE LICENCE HAS TO COVER (AGL-2331). `membership.orgId` is\n // resolved server-side from the caller's own membership by the\n // permission gate above — never a request-body field — so this is the\n // workspace the install actually lands in, and the only one a purchase\n // can entitle here.\n buyerOrgId: membership.orgId ?? '',\n listingId,\n priceUsd,\n ownsListing: await canActAsPublisher(\n firestore,\n decoded.uid,\n listing.profileId,\n ),\n })\n if (unpaid) return res.status(402).json(unpaid)\n }\n\n const artifactType = listingArtifactType(listing)\n if (artifactType === 'plugin') {\n // Plugins are pinned, not copied: an update moves a pointer at immutable\n // bytes (AGL-1017) and has nothing to merge.\n return res.status(400).json({\n error: 'Plugins update by re-pinning — use the install route',\n })\n }\n\n const versionSnapshot = await listingRef\n .collection('versions')\n .doc(String(listing.latestVersion))\n .get()\n if (!versionSnapshot.exists) {\n return res.status(404).json({ error: 'Listing version missing' })\n }\n const availableVersion =\n listing.latestVersion == null ? null : String(listing.latestVersion)\n\n const orgId = (await getOrgForHost(hostId))?.orgId ?? ''\n\n /** The installed copy, the base it was installed from, and the offer. */\n let target: Target | null = null\n let incoming: unknown = null\n let installedVersion: string | null = null\n let baseSha: string | null = null\n let notMergeable: string | undefined\n let schemaSummary: UpdatePreview['schema']\n\n if (artifactType === 'component') {\n const existing = await hostRef\n .collection('components')\n .where('marketplace.listingId', '==', listingId)\n .limit(1)\n .get()\n const doc = existing.docs.find((entry) => !entry.get('deletedAt'))\n if (!doc) return res.status(404).json({ error: 'Not installed on this site' })\n const version = versionSnapshot.data() as any\n incoming = { rootId: version.rootId, nodes: version.nodes }\n installedVersion = String(\n doc.get('installedFrom.version') ?? doc.get('marketplace.version') ?? '',\n ) || null\n baseSha = doc.get('installedFrom.sha256') ?? null\n target = {\n ref: doc.ref,\n current: {\n rootId: doc.get('rootId'),\n // DECODED, because everything downstream compares by VALUE: the\n // divergence hash, and `planArtifactUpdate`'s three-way diff over\n // dotted paths like `nodes.abc123.props.title`. A stored `Bytes`\n // has no such paths, so the merge would see the whole tree as\n // removed and write an empty one back (AGL-1151).\n nodes: decodeStoredNodes(doc.get('nodes')),\n },\n write: (content) => ({\n rootId: content?.rootId ?? null,\n // Compressed at rest, like every other writer of this document.\n nodes: Buffer.from(encodeStoredNodes(content?.nodes ?? {})!),\n }),\n // Detaching keeps the customised component as an ordinary one: it stays\n // on every page that uses it, and only stops being tracked against the\n // listing — which is what makes \"install as a new copy\" non-destructive\n // without leaving two documents claiming to be the same install.\n detach: {\n marketplace: firebaseAdmin.firestore.FieldValue.delete(),\n detachedFrom: { listingId, version: installedVersion },\n },\n // `hosts/{hostId}/components` is private to its host; no scope.\n forkScope: null,\n props: readPublishedProps(version.props),\n }\n } else if (artifactType === 'layout') {\n const existing = await hostRef\n .collection('templates')\n .where('source.listingId', '==', listingId)\n .get()\n const doc = existing.docs.find(\n (entry) => !entry.get('deletedAt') && entry.get('kind') === 'layout',\n )\n if (!doc) return res.status(404).json({ error: 'Not installed on this site' })\n const layout = versionSnapshot.get('layout') as any\n incoming = { rootId: layout?.rootId, nodes: layout?.nodes }\n installedVersion = String(\n doc.get('installedFrom.version') ?? doc.get('source.version') ?? '',\n ) || null\n baseSha = doc.get('installedFrom.sha256') ?? null\n target = {\n ref: doc.ref,\n current: {\n rootId: doc.get('rootId'),\n // DECODED, because everything downstream compares by VALUE: the\n // divergence hash, and `planArtifactUpdate`'s three-way diff over\n // dotted paths like `nodes.abc123.props.title`. A stored `Bytes`\n // has no such paths, so the merge would see the whole tree as\n // removed and write an empty one back (AGL-1151).\n nodes: decodeStoredNodes(doc.get('nodes')),\n },\n write: (content) => ({\n rootId: content?.rootId ?? null,\n // Compressed at rest, like every other writer of this document.\n nodes: Buffer.from(encodeStoredNodes(content?.nodes ?? {})!),\n }),\n detach: {\n // The customer's own from here on: `authored`, the provenance the\n // templates library's Source filter calls \"Saved here\" (AGL-3321).\n source: { type: 'authored' },\n detachedFrom: { listingId, version: installedVersion },\n },\n // `hosts/{hostId}/templates` is private to its host; no scope.\n forkScope: null,\n props: readPublishedProps(layout?.props),\n }\n } else if (artifactType === 'datasetSchema') {\n if (!orgId) {\n return res.status(404).json({ error: 'Site has no owning organization' })\n }\n const orgRef = firestore.collection('orgs').doc(orgId)\n const datasets = await orgRef.collection('datasets').get()\n const doc = datasets.docs.find(\n (entry) =>\n entry.get('source.listingId') === listingId && !entry.get('deletedAt'),\n )\n if (!doc) {\n return res.status(404).json({ error: 'Not installed in this organization' })\n }\n // Relink the incoming schema onto THIS org's datasets exactly as install\n // does — the base holds the relinked shape, so diffing the published one\n // would report every reference field as a user edit.\n const byLabel: Record<string, string> = {}\n for (const entry of datasets.docs) {\n const label = String(entry.get('displayName') ?? '').toLowerCase()\n if (label && !byLabel[label]) byLabel[label] = entry.id\n }\n const { schema } = resolveInstalledDatasetSchema(\n versionSnapshot.get('datasetSchema') as any,\n byLabel,\n )\n incoming = schema\n installedVersion = String(\n doc.get('installedFrom.version') ?? doc.get('source.version') ?? '',\n ) || null\n baseSha = doc.get('installedFrom.sha256') ?? null\n const current = doc.get('model') ?? { order: doc.get('fields') ?? [], fields: {} }\n const summary = summarizeSchemaChange(current as any, schema as any)\n // The count is read for the preview, not estimated: \"3 fields will be\n // removed\" means nothing without \"from 1,240 records\".\n const records = await doc.ref\n .collection('records')\n .count()\n .get()\n .catch(() => null)\n schemaSummary = {\n added: summary.added,\n removed: summary.removed,\n retyped: summary.retyped,\n additiveOnly: summary.additiveOnly,\n recordCount: records?.data().count ?? 0,\n }\n const installedScope = doc.get('visibleTo')\n target = {\n ref: doc.ref,\n current,\n write: (content) => ({\n model: content,\n // The v1 flat list the older editor still reads, kept in step.\n fields: content?.order ?? [],\n }),\n // A fork INHERITS the copy it forked from, never a fresh default: a\n // dataset restricted to one client's site must not reappear org-wide\n // because somebody took a publisher update as a separate copy. An\n // absent scope reads as org-wide everywhere else (`visibleToHost`),\n // so it does here. A stored empty array — \"visible to nobody\" — is\n // passed through and throws at the write rather than being widened,\n // which is the right direction to fail in.\n forkScope: Array.isArray(installedScope)\n ? (installedScope as ScopeToken[])\n : [ORG_SCOPE_TOKEN],\n }\n } else if (artifactType === 'theme') {\n // A theme lives on the host document itself, so there is no artifact doc\n // to look up and no \"not installed on this site\" — the site either runs\n // this listing's theme or it does not.\n if (hostSnapshot.get('themeInstalledFrom.listingId') !== listingId) {\n return res.status(404).json({ error: 'Not installed on this site' })\n }\n incoming = versionSnapshot.get('theme') ?? {}\n installedVersion =\n String(hostSnapshot.get('themeInstalledFrom.version') ?? '') || null\n baseSha = hostSnapshot.get('themeInstalledFrom.sha256') ?? null\n target = {\n ref: hostRef,\n current: hostSnapshot.get('theme') ?? {},\n write: (content) => ({ theme: content ?? {} }),\n // No `detach`: a site has exactly one theme, so \"install as a separate\n // copy\" has nowhere to put the second one. Merge is the only safe way\n // to take a theme update, and the preview says so.\n //\n // A theme is a FIELD on the host document, so there is no document to\n // scope even if it could fork.\n forkScope: null,\n }\n } else if (artifactType === 'template') {\n // A published template bundle's screens carry no stable identity — only a\n // displayName and a slug — so there is nothing to pair an old screen to a\n // new one by. Any merge would be a guess dressed up as a diff.\n notMergeable =\n 'Template bundles have no stable per-screen identity, so their screens ' +\n 'cannot be matched across versions. Installing the new version replaces ' +\n 'this bundle in your Templates library — pages you already created from ' +\n 'the old templates are ordinary screens and are untouched.'\n } else if (artifactType === 'emailTemplate') {\n // Already safe by construction: an email template installs as an inactive\n // draft version, so taking a new one changes nothing that is being sent.\n notMergeable =\n 'Email templates install as a draft version and never replace what your ' +\n 'site is sending. Add the new version as another draft, then activate ' +\n 'it when you are ready.'\n }\n\n if (notMergeable) {\n const preview: UpdatePreview = {\n artifactType,\n installedVersion: null,\n availableVersion,\n mergeable: false,\n reason: notMergeable,\n safe: [],\n kept: [],\n conflicts: [],\n unchanged: 0,\n identical: false,\n }\n return res.status(200).json({ preview })\n }\n if (!target) {\n return res.status(404).json({ error: 'Nothing installed to update' })\n }\n\n const baseSnapshot = baseSha\n ? await firestore\n .collection(ARTIFACT_BASE_COLLECTION)\n .doc(baseSha)\n .get()\n .catch(() => null)\n : null\n const base = baseSnapshot?.exists ? baseSnapshot.get('content') : undefined\n const mergeable = base !== undefined\n\n let plan: ArtifactUpdatePlan | null = null\n if (mergeable) plan = planArtifactUpdate(base, target.current, incoming)\n\n if (action === 'preview') {\n const preview: UpdatePreview = {\n artifactType,\n installedVersion,\n availableVersion,\n mergeable,\n ...(mergeable\n ? {}\n : {\n reason:\n 'This was installed before update tracking recorded an ' +\n 'original, so there is no way to tell your changes from the ' +\n 'publisher’s. Installing the new version as a separate copy ' +\n 'keeps yours intact.',\n }),\n safe: trimChanges(plan?.safe ?? []),\n kept: trimChanges(plan?.kept ?? []),\n conflicts: trimChanges(plan?.conflicts ?? []),\n unchanged: plan?.unchanged ?? 0,\n identical: plan?.identical ?? false,\n ...(schemaSummary ? { schema: schemaSummary } : {}),\n }\n return res.status(200).json({ preview })\n }\n\n // ---- apply ----\n const now = firebaseAdmin.firestore.FieldValue.serverTimestamp()\n\n if (mode === 'merge') {\n if (!plan) {\n return res.status(409).json({\n error:\n 'No original snapshot for this install — it can only be taken as a new copy',\n })\n }\n // A schema change that removes or retypes a field reinterprets rows that\n // already exist. It is applicable, but never without the caller having\n // been told the count and said yes to it specifically.\n if (schemaSummary && !schemaSummary.additiveOnly && !confirmDestructive) {\n return res.status(409).json({\n error:\n `This update removes or retypes ${\n schemaSummary.removed.length + schemaSummary.retyped.length\n } field(s) on a dataset holding ${schemaSummary.recordCount} record(s).`,\n needsConfirmation: true,\n schema: schemaSummary,\n })\n }\n const merged = applyArtifactUpdate(plan, target.current, { takePaths })\n const provenance = await recordInstallProvenance({\n firestore,\n listingId,\n listing,\n version: listing.latestVersion,\n artifactType: artifactType as never,\n // The base for NEXT time is the publisher's version, not the merged\n // result: the base answers \"what did the publisher ship\", and seeding\n // it with the merge would quietly launder this update's kept edits into\n // the publisher's column, where the next update could take them away.\n content: incoming,\n })\n await target.ref.set(\n {\n ...target.write(merged.content),\n ...(target.props && { props: target.props }),\n // A theme is a FIELD on the host document, not a document of its own,\n // so it namespaces its provenance (`themeInstalledFrom`) and has no\n // legacy `source`/`marketplace` pair — writing those here would put a\n // stray marketplace stamp on the host itself, where `resolveProvenance`\n // would later read it as the host being an installed artifact.\n ...(artifactType === 'theme'\n ? { themeInstalledFrom: provenance.installedFrom }\n : {\n installedFrom: provenance.installedFrom,\n // The legacy per-type version fields the console still reads.\n ...(artifactType === 'component'\n ? {\n marketplace: {\n listingId,\n profileId: listing.profileId ?? null,\n version: listing.latestVersion ?? null,\n },\n }\n : {\n source: {\n type: 'marketplace' as const,\n listingId,\n version: listing.latestVersion ?? null,\n },\n }),\n }),\n updatedAt: now,\n },\n { merge: true },\n )\n // A merged THEME repaints every page of the site the moment it lands,\n // and nothing publishes it — so the site's cached pages go now rather\n // than within the hour (AGL-3386). Awaited before the response, and it\n // never throws: a failed drop leaves the update applied.\n if (artifactType === 'theme') {\n await dropPluginSiteCache({\n hostIds: [hostId],\n reason: 'marketplace theme updated',\n })\n }\n // Per-version tally (AGL-1036): this install just moved versions, which\n // is the whole reason the per-version count exists.\n await recordVersionMove({\n firestore,\n listingRef,\n artifactType,\n from: installedVersion,\n to: availableVersion,\n })\n return res.status(200).json({\n updated: true,\n mode,\n version: availableVersion,\n applied: merged.applied.length,\n keptLocal: plan.kept.length,\n skipped: merged.skipped,\n baseStored: provenance.baseStored,\n })\n }\n\n // ---- copy ----\n if (!target.detach) {\n // Only types with a detachable link get a copy from here. A dataset's\n // \"new copy\" is a whole new dataset, which consumes org quota and lands\n // empty — exactly what the install route already does, with the quota\n // check this one does not have.\n //\n // This return is also, today, the only thing keeping the fork below\n // from creating an unstamped `orgs/{orgId}/datasets` document\n // (AGL-1478): a dataset never reaches it because it has no `detach`.\n // That is a guard by accident, three statements away from the write\n // and phrased as a quota argument — give `datasetSchema` a `detach`\n // for any reason and the scope hole opens with it. `Target.forkScope`\n // is the guard that stays true whichever way this branch goes.\n return res.status(400).json({\n error:\n artifactType === 'theme'\n ? // A site has one theme, so there is no second place to put a\n // copy. Merging keeps this site's edits; the way back to the\n // previous theme is the install route's `revert`.\n 'A site has only one theme, so this update can only be merged. ' +\n 'Your edits to fields the publisher did not touch are kept.'\n : 'Install a fresh copy of this from the listing instead',\n })\n }\n // The customised copy is detached rather than deleted or overwritten: it\n // keeps working everywhere it is used, and only stops being the document\n // this listing updates. Without the detach two documents would claim the\n // same install and the next update would pick between them arbitrarily.\n const provenance = await recordInstallProvenance({\n firestore,\n listingId,\n listing,\n version: listing.latestVersion,\n artifactType: artifactType as never,\n content: incoming,\n })\n const batch = firestore.batch()\n if (target.detach) {\n batch.set(target.ref, { ...target.detach, updatedAt: now }, { merge: true })\n }\n const collectionRef = target.ref.parent\n const freshRef = collectionRef.doc(createResourceUid())\n const carried = (await target.ref.get()).data() ?? {}\n const freshName = String(listing.displayName ?? carried.displayName ?? 'Untitled')\n batch.set(freshRef, {\n // Everything that is not content travels from the copy being replaced —\n // the display name, description and kind — so the new one lands in the\n // same place in the same library rather than as an unrecognisable stub.\n ...(carried.kind ? { kind: carried.kind } : {}),\n displayName: freshName,\n ...(listing.description ? { description: listing.description } : {}),\n // Not part of the `carried` spread above, deliberately: a spread\n // cannot carry a field the source is missing, and \"the source had one\"\n // is not the question a scoped collection asks. `Target.forkScope`\n // makes every artifact type answer it (AGL-1478), and this throws\n // rather than storing a scope nobody can see through.\n ...newResourceScopeFields(target.forkScope),\n ...target.write(incoming),\n ...(target.props\n ? { props: target.props }\n : Array.isArray(carried.props)\n ? { props: carried.props }\n : {}),\n ...(artifactType === 'component'\n ? {\n marketplace: {\n listingId,\n profileId: listing.profileId ?? null,\n version: listing.latestVersion ?? null,\n },\n }\n : {\n source: {\n type: 'marketplace' as const,\n listingId,\n version: listing.latestVersion ?? null,\n },\n }),\n installedFrom: provenance.installedFrom,\n deletedAt: null,\n // The keys the fresh copy's list finds it by (AGL-3321), for whichever\n // artifact collection it lands in.\n ...artifactCreateListKeys(collectionRef.id, {\n kind: carried.kind,\n displayName: freshName,\n source: { type: 'marketplace' },\n }),\n createdAt: now,\n updatedAt: now,\n })\n await batch.commit()\n // Per-version tally (AGL-1036). Still a move, not a second install: the\n // detached copy stops being tracked against the listing, so the version it\n // held is genuinely vacated.\n await recordVersionMove({\n firestore,\n listingRef,\n artifactType,\n from: installedVersion,\n to: availableVersion,\n })\n return res.status(200).json({\n updated: true,\n mode,\n version: availableVersion,\n newId: freshRef.id,\n detached: Boolean(target.detach),\n baseStored: provenance.baseStored,\n })\n } catch (error) {\n console.error(error)\n return res.status(500).json({ error: 'Update failed' })\n }\n}\n\nexport default updateArtifactHandler\n"],"names":["artifactCreateListKeys","createResourceUid","decodeStoredNodes","encodeStoredNodes","newResourceScopeFields","ORG_SCOPE_TOKEN","applyArtifactUpdate","planArtifactUpdate","summarizeSchemaChange","ARTIFACT_BASE_COLLECTION","dropPluginSiteCache","firebaseAdmin","getOrgForHost","resolveOrgPermissions","listingArtifactType","resolveInstalledDatasetSchema","readPublishedProps","recordInstallProvenance","canActAsPublisher","requirePurchase","recordVersionMove","isPublisherSecurityLocked","trimChanges","changes","slice","updateArtifactHandler","req","res","method","status","json","error","listingId","String","body","hostId","action","mode","takePaths","Array","isArray","map","path","confirmDestructive","Boolean","authorization","headers","idToken","startsWith","length","undefined","listing","decoded","app","auth","verifyIdToken","membership","uid","permissions","installPlugins","firestore","hostRef","collection","doc","hostSnapshot","get","exists","listingRef","listingSnapshot","data","deletedAt","hiddenAt","profileId","priceUsd","Number","unpaid","buyerUid","buyerOrgId","orgId","ownsListing","artifactType","versionSnapshot","latestVersion","availableVersion","target","incoming","installedVersion","baseSha","notMergeable","schemaSummary","existing","where","limit","docs","find","entry","version","rootId","nodes","ref","current","write","content","Buffer","from","detach","marketplace","FieldValue","delete","detachedFrom","forkScope","props","layout","source","type","orgRef","datasets","byLabel","label","toLowerCase","id","schema","order","fields","summary","records","count","catch","added","removed","retyped","additiveOnly","recordCount","installedScope","model","theme","preview","mergeable","reason","safe","kept","conflicts","unchanged","identical","baseSnapshot","base","plan","now","serverTimestamp","needsConfirmation","merged","provenance","set","themeInstalledFrom","installedFrom","updatedAt","merge","hostIds","to","updated","applied","keptLocal","skipped","baseStored","batch","collectionRef","parent","freshRef","carried","freshName","displayName","kind","description","createdAt","commit","newId","detached","console"],"mappings":"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"}