@aglyn/plugins-marketplace 1.11.0 → 1.13.0
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 +14 -14
- package/src/lib/components/listing-content.component.js +78 -36
- package/src/lib/components/listing-content.component.js.map +1 -1
- package/src/lib/components/listing-detail-page.component.js +22 -9
- package/src/lib/components/listing-detail-page.component.js.map +1 -1
- package/src/lib/components/listing-review-status.component.d.ts +8 -0
- package/src/lib/components/listing-review-status.component.js +23 -3
- package/src/lib/components/listing-review-status.component.js.map +1 -1
- package/src/lib/components/listing-verification-request.component.d.ts +8 -0
- package/src/lib/components/listing-verification-request.component.js +12 -5
- package/src/lib/components/listing-verification-request.component.js.map +1 -1
- package/src/lib/components/marketplace-hub.component.js +2 -1
- package/src/lib/components/marketplace-hub.component.js.map +1 -1
- package/src/lib/components/org-seller-panel.component.d.ts +10 -0
- package/src/lib/components/org-seller-panel.component.js +26 -4
- package/src/lib/components/org-seller-panel.component.js.map +1 -1
- package/src/lib/components/starter-picker-shelf.component.js +13 -8
- package/src/lib/components/starter-picker-shelf.component.js.map +1 -1
- package/src/lib/declarations.d.ts +2 -1
- package/src/lib/declarations.js +5 -1
- package/src/lib/declarations.js.map +1 -1
- package/src/lib/model/marketplace-catalog.js +1 -1
- package/src/lib/model/marketplace-catalog.js.map +1 -1
- package/src/lib/model/marketplace-publisher-permissions.d.ts +26 -0
- package/src/lib/model/marketplace-publisher-permissions.js +64 -0
- package/src/lib/model/marketplace-publisher-permissions.js.map +1 -0
- package/src/lib/model/marketplace.js +2 -1
- package/src/lib/model/marketplace.js.map +1 -1
- package/src/lib/model/own-listing.d.ts +52 -0
- package/src/lib/model/own-listing.js +72 -0
- package/src/lib/model/own-listing.js.map +1 -0
- package/src/lib/model/starter-query.js +2 -0
- package/src/lib/model/starter-query.js.map +1 -1
- package/src/lib/server/connect.js +11 -5
- package/src/lib/server/connect.js.map +1 -1
- package/src/lib/server/install-dataset-schema.js +10 -2
- package/src/lib/server/install-dataset-schema.js.map +1 -1
- package/src/lib/server/install-email-starter.js +8 -2
- package/src/lib/server/install-email-starter.js.map +1 -1
- package/src/lib/server/install-email-template.js +8 -2
- package/src/lib/server/install-email-template.js.map +1 -1
- package/src/lib/server/install-layout.js +8 -2
- package/src/lib/server/install-layout.js.map +1 -1
- package/src/lib/server/install-plugin.js +24 -20
- package/src/lib/server/install-plugin.js.map +1 -1
- package/src/lib/server/install-template.js +8 -5
- package/src/lib/server/install-template.js.map +1 -1
- package/src/lib/server/install-theme.js +8 -2
- package/src/lib/server/install-theme.js.map +1 -1
- package/src/lib/server/install.js +10 -5
- package/src/lib/server/install.js.map +1 -1
- package/src/lib/server/listing-versions.js +24 -9
- package/src/lib/server/listing-versions.js.map +1 -1
- package/src/lib/server/own-listing.d.ts +46 -0
- package/src/lib/server/own-listing.js +52 -0
- package/src/lib/server/own-listing.js.map +1 -0
- package/src/lib/server/preview-image.d.ts +1 -1
- package/src/lib/server/preview-image.js +9 -8
- package/src/lib/server/preview-image.js.map +1 -1
- package/src/lib/server/publish-plugin.js +17 -19
- package/src/lib/server/publish-plugin.js.map +1 -1
- package/src/lib/server/publisher-permissions.d.ts +63 -0
- package/src/lib/server/publisher-permissions.js +51 -0
- package/src/lib/server/publisher-permissions.js.map +1 -0
- package/src/lib/server/publisher-profile-save.d.ts +4 -2
- package/src/lib/server/publisher-profile-save.js +12 -5
- package/src/lib/server/publisher-profile-save.js.map +1 -1
- package/src/lib/server/publisher-profile.d.ts +18 -6
- package/src/lib/server/publisher-profile.js +19 -8
- package/src/lib/server/publisher-profile.js.map +1 -1
- package/src/lib/server/reviews.js +6 -5
- package/src/lib/server/reviews.js.map +1 -1
- package/src/lib/server/sale-risk.d.ts +0 -6
- package/src/lib/server/sale-risk.js +0 -9
- package/src/lib/server/sale-risk.js.map +1 -1
- package/src/lib/server/sale.js +10 -9
- package/src/lib/server/sale.js.map +1 -1
- package/src/lib/server/site-starter-source.js +43 -17
- package/src/lib/server/site-starter-source.js.map +1 -1
- package/src/lib/server/update-artifact.js +11 -4
- package/src/lib/server/update-artifact.js.map +1 -1
- package/src/lib/server/verification-request.js +23 -7
- package/src/lib/server/verification-request.js.map +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/components/starter-picker-shelf.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 { pluginDocsHelp } from '@aglyn/aglyn/app-utils/docs-help'\nimport { resolveMediaSrc } from '@aglyn/aglyn/app-utils/media-ref'\nimport type { SiteStarterFacets } from '@aglyn/aglyn/app-utils/site-starter'\nimport type { ConsoleHostStarterPickerZoneProps } from '@aglyn/aglyn/plugin-manager/feature-plugins'\nimport type { SiteStarterSummary } from '@aglyn/aglyn/plugin-manager/plugin-site-starters'\nimport { mdiWeb } from '@aglyn/shared-data-mdi'\nimport {\n OptionCardGrid,\n type OptionCard,\n type OptionCardChip,\n type OptionCardImage,\n} from '@aglyn/shared-ui-jsx/components/option-card-grid.component'\nimport {\n Alert,\n Box,\n Button,\n Link,\n LinearProgress,\n Skeleton,\n Stack,\n type SxProps,\n type Theme,\n} from '@mui/material'\nimport { useEffect, useMemo, useRef, useState } from 'react'\nimport { useStarterAnswers } from '../hooks/use-starter-answers'\nimport { useStarterPurchase } from '../hooks/use-starter-purchase'\nimport { useStarterSearch } from '../hooks/use-starter-search'\nimport { starterCardFacts, starterPriceLabel, type StarterCardFact } from '../model/starter-query'\n\n/**\n * What a picked starter's price or licence says, held at the foot of the\n * visible cards while the shelf is in view, so \"Buy and continue\" is beside\n * whichever card was clicked rather than below all of them.\n */\nconst PICK_NOTE_SX: SxProps<Theme> = {\n position: 'sticky',\n bottom: (theme) => theme.spacing(2),\n zIndex: 1,\n boxShadow: 3,\n}\n\n/**\n * The payment, where the note was, on its own surface. It may fill the window\n * below the app bar, so a form that has grown keeps its button on screen; a\n * smaller window scrolls inside it.\n */\nconst PAY_PANEL_SX: SxProps<Theme> = {\n position: 'sticky',\n bottom: (theme) => theme.spacing(2),\n zIndex: 1,\n boxShadow: 3,\n p: 2,\n borderRadius: 1,\n bgcolor: 'background.paper',\n maxHeight: (theme) => `calc(100dvh - ${theme.spacing(10)})`,\n overflow: 'auto',\n}\n\n/** The guide to site starters, linked from what the shelf says when it has nothing to show. */\nconst STARTERS_HELP = pluginDocsHelp('siteStarters')\n\n/**\n * A starter's preview as the card draws it: the size the platform's own\n * style previews are rendered at (square), so a row of cards stays level and\n * a publisher's picture is cropped to the same shape from its top.\n */\nconst PREVIEW_SIZE = { width: 800, height: 800 } as const\n\n/** How many placeholder cards stand in for the first answer. */\nconst PLACEHOLDER_CARDS = 3\n\n/** Statuses that mean this workspace may not have starters offered here: the dialog's own choice instead. */\nconst REFUSED = new Set([401, 403, 503])\n\n/** How each fact's chip is colored, from the theme: the price as the marketplace's own cards color it. */\nfunction chipOf(fact: StarterCardFact, row: SiteStarterSummary): OptionCardChip {\n switch (fact.key) {\n case 'price':\n return row.priceUsd > 0\n ? { label: fact.label, color: 'primary', variant: 'filled' }\n : { label: fact.label, color: 'success', variant: 'outlined' }\n case 'official':\n return { label: fact.label, color: 'primary', variant: 'outlined' }\n case 'ai':\n return { label: fact.label, color: 'secondary', variant: 'outlined' }\n default:\n return { label: fact.label }\n }\n}\n\n/** The picture a card draws, resolved the way the listing's own pages resolve it. */\nfunction previewOf(row: SiteStarterSummary): OptionCardImage | null {\n const src = resolveMediaSrc(row.previewImageUrl)\n if (!src) return null\n return { src, alt: `A preview of ${row.displayName || 'this starter'}`, ...PREVIEW_SIZE }\n}\n\n/** One starter as a card in the shared option grid. */\nfunction cardOf(row: SiteStarterSummary): OptionCard {\n const image = previewOf(row)\n return {\n id: row.listingId,\n title: row.displayName || 'Untitled starter',\n ...(row.authorName ? { description: `By ${row.authorName}` } : {}),\n ...(image ? { image } : { icon: mdiWeb.path }),\n chips: starterCardFacts(row).map((fact) => chipOf(fact, row)),\n }\n}\n\nconst narrowed = (filter: Partial<SiteStarterFacets>): boolean =>\n (['kind', 'base', 'fonts'] as const).some((key) => typeof filter[key] === 'string' && filter[key] !== '')\n\n/** A row with what the dialog or a purchase knows about owning it laid over the search's answer. */\nconst ownedOf = (row: SiteStarterSummary, override: boolean | undefined): boolean =>\n override ?? row.owned === true\n\n/** What an answer with no starters says, in the words of what was asked. */\nfunction emptyMessage(props: ConsoleHostStarterPickerZoneProps): string {\n if (props.search.trim()) return 'No starters match — try fewer words'\n if (narrowed(props.filter ?? {})) return 'No starters match these choices — try another'\n return props.mode === 'ai' ? 'No AI starters yet' : 'No starters yet'\n}\n\n/**\n * The marketplace's starters in \"Choose your starter\" (AGL-4049): the\n * `hostStarterPicker` zone, drawn before the site exists.\n *\n * Lists what the dialog asks for — its mode, its search words and its\n * filters — as cards of the shared option grid: a picture, the name, who made\n * it, and its price, kind, page count and the \"AI\" and platform marks as\n * chips. NOTHING is selected when it opens; a person picks, and the pick goes\n * to the dialog through `onPick`. It creates nothing: the dialog creates the\n * site from the starter it was handed.\n *\n * `ready` tells the dialog whether a picker is on screen. The first answer\n * decides: starters, or a search that found none (its empty state is a\n * picker with nothing in it yet), answer `true`; a workspace refused the\n * starters (no permission, the release flag off, no source) or a catalog with\n * nothing in it answers `false`, and the dialog offers its own choice\n * instead. Once shown, a later refusal hands back the same way; a later\n * failure is said here with a way to ask again.\n *\n * A picked starter with a price the workspace has not paid says so under\n * the cards, with \"Buy and continue\": the marketplace's payment, in place, on\n * the card on file, with the site still to be created. One it owns says that\n * instead. The dialog's own `selected` wins over the shelf's pick: it is how\n * a pick restored after paying is shown chosen, and how one the create\n * refused is cleared.\n *\n * An AI starter the workspace may use — free, owned, or just bought — has\n * the answers it carries read once it is picked (AGL-4049 phase 4), and\n * handed to the dialog with it (`onPick(pick, { answers })`), which goes on\n * to the AI start's questions with them. A priced one not yet bought is\n * never read: its answers are what it sells, and the door would ask payment\n * for them. A door that still asks is told back to the dialog as unowned.\n */\nexport function StarterPickerShelf(props: ConsoleHostStarterPickerZoneProps) {\n const { orgId, orgSlug, mode, search, filter, onPick, ready, selected } = props\n // The pick the dialog opened on, restored after paying for it: asked for\n // by every search from then on, so it is listed (first) wherever the\n // search would have put it, and never moves under the person's cursor.\n const [restored] = useState(() => selected?.listingId ?? null)\n const found = useStarterSearch({ orgId, mode, word: search, filter, include: restored })\n const [picked, setPicked] = useState<string | null>(null)\n const [shown, setShown] = useState(false)\n // Starters a purchase attempt found this workspace already owns.\n const [ownedHere, setOwnedHere] = useState<ReadonlySet<string>>(() => new Set())\n // AI starters whose answers the door still asked payment for (AGL-4049).\n const [unpaidHere, setUnpaidHere] = useState<ReadonlySet<string>>(() => new Set())\n const purchase = useStarterPurchase(orgId, orgSlug)\n const starterAnswers = useStarterAnswers(orgId)\n const value = selected !== undefined ? (selected?.listingId ?? null) : picked\n const pickedRow = value ? (found.rows.find((row) => row.listingId === value) ?? null) : null\n const ownedOverride = (row: SiteStarterSummary): boolean | undefined =>\n ownedHere.has(row.listingId)\n ? true\n : unpaidHere.has(row.listingId)\n ? false\n : selected?.listingId === row.listingId\n ? selected.owned\n : undefined\n\n const refused = found.error !== null && REFUSED.has(found.error.status)\n // `null` until the first answer, which is the only one that may decide\n // there is nothing to show; after that only a refusal takes it back.\n const verdict: boolean | null = !found.settled\n ? null\n : refused\n ? false\n : shown\n ? true\n : found.error\n ? false\n : found.rows.length > 0 || search.trim() !== '' || narrowed(filter ?? {})\n\n useEffect(() => {\n if (verdict === true) setShown(true)\n if (verdict === false) setShown(false)\n }, [verdict])\n\n // Each distinct verdict once, whatever identity the dialog's callback has.\n const readyRef = useRef(ready)\n readyRef.current = ready\n const reported = useRef<boolean | null>(null)\n useEffect(() => {\n if (verdict === null || reported.current === verdict) return\n reported.current = verdict\n readyRef.current(verdict)\n }, [verdict])\n\n // A pick the dialog restored is handed back once the shelf has read it,\n // so the dialog holds the starter as the search describes it.\n const onPickRef = useRef(onPick)\n onPickRef.current = onPick\n const handedBack = useRef(false)\n useEffect(() => {\n if (!restored || handedBack.current || !pickedRow || pickedRow.listingId !== restored) return\n handedBack.current = true\n const override = ownedOverride(pickedRow)\n onPickRef.current(override === undefined ? pickedRow : { ...pickedRow, owned: override })\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [restored, pickedRow])\n\n // The picked AI starter's answers (AGL-4049), read once it is one the\n // workspace may use; keyed on the listing and whether it is owned, so a\n // purchase that makes it usable reads it, and nothing reads it twice.\n const pickedOwned = pickedRow ? ownedOf(pickedRow, ownedOverride(pickedRow)) : false\n const answersKey =\n pickedRow && pickedRow.facets.mode === 'ai' && (pickedRow.priceUsd <= 0 || pickedOwned)\n ? `${pickedRow.listingId}:${pickedOwned}`\n : null\n const [reading, setReading] = useState<{ key: string; failed: string | null } | null>(null)\n const [readAttempt, setReadAttempt] = useState(0)\n const askedAnswers = useRef<string | null>(null)\n useEffect(() => {\n if (!answersKey || !pickedRow || askedAnswers.current === answersKey) return\n askedAnswers.current = answersKey\n const row = pickedRow\n const override = ownedOverride(row)\n const handed = override === undefined ? row : { ...row, owned: override }\n setReading({ key: answersKey, failed: null })\n void starterAnswers.read(row.listingId).then((outcome) => {\n if (askedAnswers.current !== answersKey) return\n if (outcome.status === 'failed') return setReading({ key: answersKey, failed: outcome.message })\n setReading(null)\n if (outcome.status === 'ready') return onPickRef.current(handed, { answers: outcome.answers })\n setUnpaidHere((current) => new Set(current).add(row.listingId))\n onPickRef.current({ ...row, owned: false })\n })\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [answersKey, readAttempt])\n const readingPicked = reading !== null && reading.key === answersKey ? reading : null\n const readAgain = () => {\n askedAnswers.current = null\n setReadAttempt((count) => count + 1)\n }\n\n const options = useMemo(() => found.rows.map(cardOf), [found.rows])\n\n const buy = async (row: SiteStarterSummary) => {\n const outcome = await purchase.buy({ listingId: row.listingId, displayName: row.displayName })\n if (outcome !== 'owned') return\n setOwnedHere((current) => new Set(current).add(row.listingId))\n setUnpaidHere((current) => {\n const next = new Set(current)\n next.delete(row.listingId)\n return next\n })\n onPick({ ...row, owned: true })\n }\n\n if (verdict === false) return null\n if (verdict === null) {\n return (\n <Box\n aria-busy\n aria-label=\"Loading starters\"\n sx={{ display: 'grid', gap: 2, gridTemplateColumns: { xs: '1fr', sm: 'repeat(2, minmax(0, 1fr))', md: 'repeat(3, minmax(0, 1fr))' } }}\n >\n {Array.from({ length: PLACEHOLDER_CARDS }, (_, index) => (\n <Skeleton key={index} variant=\"rounded\" sx={{ height: 'auto', aspectRatio: '1 / 1', borderRadius: 2 }} />\n ))}\n </Box>\n )\n }\n\n return (\n <Stack spacing={2} data-starter-shelf={mode}>\n {found.loading ? <LinearProgress aria-label=\"Searching starters\" /> : null}\n {found.error ? (\n <Alert\n severity=\"error\"\n action={\n <Button color=\"inherit\" size=\"small\" onClick={found.refresh}>\n {'Try again'}\n </Button>\n }\n >\n {found.error.message}\n </Alert>\n ) : options.length === 0 ? (\n <Alert\n severity=\"info\"\n action={\n <Button\n color=\"inherit\"\n size=\"small\"\n component={Link}\n href={STARTERS_HELP.href}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n {'How starters work'}\n </Button>\n }\n >\n {emptyMessage(props)}\n </Alert>\n ) : (\n <OptionCardGrid\n label={mode === 'ai' ? 'AI starters' : 'Starters'}\n options={options}\n value={value}\n columns={{ xs: 1, sm: 2, md: 3 }}\n onChange={(listingId) => {\n const row = found.rows.find((candidate) => candidate.listingId === listingId)\n if (!row) return\n setPicked(listingId)\n const override = ownedOverride(row)\n onPick(override === undefined ? row : { ...row, owned: override })\n }}\n />\n )}\n {pickedRow && pickedRow.priceUsd > 0 ? (\n ownedOf(pickedRow, ownedOverride(pickedRow)) ? (\n <Alert severity=\"success\" data-starter-owned={pickedRow.listingId} sx={PICK_NOTE_SX}>\n {'You own this starter. Create your site from it whenever you are ready.'}\n </Alert>\n ) : purchase.panel ? (\n <Box data-starter-pay={pickedRow.listingId} sx={PAY_PANEL_SX}>\n {purchase.panel}\n </Box>\n ) : (\n <Alert\n severity=\"info\"\n data-starter-buy={pickedRow.listingId}\n sx={PICK_NOTE_SX}\n action={\n <Button\n color=\"inherit\"\n size=\"small\"\n disabled={purchase.busy || !orgId}\n onClick={() => void buy(pickedRow)}\n >\n {purchase.busy ? 'Opening…' : 'Buy and continue'}\n </Button>\n }\n >\n {`${pickedRow.displayName || 'This starter'} costs ${starterPriceLabel(pickedRow.priceUsd)}. ` +\n 'Buy it here, then create your site from it.'}\n </Alert>\n )\n ) : null}\n {purchase.error ? <Alert severity=\"error\">{purchase.error}</Alert> : null}\n {readingPicked?.failed ? (\n <Alert\n severity=\"error\"\n data-starter-answers-failed={pickedRow?.listingId}\n action={\n <Button color=\"inherit\" size=\"small\" onClick={readAgain}>\n {'Try again'}\n </Button>\n }\n >\n {readingPicked.failed}\n </Alert>\n ) : readingPicked ? (\n <LinearProgress aria-label=\"Reading this starter’s answers\" />\n ) : null}\n </Stack>\n )\n}\n\nexport default StarterPickerShelf\n"],"names":["pluginDocsHelp","resolveMediaSrc","mdiWeb","OptionCardGrid","Alert","Box","Button","Link","LinearProgress","Skeleton","Stack","useEffect","useMemo","useRef","useState","useStarterAnswers","useStarterPurchase","useStarterSearch","starterCardFacts","starterPriceLabel","PICK_NOTE_SX","position","bottom","theme","spacing","zIndex","boxShadow","PAY_PANEL_SX","p","borderRadius","bgcolor","maxHeight","overflow","STARTERS_HELP","PREVIEW_SIZE","width","height","PLACEHOLDER_CARDS","REFUSED","Set","chipOf","fact","row","key","priceUsd","label","color","variant","previewOf","src","previewImageUrl","alt","displayName","cardOf","image","id","listingId","title","authorName","description","icon","path","chips","map","narrowed","filter","some","ownedOf","override","owned","emptyMessage","props","search","trim","mode","StarterPickerShelf","found","orgId","orgSlug","onPick","ready","selected","restored","word","include","picked","setPicked","shown","setShown","ownedHere","setOwnedHere","unpaidHere","setUnpaidHere","purchase","starterAnswers","value","undefined","pickedRow","rows","find","ownedOverride","has","refused","error","status","verdict","settled","length","readyRef","current","reported","onPickRef","handedBack","pickedOwned","answersKey","facets","reading","setReading","readAttempt","setReadAttempt","askedAnswers","handed","failed","read","then","outcome","message","answers","add","readingPicked","readAgain","count","options","buy","next","delete","aria-busy","aria-label","sx","display","gap","gridTemplateColumns","xs","sm","md","Array","from","_","index","aspectRatio","data-starter-shelf","loading","severity","action","size","onClick","refresh","component","href","target","rel","columns","onChange","candidate","data-starter-owned","panel","data-starter-pay","data-starter-buy","disabled","busy","data-starter-answers-failed"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/components/starter-picker-shelf.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 { pluginDocsHelp } from '@aglyn/aglyn/app-utils/docs-help'\nimport { resolveMediaSrc } from '@aglyn/aglyn/app-utils/media-ref'\nimport type { SiteStarterFacets } from '@aglyn/aglyn/app-utils/site-starter'\nimport type { ConsoleHostStarterPickerZoneProps } from '@aglyn/aglyn/plugin-manager/feature-plugins'\nimport type { SiteStarterSummary } from '@aglyn/aglyn/plugin-manager/plugin-site-starters'\nimport { mdiWeb } from '@aglyn/shared-data-mdi'\nimport {\n OptionCardGrid,\n type OptionCard,\n type OptionCardChip,\n type OptionCardImage,\n} from '@aglyn/shared-ui-jsx/components/option-card-grid.component'\nimport { BlockedControl } from '@aglyn/shared-ui-jsx/components/blocked-control.component'\nimport {\n Alert,\n Box,\n Button,\n Link,\n LinearProgress,\n Skeleton,\n Stack,\n type SxProps,\n type Theme,\n} from '@mui/material'\nimport { useEffect, useMemo, useRef, useState } from 'react'\nimport { useStarterAnswers } from '../hooks/use-starter-answers'\nimport { useStarterPurchase } from '../hooks/use-starter-purchase'\nimport { useStarterSearch } from '../hooks/use-starter-search'\nimport { starterCardFacts, starterPriceLabel, type StarterCardFact } from '../model/starter-query'\n\n/**\n * What a picked starter's price or licence says, held at the foot of the\n * visible cards while the shelf is in view, so \"Buy and continue\" is beside\n * whichever card was clicked rather than below all of them.\n */\nconst PICK_NOTE_SX: SxProps<Theme> = {\n position: 'sticky',\n bottom: (theme) => theme.spacing(2),\n zIndex: 1,\n boxShadow: 3,\n}\n\n/**\n * The payment, where the note was, on its own surface. It may fill the window\n * below the app bar, so a form that has grown keeps its button on screen; a\n * smaller window scrolls inside it.\n */\nconst PAY_PANEL_SX: SxProps<Theme> = {\n position: 'sticky',\n bottom: (theme) => theme.spacing(2),\n zIndex: 1,\n boxShadow: 3,\n p: 2,\n borderRadius: 1,\n bgcolor: 'background.paper',\n maxHeight: (theme) => `calc(100dvh - ${theme.spacing(10)})`,\n overflow: 'auto',\n}\n\n/** The guide to site starters, linked from what the shelf says when it has nothing to show. */\nconst STARTERS_HELP = pluginDocsHelp('siteStarters')\n\n/**\n * A starter's preview as the card draws it: the size the platform's own\n * style previews are rendered at (square), so a row of cards stays level and\n * a publisher's picture is cropped to the same shape from its top.\n */\nconst PREVIEW_SIZE = { width: 800, height: 800 } as const\n\n/** How many placeholder cards stand in for the first answer. */\nconst PLACEHOLDER_CARDS = 3\n\n/** Statuses that mean this workspace may not have starters offered here: the dialog's own choice instead. */\nconst REFUSED = new Set([401, 403, 503])\n\n/** How each fact's chip is colored, from the theme: the price as the marketplace's own cards color it. */\nfunction chipOf(fact: StarterCardFact, row: SiteStarterSummary): OptionCardChip {\n switch (fact.key) {\n case 'price':\n return row.priceUsd > 0\n ? { label: fact.label, color: 'primary', variant: 'filled' }\n : { label: fact.label, color: 'success', variant: 'outlined' }\n case 'official':\n return { label: fact.label, color: 'primary', variant: 'outlined' }\n case 'ai':\n return { label: fact.label, color: 'secondary', variant: 'outlined' }\n default:\n return { label: fact.label }\n }\n}\n\n/** The picture a card draws, resolved the way the listing's own pages resolve it. */\nfunction previewOf(row: SiteStarterSummary): OptionCardImage | null {\n const src = resolveMediaSrc(row.previewImageUrl)\n if (!src) return null\n return { src, alt: `A preview of ${row.displayName || 'this starter'}`, ...PREVIEW_SIZE }\n}\n\n/** One starter as a card in the shared option grid. */\nfunction cardOf(row: SiteStarterSummary): OptionCard {\n const image = previewOf(row)\n return {\n id: row.listingId,\n title: row.displayName || 'Untitled starter',\n ...(row.authorName ? { description: `By ${row.authorName}` } : {}),\n ...(image ? { image } : { icon: mdiWeb.path }),\n chips: starterCardFacts(row).map((fact) => chipOf(fact, row)),\n }\n}\n\nconst narrowed = (filter: Partial<SiteStarterFacets>): boolean =>\n (['kind', 'base', 'fonts'] as const).some((key) => typeof filter[key] === 'string' && filter[key] !== '')\n\n/** A row with what the dialog or a purchase knows about owning it laid over the search's answer. */\nconst ownedOf = (row: SiteStarterSummary, override: boolean | undefined): boolean =>\n override ?? row.owned === true\n\n/** What an answer with no starters says, in the words of what was asked. */\nfunction emptyMessage(props: ConsoleHostStarterPickerZoneProps): string {\n if (props.search.trim()) return 'No starters match — try fewer words'\n if (narrowed(props.filter ?? {})) return 'No starters match these choices — try another'\n return props.mode === 'ai' ? 'No AI starters yet' : 'No starters yet'\n}\n\n/**\n * The marketplace's starters in \"Choose your starter\" (AGL-4049): the\n * `hostStarterPicker` zone, drawn before the site exists.\n *\n * Lists what the dialog asks for — its mode, its search words and its\n * filters — as cards of the shared option grid: a picture, the name, who made\n * it, and its price, kind, page count and the \"AI\" and platform marks as\n * chips. NOTHING is selected when it opens; a person picks, and the pick goes\n * to the dialog through `onPick`. It creates nothing: the dialog creates the\n * site from the starter it was handed.\n *\n * `ready` tells the dialog whether a picker is on screen. The first answer\n * decides: starters, or a search that found none (its empty state is a\n * picker with nothing in it yet), answer `true`; a workspace refused the\n * starters (no permission, the release flag off, no source) or a catalog with\n * nothing in it answers `false`, and the dialog offers its own choice\n * instead. Once shown, a later refusal hands back the same way; a later\n * failure is said here with a way to ask again.\n *\n * A picked starter with a price the workspace has not paid says so under\n * the cards, with \"Buy and continue\": the marketplace's payment, in place, on\n * the card on file, with the site still to be created. One it owns says that\n * instead. The dialog's own `selected` wins over the shelf's pick: it is how\n * a pick restored after paying is shown chosen, and how one the create\n * refused is cleared.\n *\n * An AI starter the workspace may use — free, owned, or just bought — has\n * the answers it carries read once it is picked (AGL-4049 phase 4), and\n * handed to the dialog with it (`onPick(pick, { answers })`), which goes on\n * to the AI start's questions with them. A priced one not yet bought is\n * never read: its answers are what it sells, and the door would ask payment\n * for them. A door that still asks is told back to the dialog as unowned.\n */\nexport function StarterPickerShelf(props: ConsoleHostStarterPickerZoneProps) {\n const { orgId, orgSlug, mode, search, filter, onPick, ready, selected } = props\n // The pick the dialog opened on, restored after paying for it: asked for\n // by every search from then on, so it is listed (first) wherever the\n // search would have put it, and never moves under the person's cursor.\n const [restored] = useState(() => selected?.listingId ?? null)\n const found = useStarterSearch({ orgId, mode, word: search, filter, include: restored })\n const [picked, setPicked] = useState<string | null>(null)\n const [shown, setShown] = useState(false)\n // Starters a purchase attempt found this workspace already owns.\n const [ownedHere, setOwnedHere] = useState<ReadonlySet<string>>(() => new Set())\n // AI starters whose answers the door still asked payment for (AGL-4049).\n const [unpaidHere, setUnpaidHere] = useState<ReadonlySet<string>>(() => new Set())\n const purchase = useStarterPurchase(orgId, orgSlug)\n const starterAnswers = useStarterAnswers(orgId)\n const value = selected !== undefined ? (selected?.listingId ?? null) : picked\n const pickedRow = value ? (found.rows.find((row) => row.listingId === value) ?? null) : null\n const ownedOverride = (row: SiteStarterSummary): boolean | undefined =>\n ownedHere.has(row.listingId)\n ? true\n : unpaidHere.has(row.listingId)\n ? false\n : selected?.listingId === row.listingId\n ? selected.owned\n : undefined\n\n const refused = found.error !== null && REFUSED.has(found.error.status)\n // `null` until the first answer, which is the only one that may decide\n // there is nothing to show; after that only a refusal takes it back.\n const verdict: boolean | null = !found.settled\n ? null\n : refused\n ? false\n : shown\n ? true\n : found.error\n ? false\n : found.rows.length > 0 || search.trim() !== '' || narrowed(filter ?? {})\n\n useEffect(() => {\n if (verdict === true) setShown(true)\n if (verdict === false) setShown(false)\n }, [verdict])\n\n // Each distinct verdict once, whatever identity the dialog's callback has.\n const readyRef = useRef(ready)\n readyRef.current = ready\n const reported = useRef<boolean | null>(null)\n useEffect(() => {\n if (verdict === null || reported.current === verdict) return\n reported.current = verdict\n readyRef.current(verdict)\n }, [verdict])\n\n // A pick the dialog restored is handed back once the shelf has read it,\n // so the dialog holds the starter as the search describes it.\n const onPickRef = useRef(onPick)\n onPickRef.current = onPick\n const handedBack = useRef(false)\n useEffect(() => {\n if (!restored || handedBack.current || !pickedRow || pickedRow.listingId !== restored) return\n handedBack.current = true\n const override = ownedOverride(pickedRow)\n onPickRef.current(override === undefined ? pickedRow : { ...pickedRow, owned: override })\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [restored, pickedRow])\n\n // The picked AI starter's answers (AGL-4049), read once it is one the\n // workspace may use; keyed on the listing and whether it is owned, so a\n // purchase that makes it usable reads it, and nothing reads it twice.\n const pickedOwned = pickedRow ? ownedOf(pickedRow, ownedOverride(pickedRow)) : false\n const answersKey =\n pickedRow && pickedRow.facets.mode === 'ai' && (pickedRow.priceUsd <= 0 || pickedOwned)\n ? `${pickedRow.listingId}:${pickedOwned}`\n : null\n const [reading, setReading] = useState<{ key: string; failed: string | null } | null>(null)\n const [readAttempt, setReadAttempt] = useState(0)\n const askedAnswers = useRef<string | null>(null)\n useEffect(() => {\n if (!answersKey || !pickedRow || askedAnswers.current === answersKey) return\n askedAnswers.current = answersKey\n const row = pickedRow\n const override = ownedOverride(row)\n const handed = override === undefined ? row : { ...row, owned: override }\n setReading({ key: answersKey, failed: null })\n void starterAnswers.read(row.listingId).then((outcome) => {\n if (askedAnswers.current !== answersKey) return\n if (outcome.status === 'failed') return setReading({ key: answersKey, failed: outcome.message })\n setReading(null)\n if (outcome.status === 'ready') return onPickRef.current(handed, { answers: outcome.answers })\n setUnpaidHere((current) => new Set(current).add(row.listingId))\n onPickRef.current({ ...row, owned: false })\n })\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [answersKey, readAttempt])\n const readingPicked = reading !== null && reading.key === answersKey ? reading : null\n const readAgain = () => {\n askedAnswers.current = null\n setReadAttempt((count) => count + 1)\n }\n\n const options = useMemo(() => found.rows.map(cardOf), [found.rows])\n\n const buy = async (row: SiteStarterSummary) => {\n const outcome = await purchase.buy({ listingId: row.listingId, displayName: row.displayName })\n if (outcome !== 'owned') return\n setOwnedHere((current) => new Set(current).add(row.listingId))\n setUnpaidHere((current) => {\n const next = new Set(current)\n next.delete(row.listingId)\n return next\n })\n onPick({ ...row, owned: true })\n }\n\n if (verdict === false) return null\n if (verdict === null) {\n return (\n <Box\n aria-busy\n aria-label=\"Loading starters\"\n sx={{ display: 'grid', gap: 2, gridTemplateColumns: { xs: '1fr', sm: 'repeat(2, minmax(0, 1fr))', md: 'repeat(3, minmax(0, 1fr))' } }}\n >\n {Array.from({ length: PLACEHOLDER_CARDS }, (_, index) => (\n <Skeleton key={index} variant=\"rounded\" sx={{ height: 'auto', aspectRatio: '1 / 1', borderRadius: 2 }} />\n ))}\n </Box>\n )\n }\n\n return (\n <Stack spacing={2} data-starter-shelf={mode}>\n {found.loading ? <LinearProgress aria-label=\"Searching starters\" /> : null}\n {found.error ? (\n <Alert\n severity=\"error\"\n action={\n <Button color=\"inherit\" size=\"small\" onClick={found.refresh}>\n {'Try again'}\n </Button>\n }\n >\n {found.error.message}\n </Alert>\n ) : options.length === 0 ? (\n <Alert\n severity=\"info\"\n action={\n <Button\n color=\"inherit\"\n size=\"small\"\n component={Link}\n href={STARTERS_HELP.href}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n {'How starters work'}\n </Button>\n }\n >\n {emptyMessage(props)}\n </Alert>\n ) : (\n <OptionCardGrid\n label={mode === 'ai' ? 'AI starters' : 'Starters'}\n options={options}\n value={value}\n columns={{ xs: 1, sm: 2, md: 3 }}\n onChange={(listingId) => {\n const row = found.rows.find((candidate) => candidate.listingId === listingId)\n if (!row) return\n setPicked(listingId)\n const override = ownedOverride(row)\n onPick(override === undefined ? row : { ...row, owned: override })\n }}\n />\n )}\n {pickedRow && pickedRow.priceUsd > 0 ? (\n ownedOf(pickedRow, ownedOverride(pickedRow)) ? (\n <Alert severity=\"success\" data-starter-owned={pickedRow.listingId} sx={PICK_NOTE_SX}>\n {'You own this starter. Create your site from it whenever you are ready.'}\n </Alert>\n ) : purchase.panel ? (\n <Box data-starter-pay={pickedRow.listingId} sx={PAY_PANEL_SX}>\n {purchase.panel}\n </Box>\n ) : (\n <Alert\n severity=\"info\"\n data-starter-buy={pickedRow.listingId}\n sx={PICK_NOTE_SX}\n action={\n <BlockedControl\n blocked={Boolean(pickedRow.buyBlockedReason)}\n reason={pickedRow.buyBlockedReason ?? ''}\n >\n <Button\n color=\"inherit\"\n size=\"small\"\n disabled={purchase.busy || !orgId}\n onClick={() => void buy(pickedRow)}\n >\n {purchase.busy ? 'Opening…' : 'Buy and continue'}\n </Button>\n </BlockedControl>\n }\n >\n {`${pickedRow.displayName || 'This starter'} costs ${starterPriceLabel(pickedRow.priceUsd)}. ` +\n (pickedRow.buyBlockedReason ?? 'Buy it here, then create your site from it.')}\n </Alert>\n )\n ) : null}\n {purchase.error ? <Alert severity=\"error\">{purchase.error}</Alert> : null}\n {readingPicked?.failed ? (\n <Alert\n severity=\"error\"\n data-starter-answers-failed={pickedRow?.listingId}\n action={\n <Button color=\"inherit\" size=\"small\" onClick={readAgain}>\n {'Try again'}\n </Button>\n }\n >\n {readingPicked.failed}\n </Alert>\n ) : readingPicked ? (\n <LinearProgress aria-label=\"Reading this starter’s answers\" />\n ) : null}\n </Stack>\n )\n}\n\nexport default StarterPickerShelf\n"],"names":["pluginDocsHelp","resolveMediaSrc","mdiWeb","OptionCardGrid","BlockedControl","Alert","Box","Button","Link","LinearProgress","Skeleton","Stack","useEffect","useMemo","useRef","useState","useStarterAnswers","useStarterPurchase","useStarterSearch","starterCardFacts","starterPriceLabel","PICK_NOTE_SX","position","bottom","theme","spacing","zIndex","boxShadow","PAY_PANEL_SX","p","borderRadius","bgcolor","maxHeight","overflow","STARTERS_HELP","PREVIEW_SIZE","width","height","PLACEHOLDER_CARDS","REFUSED","Set","chipOf","fact","row","key","priceUsd","label","color","variant","previewOf","src","previewImageUrl","alt","displayName","cardOf","image","id","listingId","title","authorName","description","icon","path","chips","map","narrowed","filter","some","ownedOf","override","owned","emptyMessage","props","search","trim","mode","StarterPickerShelf","found","pickedRow","orgId","orgSlug","onPick","ready","selected","restored","word","include","picked","setPicked","shown","setShown","ownedHere","setOwnedHere","unpaidHere","setUnpaidHere","purchase","starterAnswers","value","undefined","rows","find","ownedOverride","has","refused","error","status","verdict","settled","length","readyRef","current","reported","onPickRef","handedBack","pickedOwned","answersKey","facets","reading","setReading","readAttempt","setReadAttempt","askedAnswers","handed","failed","read","then","outcome","message","answers","add","readingPicked","readAgain","count","options","buy","next","delete","aria-busy","aria-label","sx","display","gap","gridTemplateColumns","xs","sm","md","Array","from","_","index","aspectRatio","data-starter-shelf","loading","severity","action","size","onClick","refresh","component","href","target","rel","columns","onChange","candidate","data-starter-owned","panel","data-starter-pay","data-starter-buy","blocked","Boolean","buyBlockedReason","reason","disabled","busy","data-starter-answers-failed"],"mappings":"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"}
|
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
*/
|
|
17
17
|
/**
|
|
18
18
|
* The plugin's DECLARATIONS (AGL-4047): installing into a site and publishing
|
|
19
|
-
* from one,
|
|
19
|
+
* from one, and the workspace's publisher acts (its profile, its published
|
|
20
|
+
* listings), which the one permission catalog lists beside the core keys.
|
|
20
21
|
* Loaded eagerly by the console (client and server) and the tenant's server
|
|
21
22
|
* through the generated declarations manifest, so the role editor offers
|
|
22
23
|
* them, a custom role or a per-member override keeps them, and the site's
|
package/src/lib/declarations.js
CHANGED
|
@@ -14,11 +14,14 @@
|
|
|
14
14
|
* See the License for the specific language governing permissions and
|
|
15
15
|
* limitations under the License.
|
|
16
16
|
*/ import { registerPluginEntitlements } from "@aglyn/aglyn/plugin-manager/plugin-entitlements";
|
|
17
|
+
import { registerPluginPermissions } from "@aglyn/aglyn/plugin-manager/plugin-permissions";
|
|
18
|
+
import { MARKETPLACE_PUBLISHER_PERMISSIONS } from "./model/marketplace-publisher-permissions.js";
|
|
17
19
|
import { MARKETPLACE_SITE_PERMISSIONS } from "./model/marketplace-site-permissions.js";
|
|
18
20
|
let declared = false;
|
|
19
21
|
/**
|
|
20
22
|
* The plugin's DECLARATIONS (AGL-4047): installing into a site and publishing
|
|
21
|
-
* from one,
|
|
23
|
+
* from one, and the workspace's publisher acts (its profile, its published
|
|
24
|
+
* listings), which the one permission catalog lists beside the core keys.
|
|
22
25
|
* Loaded eagerly by the console (client and server) and the tenant's server
|
|
23
26
|
* through the generated declarations manifest, so the role editor offers
|
|
24
27
|
* them, a custom role or a per-member override keeps them, and the site's
|
|
@@ -31,6 +34,7 @@ let declared = false;
|
|
|
31
34
|
pluginId: 'marketplace',
|
|
32
35
|
orgPermissions: MARKETPLACE_SITE_PERMISSIONS
|
|
33
36
|
});
|
|
37
|
+
registerPluginPermissions(MARKETPLACE_PUBLISHER_PERMISSIONS);
|
|
34
38
|
}
|
|
35
39
|
registerMarketplaceDeclarations();
|
|
36
40
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../libs/plugins/marketplace/src/lib/declarations.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 { registerPluginEntitlements } from '@aglyn/aglyn/plugin-manager/plugin-entitlements'\nimport { MARKETPLACE_SITE_PERMISSIONS } from './model/marketplace-site-permissions'\n\nlet declared = false\n\n/**\n * The plugin's DECLARATIONS (AGL-4047): installing into a site and publishing\n * from one, which the one permission catalog lists beside the core keys.\n * Loaded eagerly by the console (client and server) and the tenant's server\n * through the generated declarations manifest, so the role editor offers\n * them, a custom role or a per-member override keeps them, and the site's\n * stamp carries them. Light by construction: nothing here reaches a\n * provider, Firestore or React.\n */\nexport function registerMarketplaceDeclarations(): void {\n if (declared) return\n declared = true\n registerPluginEntitlements({ pluginId: 'marketplace', orgPermissions: MARKETPLACE_SITE_PERMISSIONS })\n}\n\nregisterMarketplaceDeclarations()\n"],"names":["registerPluginEntitlements","MARKETPLACE_SITE_PERMISSIONS","declared","registerMarketplaceDeclarations","pluginId","orgPermissions"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,0BAA0B,QAAQ,kDAAiD;AAC5F,SAASC,4BAA4B,QAAQ,0CAAsC;AAEnF,IAAIC,WAAW;AAEf
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/plugins/marketplace/src/lib/declarations.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 { registerPluginEntitlements } from '@aglyn/aglyn/plugin-manager/plugin-entitlements'\nimport { registerPluginPermissions } from '@aglyn/aglyn/plugin-manager/plugin-permissions'\nimport { MARKETPLACE_PUBLISHER_PERMISSIONS } from './model/marketplace-publisher-permissions'\nimport { MARKETPLACE_SITE_PERMISSIONS } from './model/marketplace-site-permissions'\n\nlet declared = false\n\n/**\n * The plugin's DECLARATIONS (AGL-4047): installing into a site and publishing\n * from one, and the workspace's publisher acts (its profile, its published\n * listings), which the one permission catalog lists beside the core keys.\n * Loaded eagerly by the console (client and server) and the tenant's server\n * through the generated declarations manifest, so the role editor offers\n * them, a custom role or a per-member override keeps them, and the site's\n * stamp carries them. Light by construction: nothing here reaches a\n * provider, Firestore or React.\n */\nexport function registerMarketplaceDeclarations(): void {\n if (declared) return\n declared = true\n registerPluginEntitlements({ pluginId: 'marketplace', orgPermissions: MARKETPLACE_SITE_PERMISSIONS })\n registerPluginPermissions(MARKETPLACE_PUBLISHER_PERMISSIONS)\n}\n\nregisterMarketplaceDeclarations()\n"],"names":["registerPluginEntitlements","registerPluginPermissions","MARKETPLACE_PUBLISHER_PERMISSIONS","MARKETPLACE_SITE_PERMISSIONS","declared","registerMarketplaceDeclarations","pluginId","orgPermissions"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,0BAA0B,QAAQ,kDAAiD;AAC5F,SAASC,yBAAyB,QAAQ,iDAAgD;AAC1F,SAASC,iCAAiC,QAAQ,+CAA2C;AAC7F,SAASC,4BAA4B,QAAQ,0CAAsC;AAEnF,IAAIC,WAAW;AAEf;;;;;;;;;CASC,GACD,OAAO,SAASC;IACd,IAAID,UAAU;IACdA,WAAW;IACXJ,2BAA2B;QAAEM,UAAU;QAAeC,gBAAgBJ;IAA6B;IACnGF,0BAA0BC;AAC5B;AAEAG"}
|
|
@@ -585,7 +585,7 @@ const MANIFEST_FIELD_KIND = {
|
|
|
585
585
|
subtitle: `How ${label} behaves for this workspace`,
|
|
586
586
|
icon: 'gearshape|settings',
|
|
587
587
|
scope: 'org',
|
|
588
|
-
requires: 'org.
|
|
588
|
+
requires: 'org.can[org.settings]',
|
|
589
589
|
constants: {
|
|
590
590
|
defaults: schema.defaults
|
|
591
591
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/model/marketplace-catalog.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 { InstallableArtifactType as MarketplaceArtifactType } from '@aglyn/aglyn/app-utils/artifact-provenance'\n\n/**\n * The marketplace's catalog vocabulary, pure (AGL-3668): what each artifact\n * type is called, where each installs, the categories, a plugin's install\n * state from its two pins, and which route installs a type and what its\n * install says. The native apps generate from it; `marketplace.ts` and the\n * install hook re-export and use it.\n */\n\n/**\n * Human-readable label for each artifact type (AGL-864).\n *\n * Shared by browse cards, the listing detail page, and the seller panel so\n * \"what kind of thing is this\" reads the same everywhere. Resolve a listing's\n * label through {@link listingArtifactLabel}, which tolerates the legacy\n * `type`/`kind` shape the way {@link listingArtifactType} does.\n */\nexport const ARTIFACT_TYPE_LABELS: Record<MarketplaceArtifactType, string> = {\n plugin: 'Plugin',\n component: 'Component',\n template: 'Site template',\n layout: 'Layout',\n datasetSchema: 'Dataset schema',\n emailTemplate: 'Email template',\n emailStarter: 'Email starter',\n theme: 'Theme',\n siteStarter: 'Site starter',\n}\n\n/** Where an installed artifact lives. */\nexport type InstallTarget = 'org' | 'host'\n\n/**\n * Install targets each artifact type actually supports (AGL-656).\n *\n * This is not a policy choice — it is where the install routes physically\n * write. Only plugins have an org-scoped pin\n * (`orgs/{orgId}/installs/{listingId}`, applying to every site, shadowed by\n * a host pin). Components land in `hosts/{h}/components`, templates and\n * layouts in `hosts/{h}/templates`: all host-scoped by nature, because a\n * screen tree belongs to a site.\n *\n * Exported so the UI can ask rather than assume — an install picker that\n * offers \"this whole organization\" for a template would be lying.\n */\nexport const INSTALL_TARGETS: Record<\n MarketplaceArtifactType,\n readonly InstallTarget[]\n> = {\n plugin: ['org', 'host'],\n component: ['host'],\n template: ['host'],\n layout: ['host'],\n // Dataset schemas are org-shared data (AGL-237), so they install at org\n // scope — as a new empty dataset, not a pin (AGL-657).\n datasetSchema: ['org'],\n emailTemplate: ['host'],\n // A campaign email is a screen, and a screen belongs to a site.\n emailStarter: ['host'],\n // A theme is one site's visual identity, written to `hosts/{h}.theme`\n // (AGL-1020). Applying one org-wide would repaint every site at once from a\n // control that says \"install\".\n theme: ['host'],\n // A site starter installs by creating a site from it (AGL-4049), never\n // into an existing one.\n siteStarter: ['host'],\n}\n\n/** A plugin install pin — the version-pinned doc the install API writes. */\nexport interface InstallPin {\n version?: number | string\n}\n\n/**\n * The install state of a plugin listing for one site, told honestly (AGL-656).\n *\n * A plugin can be pinned at two scopes: the org pin\n * (`orgs/{orgId}/installs/{listingId}`) applies to every site, and a host pin\n * (`hosts/{hostId}/installs/{listingId}`) applies to just this one AND shadows\n * the org pin. Detecting installs from `hosts/{h}/components` — the COMPONENT\n * collection — never sees either pin, so an installed plugin used to read as\n * \"not installed\" on both the browse grid and the detail page. This resolves\n * the effective state from the two pins the way the loader does.\n */\nexport interface PluginInstallState {\n /** Effective pin scope for this site — host wins over org — or null. */\n scope: InstallTarget | null\n /** Version pinned at the effective scope, or null when not installed. */\n installedVersion: string | null\n /** Both pins exist: the host pin takes precedence, shadowing the org one. */\n shadowed: boolean\n /** Installed, but the pinned version is behind the listing's latest. */\n updateAvailable: boolean\n}\n\n/**\n * Resolves a plugin listing's install state for a site from its two pins\n * (AGL-656). The host pin shadows the org pin, mirroring the loader, so the\n * effective version and update prompt always describe what actually runs here.\n */\nexport function resolvePluginInstallState(\n latestVersion: number | string | undefined,\n hostPin: InstallPin | null | undefined,\n orgPin: InstallPin | null | undefined,\n): PluginInstallState {\n const effective = hostPin ?? orgPin ?? null\n const installedVersion =\n effective?.version != null ? String(effective.version) : null\n return {\n scope: hostPin ? 'host' : orgPin ? 'org' : null,\n installedVersion,\n shadowed: Boolean(hostPin && orgPin),\n // Any difference is an upgrade prompt, matching the installed-plugins card\n // — pins only ever move forward, so \"different\" means \"behind\".\n updateAvailable:\n installedVersion != null &&\n latestVersion != null &&\n String(latestVersion) !== installedVersion,\n }\n}\n\n/** Fixed category taxonomy for marketplace listings (AGL-430). */\nexport const LISTING_CATEGORIES: readonly string[] = [\n 'analytics',\n 'automation',\n 'commerce',\n 'communication',\n 'content',\n 'design',\n 'forms',\n 'integrations',\n 'marketing',\n 'productivity',\n 'seo',\n 'security',\n] as const\n\n/**\n * Each artifact type has its own installer route (AGL-672): routing\n * everything to the component one silently installed the wrong thing or\n * 404'd. `listingArtifactType` tolerates the legacy `type`/`kind`\n * discriminators (AGL-654).\n */\nexport function marketplaceInstallEndpoint(artifactType: string): string {\n switch (artifactType) {\n case 'template':\n return 'marketplace/install-template'\n case 'layout':\n return 'marketplace/install-layout'\n case 'plugin':\n return 'marketplace/install-plugin'\n case 'datasetSchema':\n return 'marketplace/install-dataset-schema'\n case 'emailTemplate':\n return 'marketplace/install-email-template'\n case 'emailStarter':\n return 'marketplace/install-email-starter'\n case 'theme':\n return 'marketplace/install-theme'\n default:\n return 'marketplace/install'\n }\n}\n\n/**\n * Artifact types whose install deliberately does NOT touch the running site\n * (AGL-669/671/657) — the copy must not imply otherwise. Templates and layouts\n * land in the Templates library; an email template lands as an inactive\n * version the owner still has to activate.\n */\nexport function marketplaceLandingMessage(\n artifactType: string,\n displayName: string,\n): string | null {\n switch (artifactType) {\n case 'template':\n case 'layout':\n return (\n `Saved \"${displayName}\" to your Templates — nothing is live until ` +\n 'you use it.'\n )\n case 'emailTemplate':\n return (\n `Saved \"${displayName}\" as a draft version — activate it in the ` +\n 'email designer to start sending it.'\n )\n case 'emailStarter':\n // A copy, and saying so is the point: the publisher cannot reach it\n // again, and nothing goes out until a campaign is sent from it.\n return (\n `Added \"${displayName}\" to your Email templates as your own copy — ` +\n 'edit it freely, nothing is sent until you send a campaign.'\n )\n case 'datasetSchema':\n return `Created \"${displayName}\" as a new, empty dataset.`\n case 'theme':\n // The one type that DOES change the running site on install, so it gets\n // the opposite of the reassurance the others get — plus the way back,\n // because a repainted site is alarming if you cannot see how to undo it.\n return (\n `Applied \"${displayName}\" to this site. Setup → Theme has a way back ` +\n 'to your previous theme.'\n )\n default:\n return null\n }\n}\n\n/**\n * ─── WHAT A PLUGIN'S PAGE SAYS ABOUT ITS INSTALLS (AGL-1016, AGL-997, AGL-1027, AGL-3671) ───\n *\n * The version comparison, the update status, the org-wide picture of one listing's pins, which sites an\n * uninstall touches and the sentences the workspace's plugin pages and their dialogs say about them. All of\n * it was spread across `update-state.ts`, `marketplace.ts` and the plugin-site-set components; it lives here,\n * pure, so the native apps replay the very same answers (`libs/native/contracts/function-cases.generated.json`).\n * Those modules re-export what moved.\n */\n\n/**\n * Orders two artifact versions, or returns null when they cannot be ordered.\n *\n * Two numbering schemes are in play and both are real: copied artifacts get a\n * monotonic integer from the publish route, plugins get the semver string off\n * their manifest. A single string compare handles neither (\"10\" < \"9\",\n * \"1.10.0\" < \"1.9.0\"), so both are parsed as dotted numbers — which covers the\n * integer case as a one-segment version.\n *\n * Anything non-numeric (a prerelease tag, a date-stamped build) is not ordered\n * by guesswork: unequal-and-incomparable is reported as such and surfaces as\n * `unknown`, which is honest, where a string compare would confidently point\n * an update button at an older build.\n */\nexport function compareArtifactVersions(\n a: string | number | null | undefined,\n b: string | number | null | undefined,\n): -1 | 0 | 1 | null {\n if (a == null || b == null || a === '' || b === '') return null\n const left = String(a)\n const right = String(b)\n if (left === right) return 0\n const parse = (value: string): number[] | null => {\n const parts = value.split('.')\n const numbers: number[] = []\n for (const part of parts) {\n if (!/^\\d+$/.test(part)) return null\n numbers.push(Number(part))\n }\n return numbers.length ? numbers : null\n }\n const leftParts = parse(left)\n const rightParts = parse(right)\n if (!leftParts || !rightParts) return null\n const length = Math.max(leftParts.length, rightParts.length)\n for (let index = 0; index < length; index += 1) {\n const leftPart = leftParts[index] ?? 0\n const rightPart = rightParts[index] ?? 0\n if (leftPart < rightPart) return -1\n if (leftPart > rightPart) return 1\n }\n // Equal numerically but not as strings — \"1.0\" and \"1.0.0\" are the same\n // version written two ways.\n return 0\n}\n\nexport type UpdateState =\n /** Running the newest version the publisher offers. */\n | 'current'\n /** A newer version exists and can be taken. */\n | 'update-available'\n /**\n * Installed version is NEWER than what is offered. Not a bug: a publisher\n * installs their own unapproved version to test it, and a version can be\n * withdrawn after someone installed it.\n */\n | 'ahead'\n /** Not enough is known to say — no provenance, or nothing published to compare. */\n | 'unknown'\n\nexport interface UpdateStatus {\n state: UpdateState\n /** The version running, or null when provenance cannot say. */\n installedVersion: string | null\n /** The newest version on offer — for plugins, the newest APPROVED one. */\n availableVersion: string | null\n /**\n * Why the answer is `unknown`, for a UI that should say which kind of\n * silence this is rather than implying the artifact is current.\n */\n unknownReason?: 'no-provenance' | 'nothing-published' | 'incomparable'\n}\n\n/**\n * One sentence describing an update state, so four surfaces do not each invent\n * their own wording for the same fact.\n */\nexport function updateStateLabel(status: UpdateStatus): string {\n switch (status.state) {\n case 'update-available':\n return `You have v${status.installedVersion} · v${status.availableVersion} available`\n case 'current':\n return `Up to date · v${status.installedVersion}`\n case 'ahead':\n return `You have v${status.installedVersion}, newer than the published v${status.availableVersion}`\n default:\n return status.unknownReason === 'nothing-published'\n ? 'No published version to compare against'\n : status.unknownReason === 'incomparable'\n ? `You have v${status.installedVersion}; v${status.availableVersion} cannot be ordered against it`\n : 'Installed before update tracking — version unknown'\n }\n}\n\n/** The pin fields an update comparison reads, in every shape the install routes have written. */\nexport interface UpdateComparablePin {\n version?: unknown\n installedFrom?: { listingId?: string; version?: unknown } | null\n marketplace?: { listingId?: string; version?: unknown } | null\n source?: { listingId?: string; version?: unknown } | null\n listingId?: string\n}\n\n/**\n * What a listing OFFERS right now (the plugin branch of `offeredPluginVersion`): the newest approved version, unless a\n * revocation (`versions` naming it, or `'all'`) has stopped it, in which case nothing is offered.\n */\nexport function pluginOfferedVersion(\n latestApprovedVersion: unknown,\n revocationVersions: readonly string[] | 'all' | null | undefined,\n): string | null {\n const offered = String(latestApprovedVersion ?? '')\n if (!offered) return null\n if (revocationVersions === 'all') return null\n return Array.isArray(revocationVersions) && revocationVersions.includes(offered) ? null : offered\n}\n\n/**\n * Whether the installed plugin is still what the publisher ships (the plugin branch of `resolveUpdateState`): the\n * pin's version against the version on offer. A pin that names no listing is the one shape that records no version\n * (`no-provenance`).\n */\nexport function pluginUpdateStatus(\n pin: UpdateComparablePin | null | undefined,\n offeredVersion: string | null | undefined,\n): UpdateStatus {\n const unknown: UpdateStatus = { state: 'unknown', installedVersion: null, availableVersion: null, unknownReason: 'no-provenance' }\n if (!pin) return unknown\n const recorded = pin.installedFrom?.listingId\n ? pin.installedFrom.version\n : pin.marketplace?.listingId\n ? pin.marketplace.version\n : pin.source?.listingId\n ? pin.source.version\n : pin.listingId\n ? pin.version\n : undefined\n const installedVersion = recorded == null || recorded === '' ? null : String(recorded)\n if (!installedVersion) return unknown\n if (offeredVersion == null || offeredVersion === '') {\n return { state: 'unknown', installedVersion, availableVersion: null, unknownReason: 'nothing-published' }\n }\n const availableVersion = String(offeredVersion)\n const order = compareArtifactVersions(installedVersion, availableVersion)\n if (order === null) return { state: 'unknown', installedVersion, availableVersion, unknownReason: 'incomparable' }\n return { state: order === 0 ? 'current' : order < 0 ? 'update-available' : 'ahead', installedVersion, availableVersion }\n}\n\n\n/** One site's slice of an org-scope install picture (AGL-997). */\nexport interface OrgInstallSite {\n hostId: string\n label: string\n /** The version that actually runs here, from the effective pin. */\n version: string | null\n /** Where that pin lives — a host pin shadows the org one, as the loader does. */\n pinnedBy: InstallTarget\n /** This site has its OWN pin on top of an org pin. */\n shadowed: boolean\n}\n\n/**\n * The whole org's install picture for one listing (AGL-997).\n *\n * At org scope, \"installed\" is a SET, not a boolean. The detail page used to\n * resolve state against the single acting host, so a plugin installed on one\n * of five sites reported \"Installed on this site\" — describing one arbitrary\n * site and staying silent about the other four — and offered an Uninstall\n * that was all-or-nothing.\n *\n * `orgWide` and per-site pins are not exclusive: an org pin covers every\n * site including ones created later, and a host pin on top of it shadows it\n * for that one site. Both facts have to survive into the UI, which is why\n * this returns the sites AND the pins rather than a single scope.\n */\nexport interface OrgInstallSummary {\n /** An org pin exists: every site is covered, including future ones. */\n orgWide: boolean\n /** Version of the org pin, when there is one. */\n orgVersion: string | null\n /** Every site the listing effectively runs on. */\n sites: OrgInstallSite[]\n /** Sites carrying their own removable host pin. */\n hostPinnedIds: string[]\n /** Sites with no pin of their own and no org pin covering them. */\n availableHostIds: string[]\n installedAnywhere: boolean\n}\n\nexport function resolveOrgInstallSummary(\n hosts: ReadonlyArray<{ id: string; label: string }>,\n hostPins: Readonly<Record<string, InstallPin | null | undefined>>,\n orgPin: InstallPin | null | undefined,\n): OrgInstallSummary {\n const orgWide = Boolean(orgPin)\n const orgVersion = orgPin?.version != null ? String(orgPin.version) : null\n const sites: OrgInstallSite[] = []\n const hostPinnedIds: string[] = []\n const availableHostIds: string[] = []\n for (const host of hosts) {\n const hostPin = hostPins[host.id]\n if (hostPin) hostPinnedIds.push(host.id)\n if (!hostPin && !orgPin) {\n availableHostIds.push(host.id)\n continue\n }\n sites.push({\n hostId: host.id,\n label: host.label,\n version:\n hostPin?.version != null ? String(hostPin.version) : orgVersion,\n pinnedBy: hostPin ? 'host' : 'org',\n shadowed: Boolean(hostPin && orgPin),\n })\n }\n return {\n orgWide,\n orgVersion,\n sites,\n hostPinnedIds,\n availableHostIds,\n installedAnywhere: orgWide || hostPinnedIds.length > 0,\n }\n}\n\n/** One site an uninstall would touch, and whether it really loses the plugin. */\nexport interface UninstallTarget {\n hostId: string\n label: string\n /**\n * The plugin keeps running here. Removing a HOST pin while an org pin still\n * covers the site, or the ORG pin while the site holds its own host pin,\n * changes which pointer is in use and nothing a visitor can see.\n */\n stillCovered: boolean\n}\n\n/**\n * Which sites an uninstall actually affects (AGL-1027).\n *\n * The shadowing case is the whole reason this is a function rather than a\n * filter at the call site. A pin is not a boolean per site: an org pin covers\n * everything, a host pin names one site, and where both exist the host pin\n * wins. So \"remove\" means three different things depending on which pointer is\n * being dropped and what is left underneath — and the one thing the dialog must\n * never do is tell someone a plugin will stop working when it will not.\n *\n * Returns every site the operation touches, including the ones that keep the\n * plugin, so the dialog can say so explicitly instead of omitting them and\n * leaving the count unexplained.\n */\nexport function resolveUninstallTargets(\n orgInstall: OrgInstallSummary,\n scope: 'org' | 'host',\n hostId?: string,\n): UninstallTarget[] {\n if (scope === 'host') {\n const site = orgInstall.sites.find((entry) => entry.hostId === hostId)\n if (!site) return []\n // Its own pin goes; the org pin, if there is one, still covers it.\n return [\n { hostId: site.hostId, label: site.label, stillCovered: orgInstall.orgWide },\n ]\n }\n // Dropping the org pin: every site loses it EXCEPT those holding a host pin\n // of their own, which were already shadowing the org pin anyway.\n return orgInstall.sites.map((site) => ({\n hostId: site.hostId,\n label: site.label,\n stillCovered: site.pinnedBy === 'host',\n }))\n}\n\n// ─── The sentences of a plugin's install pages and their dialogs (AGL-1017, AGL-1027, AGL-3671) ───\n\n/** The sites of an org-wide picture whose running version is behind the one on offer. */\nexport function staleSites(sites: readonly OrgInstallSite[], latestVersion: string | number | null | undefined): OrgInstallSite[] {\n return sites.filter((site) => (compareArtifactVersions(site.version, latestVersion) ?? 0) < 0)\n}\n\n/** What \"Where it runs\" opens with: the whole organization, or how many of its sites. */\nexport function pluginSiteSetSummary(install: OrgInstallSummary, siteCount: number): string {\n return install.orgWide\n ? `Installed for the whole organization (v${install.orgVersion}) — every site, including ones you add later.`\n : `Installed on ${install.sites.length} of ${siteCount} sites.`\n}\n\n/** What the update confirmation says: the plugin, the version it moves to and where, and what stays put. */\nexport function pluginUpdatePrompt(input: {\n name: string\n latestVersion: string\n label: string\n /** The update is for one site: other sites stay on the version they run. */\n singleSite: boolean\n}): { title: string; body: string; others: string | null; note: string } {\n return {\n title: `Update to v${input.latestVersion}?`,\n body: `${input.name} moves from the version running now to v${input.latestVersion} on ${input.label}.`,\n others: input.singleSite ? 'Other sites stay on the version they are running.' : null,\n note: 'Pins only move forward — there is no going back to the previous version from here.',\n }\n}\n\n/** What the update button of a pin that is org-wide, or of the stale per-site ones, is called and aims at. */\nexport function pluginUpdateAll(install: OrgInstallSummary, stale: readonly OrgInstallSite[], latestVersion: string) {\n return {\n label: install.orgWide ? `Update to v${latestVersion}` : `Update all ${stale.length} to v${latestVersion}`,\n target: install.orgWide ? 'every site in this organization' : `${stale.length} site${stale.length === 1 ? '' : 's'}`,\n hostIds: install.orgWide ? null : stale.map((site) => site.hostId),\n }\n}\n\n/** The notice above an org-wide update: one pin moves every site at once. */\nexport const PLUGIN_ORG_WIDE_UPDATE_NOTE =\n 'This is one organization-wide pin, so updating moves every site at once. To take a version on one site first, install per site instead.'\n\n/** One site an uninstall dialog was told about by the impact check. */\nexport interface UninstallImpactSite {\n hostId: string\n label: string\n stillCovered: boolean\n placements: Array<{ type: 'screen' | 'layout' | 'component'; id: string; name: string; count: number }>\n affectedScreens: number\n truncated: boolean\n}\n\n/** What `/api/hosts/plugin-impact` answers. */\nexport interface UninstallImpact {\n sites: UninstallImpactSite[]\n losingSites: number\n placements: number\n affectedScreens: number\n truncated: boolean\n dataSurvives: boolean\n}\n\n/** The word typed to confirm an uninstall that breaks pages, or whose impact could not be checked. */\nexport const UNINSTALL_ACKNOWLEDGEMENT = 'UNINSTALL'\n\n/** What the uninstall dialog shows, worked out from the targets and the impact check's answer. */\nexport interface UninstallImpactView {\n title: string\n confirmLabel: string\n /** The check is still running. */\n scanning: boolean\n /** Alerts above the sites, in order. */\n alerts: Array<{ tone: 'warning' | 'error' | 'info'; text: string }>\n /** Sites that lose the plugin and place it on pages: the site, how many pages, and the chips. */\n sites: Array<{ label: string; pages: string; chips: string[] }>\n /** The closing note, when sites lose the plugin. */\n note: string | null\n /** Pages stop rendering it, or the check could not say: the person types {@link UNINSTALL_ACKNOWLEDGEMENT}. */\n needsAcknowledgement: boolean\n}\n\nconst PLACEMENT_NOUNS = { screen: 'page', layout: 'layout', component: 'component' } as const\n\n/** The uninstall dialog (`UninstallImpactDialog`) as data, so every client says the same things. */\nexport function uninstallImpactView(input: {\n name: string\n scope: 'org' | 'host'\n /** The listing's plugin id; without one the placements cannot be found. */\n pluginId: string\n targets: readonly UninstallTarget[]\n /** The impact check's answer, once it has come. */\n impact: UninstallImpact | null\n /** The impact check failed. */\n failed: boolean\n}): UninstallImpactView {\n const { name, scope, pluginId, targets, impact, failed } = input\n const losing = targets.filter((target) => !target.stillCovered)\n const covered = targets.filter((target) => target.stillCovered)\n const scanning = Boolean(pluginId) && losing.length > 0 && !impact && !failed\n const affected = impact?.affectedScreens ?? 0\n const unknown = failed || (!pluginId && losing.length > 0)\n const alerts: UninstallImpactView['alerts'] = []\n if (unknown && !scanning) {\n alerts.push({\n tone: 'warning',\n text: pluginId\n ? 'The impact check could not run, so this cannot say what would stop working. Nothing here means \"nothing breaks\".'\n : 'This listing records no plugin id, so its placements cannot be found. Pages using it may stop rendering it.',\n })\n }\n if (!scanning && !unknown && affected > 0) {\n const sites = impact?.losingSites ?? losing.length\n alerts.push({\n tone: 'error',\n text:\n `Removes ${name} from ${sites} site${sites === 1 ? '' : 's'}; ` +\n `${impact?.truncated ? 'at least ' : ''}${affected} published page${affected === 1 ? '' : 's'} ` +\n `place${affected === 1 ? 's' : ''} it and will stop rendering it.`,\n })\n }\n if (!scanning && !unknown && affected === 0 && losing.length) {\n alerts.push({ tone: 'info', text: 'No published page places this plugin, so nothing visitors can see changes.' })\n }\n if (covered.length) {\n alerts.push({\n tone: 'info',\n text:\n covered.length === targets.length\n ? `The plugin keeps running on ${covered.length === 1 ? 'this site' : `all ${covered.length} of these sites`}, ` +\n `because another install still covers ${covered.length === 1 ? 'it' : 'them'}. Only which version is in use changes.`\n : `${covered.length} of these sites keep the plugin — ${covered.map((target) => target.label).join(', ')} — ` +\n 'because another install still covers them.',\n })\n }\n const sites = (impact?.sites ?? [])\n .filter((site) => !site.stillCovered && site.placements.length)\n .map((site) => ({\n label: site.label,\n pages: `${site.affectedScreens} published page${site.affectedScreens === 1 ? '' : 's'} affected`,\n chips: [\n ...site.placements\n .slice(0, 12)\n .map((placement) => `${PLACEMENT_NOUNS[placement.type]}: ${placement.name}${placement.count > 1 ? ` ×${placement.count}` : ''}`),\n ...(site.placements.length > 12 ? [`+${site.placements.length - 12} more`] : []),\n ],\n }))\n return {\n title: scope === 'org' ? `Uninstall ${name} across the organization?` : `Remove ${name} from this site?`,\n confirmLabel: scope === 'org' ? 'Uninstall org-wide' : 'Remove',\n scanning,\n alerts,\n sites,\n note:\n !scanning && losing.length\n ? 'Uninstalling removes the install only. The plugin’s settings and any data it stored are kept, and installing it again restores the pages that place it.'\n : null,\n needsAcknowledgement: !scanning && (affected > 0 || unknown),\n }\n}\n\n/** What a run of installs (an install, an update, or the several steps of one) says once it is done. */\nexport function installPlanOutcome(input: {\n name: string\n /** Steps that went through, and the refusals of those that did not. */\n installed: number\n errors: readonly string[]\n /** One of the steps was the org-wide pin. */\n orgWide: boolean\n /** The steps moved existing pins to {@link version}. */\n updating: boolean\n version?: string | null\n /** The artifact lands in a library and is not live (a template, a layout): \"Saved\", not \"Installed\". */\n saved?: boolean\n}): { tone: 'success' | 'warning' | 'error'; text: string } {\n const { name, installed, errors, orgWide, updating, version, saved } = input\n const noun = updating ? 'Updated' : saved ? 'Saved' : 'Installed'\n const toVersion = updating && version != null ? ` to v${version}` : ''\n if (installed && !errors.length) {\n return {\n tone: 'success',\n text: orgWide\n ? `${noun} \"${name}\"${toVersion} for the whole organization`\n : `${noun} \"${name}\"${toVersion} on ${installed} site${installed === 1 ? '' : 's'}`,\n }\n }\n if (installed && errors.length) {\n return { tone: 'warning', text: `${noun} on ${installed} site(s); ${errors.length} failed — ${errors[0]}` }\n }\n return { tone: 'error', text: errors[0] ?? 'Install failed' }\n}\n\n/** The row of the plugins list for one install: the version and where it runs. */\nexport function installedPluginCaption(input: { version?: unknown; orgWide: boolean; siteLabels: readonly string[] }): string {\n return (\n `v${input.version} · ` +\n (input.orgWide ? 'every site in this organization' : input.siteLabels.length === 1 ? input.siteLabels[0] : `${input.siteLabels.length} sites`)\n )\n}\n\n// ─── A marketplace plugin's settings, from its manifest (AGL-428, AGL-3671) ───\n\nconst MANIFEST_CONFIG_FIELD_LIMIT = 50\nconst MANIFEST_CONFIG_FIELD_TYPES: ReadonlySet<string> = new Set(['string', 'number', 'boolean', 'select'])\n\ninterface ManifestConfigField {\n key: string\n label: string\n type: 'string' | 'number' | 'boolean' | 'select'\n description?: string\n options?: Array<{ value: string; label: string }>\n min?: number\n max?: number\n}\n\n/** A stored or declared value as its field takes it (`coerce` of `plugin-config.ts`), else [fallback]. */\nfunction coerceManifestConfigValue(field: ManifestConfigField, raw: unknown, fallback: unknown): unknown {\n switch (field.type) {\n case 'boolean':\n return typeof raw === 'boolean' ? raw : fallback\n case 'number': {\n const value = typeof raw === 'number' && Number.isFinite(raw) ? raw : NaN\n if (Number.isNaN(value)) return fallback\n if (field.min != null && value < field.min) return field.min\n if (field.max != null && value > field.max) return field.max\n return value\n }\n case 'select': {\n const value = typeof raw === 'string' ? raw : undefined\n return field.options?.some((option) => option.value === value) ? value : fallback\n }\n default:\n return typeof raw === 'string' ? raw : fallback\n }\n}\n\n/**\n * The fields a plugin's MANIFEST declares for its settings and the values they start from (`pluginConfigSchemaFromManifest`\n * of `plugin-config.ts`, which this repeats without its imports so the native apps replay it). Everything is re-validated\n * rather than trusted: a junk type drops the field, a duplicate key drops the second, a select with nothing to select is\n * dropped, and the count is capped. `null` when the manifest declares nothing usable, which means \"this plugin has no\n * settings\".\n */\nexport function manifestSettingsFields(manifest: unknown): { fields: ManifestConfigField[]; defaults: Record<string, unknown> } | null {\n const declared = (manifest as { config?: unknown } | null | undefined)?.config\n const rawFields = (declared as { fields?: unknown } | undefined)?.fields\n if (!Array.isArray(rawFields)) return null\n const seen = new Set<string>()\n const fields: ManifestConfigField[] = []\n for (const raw of rawFields) {\n if (fields.length >= MANIFEST_CONFIG_FIELD_LIMIT) break\n const entry = raw as Record<string, unknown> | null\n const key = typeof entry?.['key'] === 'string' ? entry['key'].trim() : ''\n if (!key || seen.has(key)) continue\n const type = String(entry?.['type'] ?? 'string')\n if (!MANIFEST_CONFIG_FIELD_TYPES.has(type)) continue\n const options = Array.isArray(entry?.['options'])\n ? (entry['options'] as unknown[])\n .map((option) => option as Record<string, unknown> | null)\n .filter((option) => typeof option?.['value'] === 'string' && typeof option?.['label'] === 'string')\n .map((option) => ({ value: String(option?.['value']), label: String(option?.['label']) }))\n : undefined\n if (type === 'select' && !options?.length) continue\n seen.add(key)\n fields.push({\n key,\n label: typeof entry?.['label'] === 'string' && entry['label'].trim() ? String(entry['label']).slice(0, 120) : key,\n type: type as ManifestConfigField['type'],\n description: typeof entry?.['description'] === 'string' ? String(entry['description']).slice(0, 400) : undefined,\n options,\n min: typeof entry?.['min'] === 'number' ? entry['min'] : undefined,\n max: typeof entry?.['max'] === 'number' ? entry['max'] : undefined,\n })\n }\n if (!fields.length) return null\n const rawDefaults = (declared as { defaults?: unknown } | undefined)?.defaults\n const defaults: Record<string, unknown> = {}\n for (const field of fields) {\n const supplied = rawDefaults && typeof rawDefaults === 'object' ? (rawDefaults as Record<string, unknown>)[field.key] : undefined\n const empty =\n field.type === 'boolean' ? false : field.type === 'number' ? (field.min ?? 0) : field.type === 'select' ? (field.options?.[0]?.value ?? '') : ''\n defaults[field.key] = coerceManifestConfigValue(field, supplied, empty)\n }\n return { fields, defaults }\n}\n\nconst MANIFEST_FIELD_KIND = { string: 'text', number: 'number', boolean: 'toggle', select: 'select' } as const\n\n/**\n * The workspace-settings screen of a marketplace plugin, as a native screen spec: the console's `PluginConfigCards` for a\n * pin's manifest. The same shape the generated screens of the built-in plugins have (`pluginSettingsScreen` of\n * `native-plugin-settings.mjs`), under an id of its own, saved to `orgs/{org}/pluginSettings/{plugin}` as the console\n * saves it. `null` for a plugin that declares no settings.\n */\nexport function manifestSettingsScreen(pluginId: string, label: string, manifest: unknown): Record<string, unknown> | null {\n const schema = pluginId ? manifestSettingsFields(manifest) : null\n if (!schema) return null\n const fields = schema.fields.map((field) => {\n const out: Record<string, unknown> = {\n key: field.key,\n label: field.label,\n kind: MANIFEST_FIELD_KIND[field.type],\n // `??` keeps a stored false or 0, which `|` would skip for the default.\n initial: `{item.${field.key} ?? const.defaults.${field.key}}`,\n }\n if (field.type === 'select') out['options'] = (field.options ?? []).map(({ value, label: text }) => ({ value, label: text }))\n const band = field.type === 'number' && (field.min != null || field.max != null) ? `Between ${field.min ?? 'any'} and ${field.max ?? 'any'}.` : ''\n const help = [field.description, band].filter(Boolean).join(' ')\n if (help) out['help'] = help\n return out\n })\n const written = Object.fromEntries(schema.fields.map((field) => [field.key, `{form.${field.key}}`]))\n return {\n id: `marketplace.pluginSettings.${pluginId}`,\n title: `${label} settings`,\n subtitle: `How ${label} behaves for this workspace`,\n icon: 'gearshape|settings',\n scope: 'org',\n requires: 'org.manager',\n constants: { defaults: schema.defaults },\n load: { settings: { doc: `orgs/{org.id}/pluginSettings/${pluginId}`, optional: true } },\n blocks: [\n {\n type: 'form',\n id: 'settings',\n object: 'data.settings',\n footer: 'A site can override these on its own plugin page.',\n fields,\n submit: {\n label: 'Save settings',\n write: { doc: `orgs/{org.id}/pluginSettings/${pluginId}`, fields: { ...written, updatedBy: '{user.uid}' } },\n success: 'Settings saved',\n },\n },\n ],\n }\n}\n"],"names":["ARTIFACT_TYPE_LABELS","plugin","component","template","layout","datasetSchema","emailTemplate","emailStarter","theme","siteStarter","INSTALL_TARGETS","resolvePluginInstallState","latestVersion","hostPin","orgPin","effective","installedVersion","version","String","scope","shadowed","Boolean","updateAvailable","LISTING_CATEGORIES","marketplaceInstallEndpoint","artifactType","marketplaceLandingMessage","displayName","compareArtifactVersions","a","b","left","right","parse","value","parts","split","numbers","part","test","push","Number","length","leftParts","rightParts","Math","max","index","leftPart","rightPart","updateStateLabel","status","state","availableVersion","unknownReason","pluginOfferedVersion","latestApprovedVersion","revocationVersions","offered","Array","isArray","includes","pluginUpdateStatus","pin","offeredVersion","unknown","recorded","installedFrom","listingId","marketplace","source","undefined","order","resolveOrgInstallSummary","hosts","hostPins","orgWide","orgVersion","sites","hostPinnedIds","availableHostIds","host","id","hostId","label","pinnedBy","installedAnywhere","resolveUninstallTargets","orgInstall","site","find","entry","stillCovered","map","staleSites","filter","pluginSiteSetSummary","install","siteCount","pluginUpdatePrompt","input","title","body","name","others","singleSite","note","pluginUpdateAll","stale","target","hostIds","PLUGIN_ORG_WIDE_UPDATE_NOTE","UNINSTALL_ACKNOWLEDGEMENT","PLACEMENT_NOUNS","screen","uninstallImpactView","pluginId","targets","impact","failed","losing","covered","scanning","affected","affectedScreens","alerts","tone","text","losingSites","truncated","join","placements","pages","chips","slice","placement","type","count","confirmLabel","needsAcknowledgement","installPlanOutcome","errors","installed","updating","saved","noun","toVersion","installedPluginCaption","siteLabels","MANIFEST_CONFIG_FIELD_LIMIT","MANIFEST_CONFIG_FIELD_TYPES","Set","coerceManifestConfigValue","field","raw","fallback","isFinite","NaN","isNaN","min","options","some","option","manifestSettingsFields","manifest","declared","config","rawFields","fields","seen","key","trim","has","add","description","rawDefaults","defaults","supplied","empty","MANIFEST_FIELD_KIND","string","number","boolean","select","manifestSettingsScreen","schema","out","kind","initial","band","help","written","Object","fromEntries","subtitle","icon","requires","constants","load","settings","doc","optional","blocks","object","footer","submit","write","updatedBy","success"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/model/marketplace-catalog.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 { InstallableArtifactType as MarketplaceArtifactType } from '@aglyn/aglyn/app-utils/artifact-provenance'\n\n/**\n * The marketplace's catalog vocabulary, pure (AGL-3668): what each artifact\n * type is called, where each installs, the categories, a plugin's install\n * state from its two pins, and which route installs a type and what its\n * install says. The native apps generate from it; `marketplace.ts` and the\n * install hook re-export and use it.\n */\n\n/**\n * Human-readable label for each artifact type (AGL-864).\n *\n * Shared by browse cards, the listing detail page, and the seller panel so\n * \"what kind of thing is this\" reads the same everywhere. Resolve a listing's\n * label through {@link listingArtifactLabel}, which tolerates the legacy\n * `type`/`kind` shape the way {@link listingArtifactType} does.\n */\nexport const ARTIFACT_TYPE_LABELS: Record<MarketplaceArtifactType, string> = {\n plugin: 'Plugin',\n component: 'Component',\n template: 'Site template',\n layout: 'Layout',\n datasetSchema: 'Dataset schema',\n emailTemplate: 'Email template',\n emailStarter: 'Email starter',\n theme: 'Theme',\n siteStarter: 'Site starter',\n}\n\n/** Where an installed artifact lives. */\nexport type InstallTarget = 'org' | 'host'\n\n/**\n * Install targets each artifact type actually supports (AGL-656).\n *\n * This is not a policy choice — it is where the install routes physically\n * write. Only plugins have an org-scoped pin\n * (`orgs/{orgId}/installs/{listingId}`, applying to every site, shadowed by\n * a host pin). Components land in `hosts/{h}/components`, templates and\n * layouts in `hosts/{h}/templates`: all host-scoped by nature, because a\n * screen tree belongs to a site.\n *\n * Exported so the UI can ask rather than assume — an install picker that\n * offers \"this whole organization\" for a template would be lying.\n */\nexport const INSTALL_TARGETS: Record<\n MarketplaceArtifactType,\n readonly InstallTarget[]\n> = {\n plugin: ['org', 'host'],\n component: ['host'],\n template: ['host'],\n layout: ['host'],\n // Dataset schemas are org-shared data (AGL-237), so they install at org\n // scope — as a new empty dataset, not a pin (AGL-657).\n datasetSchema: ['org'],\n emailTemplate: ['host'],\n // A campaign email is a screen, and a screen belongs to a site.\n emailStarter: ['host'],\n // A theme is one site's visual identity, written to `hosts/{h}.theme`\n // (AGL-1020). Applying one org-wide would repaint every site at once from a\n // control that says \"install\".\n theme: ['host'],\n // A site starter installs by creating a site from it (AGL-4049), never\n // into an existing one.\n siteStarter: ['host'],\n}\n\n/** A plugin install pin — the version-pinned doc the install API writes. */\nexport interface InstallPin {\n version?: number | string\n}\n\n/**\n * The install state of a plugin listing for one site, told honestly (AGL-656).\n *\n * A plugin can be pinned at two scopes: the org pin\n * (`orgs/{orgId}/installs/{listingId}`) applies to every site, and a host pin\n * (`hosts/{hostId}/installs/{listingId}`) applies to just this one AND shadows\n * the org pin. Detecting installs from `hosts/{h}/components` — the COMPONENT\n * collection — never sees either pin, so an installed plugin used to read as\n * \"not installed\" on both the browse grid and the detail page. This resolves\n * the effective state from the two pins the way the loader does.\n */\nexport interface PluginInstallState {\n /** Effective pin scope for this site — host wins over org — or null. */\n scope: InstallTarget | null\n /** Version pinned at the effective scope, or null when not installed. */\n installedVersion: string | null\n /** Both pins exist: the host pin takes precedence, shadowing the org one. */\n shadowed: boolean\n /** Installed, but the pinned version is behind the listing's latest. */\n updateAvailable: boolean\n}\n\n/**\n * Resolves a plugin listing's install state for a site from its two pins\n * (AGL-656). The host pin shadows the org pin, mirroring the loader, so the\n * effective version and update prompt always describe what actually runs here.\n */\nexport function resolvePluginInstallState(\n latestVersion: number | string | undefined,\n hostPin: InstallPin | null | undefined,\n orgPin: InstallPin | null | undefined,\n): PluginInstallState {\n const effective = hostPin ?? orgPin ?? null\n const installedVersion =\n effective?.version != null ? String(effective.version) : null\n return {\n scope: hostPin ? 'host' : orgPin ? 'org' : null,\n installedVersion,\n shadowed: Boolean(hostPin && orgPin),\n // Any difference is an upgrade prompt, matching the installed-plugins card\n // — pins only ever move forward, so \"different\" means \"behind\".\n updateAvailable:\n installedVersion != null &&\n latestVersion != null &&\n String(latestVersion) !== installedVersion,\n }\n}\n\n/** Fixed category taxonomy for marketplace listings (AGL-430). */\nexport const LISTING_CATEGORIES: readonly string[] = [\n 'analytics',\n 'automation',\n 'commerce',\n 'communication',\n 'content',\n 'design',\n 'forms',\n 'integrations',\n 'marketing',\n 'productivity',\n 'seo',\n 'security',\n] as const\n\n/**\n * Each artifact type has its own installer route (AGL-672): routing\n * everything to the component one silently installed the wrong thing or\n * 404'd. `listingArtifactType` tolerates the legacy `type`/`kind`\n * discriminators (AGL-654).\n */\nexport function marketplaceInstallEndpoint(artifactType: string): string {\n switch (artifactType) {\n case 'template':\n return 'marketplace/install-template'\n case 'layout':\n return 'marketplace/install-layout'\n case 'plugin':\n return 'marketplace/install-plugin'\n case 'datasetSchema':\n return 'marketplace/install-dataset-schema'\n case 'emailTemplate':\n return 'marketplace/install-email-template'\n case 'emailStarter':\n return 'marketplace/install-email-starter'\n case 'theme':\n return 'marketplace/install-theme'\n default:\n return 'marketplace/install'\n }\n}\n\n/**\n * Artifact types whose install deliberately does NOT touch the running site\n * (AGL-669/671/657) — the copy must not imply otherwise. Templates and layouts\n * land in the Templates library; an email template lands as an inactive\n * version the owner still has to activate.\n */\nexport function marketplaceLandingMessage(\n artifactType: string,\n displayName: string,\n): string | null {\n switch (artifactType) {\n case 'template':\n case 'layout':\n return (\n `Saved \"${displayName}\" to your Templates — nothing is live until ` +\n 'you use it.'\n )\n case 'emailTemplate':\n return (\n `Saved \"${displayName}\" as a draft version — activate it in the ` +\n 'email designer to start sending it.'\n )\n case 'emailStarter':\n // A copy, and saying so is the point: the publisher cannot reach it\n // again, and nothing goes out until a campaign is sent from it.\n return (\n `Added \"${displayName}\" to your Email templates as your own copy — ` +\n 'edit it freely, nothing is sent until you send a campaign.'\n )\n case 'datasetSchema':\n return `Created \"${displayName}\" as a new, empty dataset.`\n case 'theme':\n // The one type that DOES change the running site on install, so it gets\n // the opposite of the reassurance the others get — plus the way back,\n // because a repainted site is alarming if you cannot see how to undo it.\n return (\n `Applied \"${displayName}\" to this site. Setup → Theme has a way back ` +\n 'to your previous theme.'\n )\n default:\n return null\n }\n}\n\n/**\n * ─── WHAT A PLUGIN'S PAGE SAYS ABOUT ITS INSTALLS (AGL-1016, AGL-997, AGL-1027, AGL-3671) ───\n *\n * The version comparison, the update status, the org-wide picture of one listing's pins, which sites an\n * uninstall touches and the sentences the workspace's plugin pages and their dialogs say about them. All of\n * it was spread across `update-state.ts`, `marketplace.ts` and the plugin-site-set components; it lives here,\n * pure, so the native apps replay the very same answers (`libs/native/contracts/function-cases.generated.json`).\n * Those modules re-export what moved.\n */\n\n/**\n * Orders two artifact versions, or returns null when they cannot be ordered.\n *\n * Two numbering schemes are in play and both are real: copied artifacts get a\n * monotonic integer from the publish route, plugins get the semver string off\n * their manifest. A single string compare handles neither (\"10\" < \"9\",\n * \"1.10.0\" < \"1.9.0\"), so both are parsed as dotted numbers — which covers the\n * integer case as a one-segment version.\n *\n * Anything non-numeric (a prerelease tag, a date-stamped build) is not ordered\n * by guesswork: unequal-and-incomparable is reported as such and surfaces as\n * `unknown`, which is honest, where a string compare would confidently point\n * an update button at an older build.\n */\nexport function compareArtifactVersions(\n a: string | number | null | undefined,\n b: string | number | null | undefined,\n): -1 | 0 | 1 | null {\n if (a == null || b == null || a === '' || b === '') return null\n const left = String(a)\n const right = String(b)\n if (left === right) return 0\n const parse = (value: string): number[] | null => {\n const parts = value.split('.')\n const numbers: number[] = []\n for (const part of parts) {\n if (!/^\\d+$/.test(part)) return null\n numbers.push(Number(part))\n }\n return numbers.length ? numbers : null\n }\n const leftParts = parse(left)\n const rightParts = parse(right)\n if (!leftParts || !rightParts) return null\n const length = Math.max(leftParts.length, rightParts.length)\n for (let index = 0; index < length; index += 1) {\n const leftPart = leftParts[index] ?? 0\n const rightPart = rightParts[index] ?? 0\n if (leftPart < rightPart) return -1\n if (leftPart > rightPart) return 1\n }\n // Equal numerically but not as strings — \"1.0\" and \"1.0.0\" are the same\n // version written two ways.\n return 0\n}\n\nexport type UpdateState =\n /** Running the newest version the publisher offers. */\n | 'current'\n /** A newer version exists and can be taken. */\n | 'update-available'\n /**\n * Installed version is NEWER than what is offered. Not a bug: a publisher\n * installs their own unapproved version to test it, and a version can be\n * withdrawn after someone installed it.\n */\n | 'ahead'\n /** Not enough is known to say — no provenance, or nothing published to compare. */\n | 'unknown'\n\nexport interface UpdateStatus {\n state: UpdateState\n /** The version running, or null when provenance cannot say. */\n installedVersion: string | null\n /** The newest version on offer — for plugins, the newest APPROVED one. */\n availableVersion: string | null\n /**\n * Why the answer is `unknown`, for a UI that should say which kind of\n * silence this is rather than implying the artifact is current.\n */\n unknownReason?: 'no-provenance' | 'nothing-published' | 'incomparable'\n}\n\n/**\n * One sentence describing an update state, so four surfaces do not each invent\n * their own wording for the same fact.\n */\nexport function updateStateLabel(status: UpdateStatus): string {\n switch (status.state) {\n case 'update-available':\n return `You have v${status.installedVersion} · v${status.availableVersion} available`\n case 'current':\n return `Up to date · v${status.installedVersion}`\n case 'ahead':\n return `You have v${status.installedVersion}, newer than the published v${status.availableVersion}`\n default:\n return status.unknownReason === 'nothing-published'\n ? 'No published version to compare against'\n : status.unknownReason === 'incomparable'\n ? `You have v${status.installedVersion}; v${status.availableVersion} cannot be ordered against it`\n : 'Installed before update tracking — version unknown'\n }\n}\n\n/** The pin fields an update comparison reads, in every shape the install routes have written. */\nexport interface UpdateComparablePin {\n version?: unknown\n installedFrom?: { listingId?: string; version?: unknown } | null\n marketplace?: { listingId?: string; version?: unknown } | null\n source?: { listingId?: string; version?: unknown } | null\n listingId?: string\n}\n\n/**\n * What a listing OFFERS right now (the plugin branch of `offeredPluginVersion`): the newest approved version, unless a\n * revocation (`versions` naming it, or `'all'`) has stopped it, in which case nothing is offered.\n */\nexport function pluginOfferedVersion(\n latestApprovedVersion: unknown,\n revocationVersions: readonly string[] | 'all' | null | undefined,\n): string | null {\n const offered = String(latestApprovedVersion ?? '')\n if (!offered) return null\n if (revocationVersions === 'all') return null\n return Array.isArray(revocationVersions) && revocationVersions.includes(offered) ? null : offered\n}\n\n/**\n * Whether the installed plugin is still what the publisher ships (the plugin branch of `resolveUpdateState`): the\n * pin's version against the version on offer. A pin that names no listing is the one shape that records no version\n * (`no-provenance`).\n */\nexport function pluginUpdateStatus(\n pin: UpdateComparablePin | null | undefined,\n offeredVersion: string | null | undefined,\n): UpdateStatus {\n const unknown: UpdateStatus = { state: 'unknown', installedVersion: null, availableVersion: null, unknownReason: 'no-provenance' }\n if (!pin) return unknown\n const recorded = pin.installedFrom?.listingId\n ? pin.installedFrom.version\n : pin.marketplace?.listingId\n ? pin.marketplace.version\n : pin.source?.listingId\n ? pin.source.version\n : pin.listingId\n ? pin.version\n : undefined\n const installedVersion = recorded == null || recorded === '' ? null : String(recorded)\n if (!installedVersion) return unknown\n if (offeredVersion == null || offeredVersion === '') {\n return { state: 'unknown', installedVersion, availableVersion: null, unknownReason: 'nothing-published' }\n }\n const availableVersion = String(offeredVersion)\n const order = compareArtifactVersions(installedVersion, availableVersion)\n if (order === null) return { state: 'unknown', installedVersion, availableVersion, unknownReason: 'incomparable' }\n return { state: order === 0 ? 'current' : order < 0 ? 'update-available' : 'ahead', installedVersion, availableVersion }\n}\n\n\n/** One site's slice of an org-scope install picture (AGL-997). */\nexport interface OrgInstallSite {\n hostId: string\n label: string\n /** The version that actually runs here, from the effective pin. */\n version: string | null\n /** Where that pin lives — a host pin shadows the org one, as the loader does. */\n pinnedBy: InstallTarget\n /** This site has its OWN pin on top of an org pin. */\n shadowed: boolean\n}\n\n/**\n * The whole org's install picture for one listing (AGL-997).\n *\n * At org scope, \"installed\" is a SET, not a boolean. The detail page used to\n * resolve state against the single acting host, so a plugin installed on one\n * of five sites reported \"Installed on this site\" — describing one arbitrary\n * site and staying silent about the other four — and offered an Uninstall\n * that was all-or-nothing.\n *\n * `orgWide` and per-site pins are not exclusive: an org pin covers every\n * site including ones created later, and a host pin on top of it shadows it\n * for that one site. Both facts have to survive into the UI, which is why\n * this returns the sites AND the pins rather than a single scope.\n */\nexport interface OrgInstallSummary {\n /** An org pin exists: every site is covered, including future ones. */\n orgWide: boolean\n /** Version of the org pin, when there is one. */\n orgVersion: string | null\n /** Every site the listing effectively runs on. */\n sites: OrgInstallSite[]\n /** Sites carrying their own removable host pin. */\n hostPinnedIds: string[]\n /** Sites with no pin of their own and no org pin covering them. */\n availableHostIds: string[]\n installedAnywhere: boolean\n}\n\nexport function resolveOrgInstallSummary(\n hosts: ReadonlyArray<{ id: string; label: string }>,\n hostPins: Readonly<Record<string, InstallPin | null | undefined>>,\n orgPin: InstallPin | null | undefined,\n): OrgInstallSummary {\n const orgWide = Boolean(orgPin)\n const orgVersion = orgPin?.version != null ? String(orgPin.version) : null\n const sites: OrgInstallSite[] = []\n const hostPinnedIds: string[] = []\n const availableHostIds: string[] = []\n for (const host of hosts) {\n const hostPin = hostPins[host.id]\n if (hostPin) hostPinnedIds.push(host.id)\n if (!hostPin && !orgPin) {\n availableHostIds.push(host.id)\n continue\n }\n sites.push({\n hostId: host.id,\n label: host.label,\n version:\n hostPin?.version != null ? String(hostPin.version) : orgVersion,\n pinnedBy: hostPin ? 'host' : 'org',\n shadowed: Boolean(hostPin && orgPin),\n })\n }\n return {\n orgWide,\n orgVersion,\n sites,\n hostPinnedIds,\n availableHostIds,\n installedAnywhere: orgWide || hostPinnedIds.length > 0,\n }\n}\n\n/** One site an uninstall would touch, and whether it really loses the plugin. */\nexport interface UninstallTarget {\n hostId: string\n label: string\n /**\n * The plugin keeps running here. Removing a HOST pin while an org pin still\n * covers the site, or the ORG pin while the site holds its own host pin,\n * changes which pointer is in use and nothing a visitor can see.\n */\n stillCovered: boolean\n}\n\n/**\n * Which sites an uninstall actually affects (AGL-1027).\n *\n * The shadowing case is the whole reason this is a function rather than a\n * filter at the call site. A pin is not a boolean per site: an org pin covers\n * everything, a host pin names one site, and where both exist the host pin\n * wins. So \"remove\" means three different things depending on which pointer is\n * being dropped and what is left underneath — and the one thing the dialog must\n * never do is tell someone a plugin will stop working when it will not.\n *\n * Returns every site the operation touches, including the ones that keep the\n * plugin, so the dialog can say so explicitly instead of omitting them and\n * leaving the count unexplained.\n */\nexport function resolveUninstallTargets(\n orgInstall: OrgInstallSummary,\n scope: 'org' | 'host',\n hostId?: string,\n): UninstallTarget[] {\n if (scope === 'host') {\n const site = orgInstall.sites.find((entry) => entry.hostId === hostId)\n if (!site) return []\n // Its own pin goes; the org pin, if there is one, still covers it.\n return [\n { hostId: site.hostId, label: site.label, stillCovered: orgInstall.orgWide },\n ]\n }\n // Dropping the org pin: every site loses it EXCEPT those holding a host pin\n // of their own, which were already shadowing the org pin anyway.\n return orgInstall.sites.map((site) => ({\n hostId: site.hostId,\n label: site.label,\n stillCovered: site.pinnedBy === 'host',\n }))\n}\n\n// ─── The sentences of a plugin's install pages and their dialogs (AGL-1017, AGL-1027, AGL-3671) ───\n\n/** The sites of an org-wide picture whose running version is behind the one on offer. */\nexport function staleSites(sites: readonly OrgInstallSite[], latestVersion: string | number | null | undefined): OrgInstallSite[] {\n return sites.filter((site) => (compareArtifactVersions(site.version, latestVersion) ?? 0) < 0)\n}\n\n/** What \"Where it runs\" opens with: the whole organization, or how many of its sites. */\nexport function pluginSiteSetSummary(install: OrgInstallSummary, siteCount: number): string {\n return install.orgWide\n ? `Installed for the whole organization (v${install.orgVersion}) — every site, including ones you add later.`\n : `Installed on ${install.sites.length} of ${siteCount} sites.`\n}\n\n/** What the update confirmation says: the plugin, the version it moves to and where, and what stays put. */\nexport function pluginUpdatePrompt(input: {\n name: string\n latestVersion: string\n label: string\n /** The update is for one site: other sites stay on the version they run. */\n singleSite: boolean\n}): { title: string; body: string; others: string | null; note: string } {\n return {\n title: `Update to v${input.latestVersion}?`,\n body: `${input.name} moves from the version running now to v${input.latestVersion} on ${input.label}.`,\n others: input.singleSite ? 'Other sites stay on the version they are running.' : null,\n note: 'Pins only move forward — there is no going back to the previous version from here.',\n }\n}\n\n/** What the update button of a pin that is org-wide, or of the stale per-site ones, is called and aims at. */\nexport function pluginUpdateAll(install: OrgInstallSummary, stale: readonly OrgInstallSite[], latestVersion: string) {\n return {\n label: install.orgWide ? `Update to v${latestVersion}` : `Update all ${stale.length} to v${latestVersion}`,\n target: install.orgWide ? 'every site in this organization' : `${stale.length} site${stale.length === 1 ? '' : 's'}`,\n hostIds: install.orgWide ? null : stale.map((site) => site.hostId),\n }\n}\n\n/** The notice above an org-wide update: one pin moves every site at once. */\nexport const PLUGIN_ORG_WIDE_UPDATE_NOTE =\n 'This is one organization-wide pin, so updating moves every site at once. To take a version on one site first, install per site instead.'\n\n/** One site an uninstall dialog was told about by the impact check. */\nexport interface UninstallImpactSite {\n hostId: string\n label: string\n stillCovered: boolean\n placements: Array<{ type: 'screen' | 'layout' | 'component'; id: string; name: string; count: number }>\n affectedScreens: number\n truncated: boolean\n}\n\n/** What `/api/hosts/plugin-impact` answers. */\nexport interface UninstallImpact {\n sites: UninstallImpactSite[]\n losingSites: number\n placements: number\n affectedScreens: number\n truncated: boolean\n dataSurvives: boolean\n}\n\n/** The word typed to confirm an uninstall that breaks pages, or whose impact could not be checked. */\nexport const UNINSTALL_ACKNOWLEDGEMENT = 'UNINSTALL'\n\n/** What the uninstall dialog shows, worked out from the targets and the impact check's answer. */\nexport interface UninstallImpactView {\n title: string\n confirmLabel: string\n /** The check is still running. */\n scanning: boolean\n /** Alerts above the sites, in order. */\n alerts: Array<{ tone: 'warning' | 'error' | 'info'; text: string }>\n /** Sites that lose the plugin and place it on pages: the site, how many pages, and the chips. */\n sites: Array<{ label: string; pages: string; chips: string[] }>\n /** The closing note, when sites lose the plugin. */\n note: string | null\n /** Pages stop rendering it, or the check could not say: the person types {@link UNINSTALL_ACKNOWLEDGEMENT}. */\n needsAcknowledgement: boolean\n}\n\nconst PLACEMENT_NOUNS = { screen: 'page', layout: 'layout', component: 'component' } as const\n\n/** The uninstall dialog (`UninstallImpactDialog`) as data, so every client says the same things. */\nexport function uninstallImpactView(input: {\n name: string\n scope: 'org' | 'host'\n /** The listing's plugin id; without one the placements cannot be found. */\n pluginId: string\n targets: readonly UninstallTarget[]\n /** The impact check's answer, once it has come. */\n impact: UninstallImpact | null\n /** The impact check failed. */\n failed: boolean\n}): UninstallImpactView {\n const { name, scope, pluginId, targets, impact, failed } = input\n const losing = targets.filter((target) => !target.stillCovered)\n const covered = targets.filter((target) => target.stillCovered)\n const scanning = Boolean(pluginId) && losing.length > 0 && !impact && !failed\n const affected = impact?.affectedScreens ?? 0\n const unknown = failed || (!pluginId && losing.length > 0)\n const alerts: UninstallImpactView['alerts'] = []\n if (unknown && !scanning) {\n alerts.push({\n tone: 'warning',\n text: pluginId\n ? 'The impact check could not run, so this cannot say what would stop working. Nothing here means \"nothing breaks\".'\n : 'This listing records no plugin id, so its placements cannot be found. Pages using it may stop rendering it.',\n })\n }\n if (!scanning && !unknown && affected > 0) {\n const sites = impact?.losingSites ?? losing.length\n alerts.push({\n tone: 'error',\n text:\n `Removes ${name} from ${sites} site${sites === 1 ? '' : 's'}; ` +\n `${impact?.truncated ? 'at least ' : ''}${affected} published page${affected === 1 ? '' : 's'} ` +\n `place${affected === 1 ? 's' : ''} it and will stop rendering it.`,\n })\n }\n if (!scanning && !unknown && affected === 0 && losing.length) {\n alerts.push({ tone: 'info', text: 'No published page places this plugin, so nothing visitors can see changes.' })\n }\n if (covered.length) {\n alerts.push({\n tone: 'info',\n text:\n covered.length === targets.length\n ? `The plugin keeps running on ${covered.length === 1 ? 'this site' : `all ${covered.length} of these sites`}, ` +\n `because another install still covers ${covered.length === 1 ? 'it' : 'them'}. Only which version is in use changes.`\n : `${covered.length} of these sites keep the plugin — ${covered.map((target) => target.label).join(', ')} — ` +\n 'because another install still covers them.',\n })\n }\n const sites = (impact?.sites ?? [])\n .filter((site) => !site.stillCovered && site.placements.length)\n .map((site) => ({\n label: site.label,\n pages: `${site.affectedScreens} published page${site.affectedScreens === 1 ? '' : 's'} affected`,\n chips: [\n ...site.placements\n .slice(0, 12)\n .map((placement) => `${PLACEMENT_NOUNS[placement.type]}: ${placement.name}${placement.count > 1 ? ` ×${placement.count}` : ''}`),\n ...(site.placements.length > 12 ? [`+${site.placements.length - 12} more`] : []),\n ],\n }))\n return {\n title: scope === 'org' ? `Uninstall ${name} across the organization?` : `Remove ${name} from this site?`,\n confirmLabel: scope === 'org' ? 'Uninstall org-wide' : 'Remove',\n scanning,\n alerts,\n sites,\n note:\n !scanning && losing.length\n ? 'Uninstalling removes the install only. The plugin’s settings and any data it stored are kept, and installing it again restores the pages that place it.'\n : null,\n needsAcknowledgement: !scanning && (affected > 0 || unknown),\n }\n}\n\n/** What a run of installs (an install, an update, or the several steps of one) says once it is done. */\nexport function installPlanOutcome(input: {\n name: string\n /** Steps that went through, and the refusals of those that did not. */\n installed: number\n errors: readonly string[]\n /** One of the steps was the org-wide pin. */\n orgWide: boolean\n /** The steps moved existing pins to {@link version}. */\n updating: boolean\n version?: string | null\n /** The artifact lands in a library and is not live (a template, a layout): \"Saved\", not \"Installed\". */\n saved?: boolean\n}): { tone: 'success' | 'warning' | 'error'; text: string } {\n const { name, installed, errors, orgWide, updating, version, saved } = input\n const noun = updating ? 'Updated' : saved ? 'Saved' : 'Installed'\n const toVersion = updating && version != null ? ` to v${version}` : ''\n if (installed && !errors.length) {\n return {\n tone: 'success',\n text: orgWide\n ? `${noun} \"${name}\"${toVersion} for the whole organization`\n : `${noun} \"${name}\"${toVersion} on ${installed} site${installed === 1 ? '' : 's'}`,\n }\n }\n if (installed && errors.length) {\n return { tone: 'warning', text: `${noun} on ${installed} site(s); ${errors.length} failed — ${errors[0]}` }\n }\n return { tone: 'error', text: errors[0] ?? 'Install failed' }\n}\n\n/** The row of the plugins list for one install: the version and where it runs. */\nexport function installedPluginCaption(input: { version?: unknown; orgWide: boolean; siteLabels: readonly string[] }): string {\n return (\n `v${input.version} · ` +\n (input.orgWide ? 'every site in this organization' : input.siteLabels.length === 1 ? input.siteLabels[0] : `${input.siteLabels.length} sites`)\n )\n}\n\n// ─── A marketplace plugin's settings, from its manifest (AGL-428, AGL-3671) ───\n\nconst MANIFEST_CONFIG_FIELD_LIMIT = 50\nconst MANIFEST_CONFIG_FIELD_TYPES: ReadonlySet<string> = new Set(['string', 'number', 'boolean', 'select'])\n\ninterface ManifestConfigField {\n key: string\n label: string\n type: 'string' | 'number' | 'boolean' | 'select'\n description?: string\n options?: Array<{ value: string; label: string }>\n min?: number\n max?: number\n}\n\n/** A stored or declared value as its field takes it (`coerce` of `plugin-config.ts`), else [fallback]. */\nfunction coerceManifestConfigValue(field: ManifestConfigField, raw: unknown, fallback: unknown): unknown {\n switch (field.type) {\n case 'boolean':\n return typeof raw === 'boolean' ? raw : fallback\n case 'number': {\n const value = typeof raw === 'number' && Number.isFinite(raw) ? raw : NaN\n if (Number.isNaN(value)) return fallback\n if (field.min != null && value < field.min) return field.min\n if (field.max != null && value > field.max) return field.max\n return value\n }\n case 'select': {\n const value = typeof raw === 'string' ? raw : undefined\n return field.options?.some((option) => option.value === value) ? value : fallback\n }\n default:\n return typeof raw === 'string' ? raw : fallback\n }\n}\n\n/**\n * The fields a plugin's MANIFEST declares for its settings and the values they start from (`pluginConfigSchemaFromManifest`\n * of `plugin-config.ts`, which this repeats without its imports so the native apps replay it). Everything is re-validated\n * rather than trusted: a junk type drops the field, a duplicate key drops the second, a select with nothing to select is\n * dropped, and the count is capped. `null` when the manifest declares nothing usable, which means \"this plugin has no\n * settings\".\n */\nexport function manifestSettingsFields(manifest: unknown): { fields: ManifestConfigField[]; defaults: Record<string, unknown> } | null {\n const declared = (manifest as { config?: unknown } | null | undefined)?.config\n const rawFields = (declared as { fields?: unknown } | undefined)?.fields\n if (!Array.isArray(rawFields)) return null\n const seen = new Set<string>()\n const fields: ManifestConfigField[] = []\n for (const raw of rawFields) {\n if (fields.length >= MANIFEST_CONFIG_FIELD_LIMIT) break\n const entry = raw as Record<string, unknown> | null\n const key = typeof entry?.['key'] === 'string' ? entry['key'].trim() : ''\n if (!key || seen.has(key)) continue\n const type = String(entry?.['type'] ?? 'string')\n if (!MANIFEST_CONFIG_FIELD_TYPES.has(type)) continue\n const options = Array.isArray(entry?.['options'])\n ? (entry['options'] as unknown[])\n .map((option) => option as Record<string, unknown> | null)\n .filter((option) => typeof option?.['value'] === 'string' && typeof option?.['label'] === 'string')\n .map((option) => ({ value: String(option?.['value']), label: String(option?.['label']) }))\n : undefined\n if (type === 'select' && !options?.length) continue\n seen.add(key)\n fields.push({\n key,\n label: typeof entry?.['label'] === 'string' && entry['label'].trim() ? String(entry['label']).slice(0, 120) : key,\n type: type as ManifestConfigField['type'],\n description: typeof entry?.['description'] === 'string' ? String(entry['description']).slice(0, 400) : undefined,\n options,\n min: typeof entry?.['min'] === 'number' ? entry['min'] : undefined,\n max: typeof entry?.['max'] === 'number' ? entry['max'] : undefined,\n })\n }\n if (!fields.length) return null\n const rawDefaults = (declared as { defaults?: unknown } | undefined)?.defaults\n const defaults: Record<string, unknown> = {}\n for (const field of fields) {\n const supplied = rawDefaults && typeof rawDefaults === 'object' ? (rawDefaults as Record<string, unknown>)[field.key] : undefined\n const empty =\n field.type === 'boolean' ? false : field.type === 'number' ? (field.min ?? 0) : field.type === 'select' ? (field.options?.[0]?.value ?? '') : ''\n defaults[field.key] = coerceManifestConfigValue(field, supplied, empty)\n }\n return { fields, defaults }\n}\n\nconst MANIFEST_FIELD_KIND = { string: 'text', number: 'number', boolean: 'toggle', select: 'select' } as const\n\n/**\n * The workspace-settings screen of a marketplace plugin, as a native screen spec: the console's `PluginConfigCards` for a\n * pin's manifest. The same shape the generated screens of the built-in plugins have (`pluginSettingsScreen` of\n * `native-plugin-settings.mjs`), under an id of its own, saved to `orgs/{org}/pluginSettings/{plugin}` as the console\n * saves it. `null` for a plugin that declares no settings.\n */\nexport function manifestSettingsScreen(pluginId: string, label: string, manifest: unknown): Record<string, unknown> | null {\n const schema = pluginId ? manifestSettingsFields(manifest) : null\n if (!schema) return null\n const fields = schema.fields.map((field) => {\n const out: Record<string, unknown> = {\n key: field.key,\n label: field.label,\n kind: MANIFEST_FIELD_KIND[field.type],\n // `??` keeps a stored false or 0, which `|` would skip for the default.\n initial: `{item.${field.key} ?? const.defaults.${field.key}}`,\n }\n if (field.type === 'select') out['options'] = (field.options ?? []).map(({ value, label: text }) => ({ value, label: text }))\n const band = field.type === 'number' && (field.min != null || field.max != null) ? `Between ${field.min ?? 'any'} and ${field.max ?? 'any'}.` : ''\n const help = [field.description, band].filter(Boolean).join(' ')\n if (help) out['help'] = help\n return out\n })\n const written = Object.fromEntries(schema.fields.map((field) => [field.key, `{form.${field.key}}`]))\n return {\n id: `marketplace.pluginSettings.${pluginId}`,\n title: `${label} settings`,\n subtitle: `How ${label} behaves for this workspace`,\n icon: 'gearshape|settings',\n scope: 'org',\n requires: 'org.can[org.settings]',\n constants: { defaults: schema.defaults },\n load: { settings: { doc: `orgs/{org.id}/pluginSettings/${pluginId}`, optional: true } },\n blocks: [\n {\n type: 'form',\n id: 'settings',\n object: 'data.settings',\n footer: 'A site can override these on its own plugin page.',\n fields,\n submit: {\n label: 'Save settings',\n write: { doc: `orgs/{org.id}/pluginSettings/${pluginId}`, fields: { ...written, updatedBy: '{user.uid}' } },\n success: 'Settings saved',\n },\n },\n ],\n }\n}\n"],"names":["ARTIFACT_TYPE_LABELS","plugin","component","template","layout","datasetSchema","emailTemplate","emailStarter","theme","siteStarter","INSTALL_TARGETS","resolvePluginInstallState","latestVersion","hostPin","orgPin","effective","installedVersion","version","String","scope","shadowed","Boolean","updateAvailable","LISTING_CATEGORIES","marketplaceInstallEndpoint","artifactType","marketplaceLandingMessage","displayName","compareArtifactVersions","a","b","left","right","parse","value","parts","split","numbers","part","test","push","Number","length","leftParts","rightParts","Math","max","index","leftPart","rightPart","updateStateLabel","status","state","availableVersion","unknownReason","pluginOfferedVersion","latestApprovedVersion","revocationVersions","offered","Array","isArray","includes","pluginUpdateStatus","pin","offeredVersion","unknown","recorded","installedFrom","listingId","marketplace","source","undefined","order","resolveOrgInstallSummary","hosts","hostPins","orgWide","orgVersion","sites","hostPinnedIds","availableHostIds","host","id","hostId","label","pinnedBy","installedAnywhere","resolveUninstallTargets","orgInstall","site","find","entry","stillCovered","map","staleSites","filter","pluginSiteSetSummary","install","siteCount","pluginUpdatePrompt","input","title","body","name","others","singleSite","note","pluginUpdateAll","stale","target","hostIds","PLUGIN_ORG_WIDE_UPDATE_NOTE","UNINSTALL_ACKNOWLEDGEMENT","PLACEMENT_NOUNS","screen","uninstallImpactView","pluginId","targets","impact","failed","losing","covered","scanning","affected","affectedScreens","alerts","tone","text","losingSites","truncated","join","placements","pages","chips","slice","placement","type","count","confirmLabel","needsAcknowledgement","installPlanOutcome","errors","installed","updating","saved","noun","toVersion","installedPluginCaption","siteLabels","MANIFEST_CONFIG_FIELD_LIMIT","MANIFEST_CONFIG_FIELD_TYPES","Set","coerceManifestConfigValue","field","raw","fallback","isFinite","NaN","isNaN","min","options","some","option","manifestSettingsFields","manifest","declared","config","rawFields","fields","seen","key","trim","has","add","description","rawDefaults","defaults","supplied","empty","MANIFEST_FIELD_KIND","string","number","boolean","select","manifestSettingsScreen","schema","out","kind","initial","band","help","written","Object","fromEntries","subtitle","icon","requires","constants","load","settings","doc","optional","blocks","object","footer","submit","write","updatedBy","success"],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
import type { PluginPermission } from '@aglyn/aglyn/plugin-manager/plugin-permissions';
|
|
18
|
+
/** The marketplace's publisher keys. */
|
|
19
|
+
export type MarketplacePublisherPermission = 'marketplace.publisherProfile' | 'marketplace.manageListings' | 'marketplace.payouts';
|
|
20
|
+
export declare const MARKETPLACE_PUBLISHER_PROFILE_PERMISSION: MarketplacePublisherPermission;
|
|
21
|
+
export declare const MARKETPLACE_MANAGE_LISTINGS_PERMISSION: MarketplacePublisherPermission;
|
|
22
|
+
export declare const MARKETPLACE_PAYOUTS_PERMISSION: MarketplacePublisherPermission;
|
|
23
|
+
/** The keys as the plugin declares them; owners and admins hold each by default. */
|
|
24
|
+
export declare const MARKETPLACE_PUBLISHER_PERMISSIONS: readonly PluginPermission[];
|
|
25
|
+
/** The refusal a door answers without one: names the permission, never a role. */
|
|
26
|
+
export declare function marketplacePublisherPermissionRefusal(key: MarketplacePublisherPermission): string;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/ export const MARKETPLACE_PUBLISHER_PROFILE_PERMISSION = 'marketplace.publisherProfile';
|
|
17
|
+
export const MARKETPLACE_MANAGE_LISTINGS_PERMISSION = 'marketplace.manageListings';
|
|
18
|
+
export const MARKETPLACE_PAYOUTS_PERMISSION = 'marketplace.payouts';
|
|
19
|
+
/** The keys as the plugin declares them; owners and admins hold each by default. */ export const MARKETPLACE_PUBLISHER_PERMISSIONS = [
|
|
20
|
+
{
|
|
21
|
+
key: 'marketplace.publisherProfile',
|
|
22
|
+
pluginId: 'marketplace',
|
|
23
|
+
label: 'Edit the publisher profile',
|
|
24
|
+
description: 'Change the workspace’s public marketplace profile (its handle, name, bio, logo, support contact and links), and ask for the Verified publisher badge or withdraw the ask.',
|
|
25
|
+
defaults: {
|
|
26
|
+
admin: true,
|
|
27
|
+
editor: false,
|
|
28
|
+
viewer: false
|
|
29
|
+
},
|
|
30
|
+
groupLabel: 'Marketplace'
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
key: 'marketplace.manageListings',
|
|
34
|
+
pluginId: 'marketplace',
|
|
35
|
+
label: 'Manage published listings',
|
|
36
|
+
description: 'Edit the listings the workspace has published (their words, pictures and category), make a plugin public or private, unpublish or republish a listing, and see each version’s review status.',
|
|
37
|
+
defaults: {
|
|
38
|
+
admin: true,
|
|
39
|
+
editor: false,
|
|
40
|
+
viewer: false
|
|
41
|
+
},
|
|
42
|
+
groupLabel: 'Marketplace'
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
key: 'marketplace.payouts',
|
|
46
|
+
pluginId: 'marketplace',
|
|
47
|
+
label: 'Manage marketplace payouts',
|
|
48
|
+
description: 'Connect the payout account the workspace’s paid listings pay into, finish its setup, and open its payouts.',
|
|
49
|
+
defaults: {
|
|
50
|
+
admin: true,
|
|
51
|
+
editor: false,
|
|
52
|
+
viewer: false
|
|
53
|
+
},
|
|
54
|
+
groupLabel: 'Marketplace'
|
|
55
|
+
}
|
|
56
|
+
];
|
|
57
|
+
/** The refusal a door answers without one: names the permission, never a role. */ export function marketplacePublisherPermissionRefusal(key) {
|
|
58
|
+
var _ref;
|
|
59
|
+
var _MARKETPLACE_PUBLISHER_PERMISSIONS_find;
|
|
60
|
+
const label = (_ref = (_MARKETPLACE_PUBLISHER_PERMISSIONS_find = MARKETPLACE_PUBLISHER_PERMISSIONS.find((entry)=>entry.key === key)) == null ? void 0 : _MARKETPLACE_PUBLISHER_PERMISSIONS_find.label) != null ? _ref : key;
|
|
61
|
+
return `Your role does not include "${label}" — ask an organization admin`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
//# sourceMappingURL=marketplace-publisher-permissions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/model/marketplace-publisher-permissions.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 { PluginPermission } from '@aglyn/aglyn/plugin-manager/plugin-permissions'\n\n/*\n * THE WORKSPACE AS A MARKETPLACE PUBLISHER (AGL-4047).\n *\n * Saving the publisher profile, managing the listings the workspace has\n * published and setting up where its sales are paid asked whether the\n * member's workspace role was owner or admin: the routes through a role\n * helper, and the Firestore rules on `publisherProfiles` and\n * `marketplaceListings` through a role list of their own. Each is now a key\n * whose default is exactly those roles (the owner on the admin tier), so\n * nobody's access moved, and a custom role or a member's own permissions can\n * grant or refuse it.\n *\n * What is NOT a key: whether someone belongs to the workspace that published\n * a listing (`isPublisherMember`). Its members install it free on its own\n * sites (`ownsListingHere`; anywhere else they buy it like anyone), and\n * nobody in it may buy or review it. That is a fact about who published the\n * listing, so no grant can buy it and no refusal can take it away.\n *\n * Declared through `registerPluginPermissions`, as Sequences declares its own\n * workspace keys: a key of that registry rides the permission map the\n * console hands the marketplace's pages, which is what the seller sections\n * disable their controls by.\n */\n\n/** The marketplace's publisher keys. */\nexport type MarketplacePublisherPermission =\n | 'marketplace.publisherProfile'\n | 'marketplace.manageListings'\n | 'marketplace.payouts'\n\nexport const MARKETPLACE_PUBLISHER_PROFILE_PERMISSION: MarketplacePublisherPermission = 'marketplace.publisherProfile'\nexport const MARKETPLACE_MANAGE_LISTINGS_PERMISSION: MarketplacePublisherPermission = 'marketplace.manageListings'\nexport const MARKETPLACE_PAYOUTS_PERMISSION: MarketplacePublisherPermission = 'marketplace.payouts'\n\n/** The keys as the plugin declares them; owners and admins hold each by default. */\nexport const MARKETPLACE_PUBLISHER_PERMISSIONS: readonly PluginPermission[] = [\n {\n key: 'marketplace.publisherProfile',\n pluginId: 'marketplace',\n label: 'Edit the publisher profile',\n description:\n 'Change the workspace’s public marketplace profile (its handle, name, bio, logo, support contact and links), and ask for the Verified publisher badge or withdraw the ask.',\n defaults: { admin: true, editor: false, viewer: false },\n groupLabel: 'Marketplace',\n },\n {\n key: 'marketplace.manageListings',\n pluginId: 'marketplace',\n label: 'Manage published listings',\n description:\n 'Edit the listings the workspace has published (their words, pictures and category), make a plugin public or private, unpublish or republish a listing, and see each version’s review status.',\n defaults: { admin: true, editor: false, viewer: false },\n groupLabel: 'Marketplace',\n },\n {\n key: 'marketplace.payouts',\n pluginId: 'marketplace',\n label: 'Manage marketplace payouts',\n description:\n 'Connect the payout account the workspace’s paid listings pay into, finish its setup, and open its payouts.',\n defaults: { admin: true, editor: false, viewer: false },\n groupLabel: 'Marketplace',\n },\n]\n\n/** The refusal a door answers without one: names the permission, never a role. */\nexport function marketplacePublisherPermissionRefusal(key: MarketplacePublisherPermission): string {\n const label = MARKETPLACE_PUBLISHER_PERMISSIONS.find((entry) => entry.key === key)?.label ?? key\n return `Your role does not include \"${label}\" — ask an organization admin`\n}\n"],"names":["MARKETPLACE_PUBLISHER_PROFILE_PERMISSION","MARKETPLACE_MANAGE_LISTINGS_PERMISSION","MARKETPLACE_PAYOUTS_PERMISSION","MARKETPLACE_PUBLISHER_PERMISSIONS","key","pluginId","label","description","defaults","admin","editor","viewer","groupLabel","marketplacePublisherPermissionRefusal","find","entry"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAkCD,OAAO,MAAMA,2CAA2E,+BAA8B;AACtH,OAAO,MAAMC,yCAAyE,6BAA4B;AAClH,OAAO,MAAMC,iCAAiE,sBAAqB;AAEnG,kFAAkF,GAClF,OAAO,MAAMC,oCAAiE;IAC5E;QACEC,KAAK;QACLC,UAAU;QACVC,OAAO;QACPC,aACE;QACFC,UAAU;YAAEC,OAAO;YAAMC,QAAQ;YAAOC,QAAQ;QAAM;QACtDC,YAAY;IACd;IACA;QACER,KAAK;QACLC,UAAU;QACVC,OAAO;QACPC,aACE;QACFC,UAAU;YAAEC,OAAO;YAAMC,QAAQ;YAAOC,QAAQ;QAAM;QACtDC,YAAY;IACd;IACA;QACER,KAAK;QACLC,UAAU;QACVC,OAAO;QACPC,aACE;QACFC,UAAU;YAAEC,OAAO;YAAMC,QAAQ;YAAOC,QAAQ;QAAM;QACtDC,YAAY;IACd;CACD,CAAA;AAED,gFAAgF,GAChF,OAAO,SAASC,sCAAsCT,GAAmC;;QACzED;IAAd,MAAMG,iBAAQH,0CAAAA,kCAAkCW,IAAI,CAAC,CAACC,QAAUA,MAAMX,GAAG,KAAKA,yBAAhED,wCAAsEG,KAAK,mBAAIF;IAC7F,OAAO,CAAC,4BAA4B,EAAEE,MAAM,6BAA6B,CAAC;AAC5E"}
|