@aglyn/plugins-marketplace 1.0.0-beta.149 → 1.0.0-beta.151
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 +15 -11
- package/src/lib/components/edit-listing-dialog.component.d.ts +20 -0
- package/src/lib/components/edit-listing-dialog.component.js +51 -0
- package/src/lib/components/edit-listing-dialog.component.js.map +1 -0
- package/src/lib/components/listing-detail-editor.component.d.ts +24 -0
- package/src/lib/components/listing-detail-editor.component.js +457 -0
- package/src/lib/components/listing-detail-editor.component.js.map +1 -0
- package/src/lib/components/listing-detail-page.component.d.ts +28 -0
- package/src/lib/components/listing-detail-page.component.js +151 -0
- package/src/lib/components/listing-detail-page.component.js.map +1 -0
- package/src/lib/components/listing-review-status.component.d.ts +67 -0
- package/src/lib/components/listing-review-status.component.js +301 -0
- package/src/lib/components/listing-review-status.component.js.map +1 -0
- package/src/lib/components/listing-verification-request.component.d.ts +40 -0
- package/src/lib/components/listing-verification-request.component.js +170 -0
- package/src/lib/components/listing-verification-request.component.js.map +1 -0
- package/src/lib/components/marketplace-hub.component.d.ts +43 -0
- package/src/lib/components/marketplace-hub.component.js +244 -0
- package/src/lib/components/marketplace-hub.component.js.map +1 -0
- package/src/lib/components/marketplace-reports-page.component.d.ts +23 -0
- package/src/lib/components/marketplace-reports-page.component.js +324 -0
- package/src/lib/components/marketplace-reports-page.component.js.map +1 -0
- package/src/lib/components/org-licences-panel.component.d.ts +73 -0
- package/src/lib/components/org-licences-panel.component.js +318 -0
- package/src/lib/components/org-licences-panel.component.js.map +1 -0
- package/src/lib/components/org-publish-panel.component.d.ts +45 -0
- package/src/lib/components/org-publish-panel.component.js +402 -0
- package/src/lib/components/org-publish-panel.component.js.map +1 -0
- package/src/lib/components/org-seller-panel.component.d.ts +28 -0
- package/src/lib/components/org-seller-panel.component.js +956 -0
- package/src/lib/components/org-seller-panel.component.js.map +1 -0
- package/src/lib/components/plugin-review-detail.component.d.ts +16 -0
- package/src/lib/components/plugin-review-detail.component.js +1438 -0
- package/src/lib/components/plugin-review-detail.component.js.map +1 -0
- package/src/lib/components/plugin-reviews-queue.component.d.ts +13 -0
- package/src/lib/components/plugin-reviews-queue.component.js +385 -0
- package/src/lib/components/plugin-reviews-queue.component.js.map +1 -0
- package/src/lib/components/plugin-reviews-surface.component.d.ts +19 -0
- package/src/lib/components/plugin-reviews-surface.component.js +46 -0
- package/src/lib/components/plugin-reviews-surface.component.js.map +1 -0
- package/src/lib/components/publish-plugin-form.component.d.ts +61 -0
- package/src/lib/components/publish-plugin-form.component.js +857 -0
- package/src/lib/components/publish-plugin-form.component.js.map +1 -0
- package/src/lib/components/publish-plugin-page.component.d.ts +29 -0
- package/src/lib/components/publish-plugin-page.component.js +92 -0
- package/src/lib/components/publish-plugin-page.component.js.map +1 -0
- package/src/lib/components/publisher-profile-page.component.d.ts +13 -0
- package/src/lib/components/publisher-profile-page.component.js +261 -0
- package/src/lib/components/publisher-profile-page.component.js.map +1 -0
- package/src/lib/constants/listing-media.d.ts +28 -0
- package/src/lib/constants/listing-media.js +28 -0
- package/src/lib/constants/listing-media.js.map +1 -0
- package/src/lib/model/listing-social-card.d.ts +94 -0
- package/src/lib/model/listing-social-card.js +177 -0
- package/src/lib/model/listing-social-card.js.map +1 -0
- package/src/lib/model/marketplace-paths.d.ts +54 -0
- package/src/lib/model/marketplace-paths.js +59 -0
- package/src/lib/model/marketplace-paths.js.map +1 -0
- package/src/lib/model/payout-readiness.d.ts +81 -0
- package/src/lib/model/payout-readiness.js +52 -0
- package/src/lib/model/payout-readiness.js.map +1 -0
- package/src/lib/model/plugin-rejection-categories.d.ts +66 -0
- package/src/lib/model/plugin-rejection-categories.js +124 -0
- package/src/lib/model/plugin-rejection-categories.js.map +1 -0
- package/src/lib/model/plugin-review-checklist.d.ts +62 -0
- package/src/lib/model/plugin-review-checklist.js +131 -0
- package/src/lib/model/plugin-review-checklist.js.map +1 -0
- package/src/lib/model/plugin-review-status.d.ts +104 -0
- package/src/lib/model/plugin-review-status.js +126 -0
- package/src/lib/model/plugin-review-status.js.map +1 -0
- package/src/lib/model/seller-ledger-totals.d.ts +100 -0
- package/src/lib/model/seller-ledger-totals.js +96 -0
- package/src/lib/model/seller-ledger-totals.js.map +1 -0
- package/src/lib/plugin.js +170 -40
- package/src/lib/plugin.js.map +1 -1
- package/src/lib/server/admin-reports.d.ts +18 -0
- package/src/lib/server/admin-reports.js +280 -0
- package/src/lib/server/admin-reports.js.map +1 -0
- package/src/lib/server/admin-reviews.d.ts +18 -0
- package/src/lib/server/admin-reviews.js +1341 -0
- package/src/lib/server/admin-reviews.js.map +1 -0
- package/src/lib/server/listing-social-card.server.d.ts +24 -0
- package/src/lib/server/listing-social-card.server.js +65 -0
- package/src/lib/server/listing-social-card.server.js.map +1 -0
- package/src/lib/server/publisher-review-email.d.ts +54 -0
- package/src/lib/server/publisher-review-email.js +85 -0
- package/src/lib/server/publisher-review-email.js.map +1 -0
- package/src/lib/server/revoke-cache-drop.d.ts +59 -0
- package/src/lib/server/revoke-cache-drop.js +74 -0
- package/src/lib/server/revoke-cache-drop.js.map +1 -0
- package/src/lib/server.js +47 -0
- package/src/lib/server.js.map +1 -1
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
2
|
+
/**
|
|
3
|
+
* @license
|
|
4
|
+
* Copyright 2026 Aglyn LLC
|
|
5
|
+
*
|
|
6
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
* you may not use this file except in compliance with the License.
|
|
8
|
+
* You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*
|
|
12
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
* See the License for the specific language governing permissions and
|
|
16
|
+
* limitations under the License.
|
|
17
|
+
*/ import { dropPluginSiteCache } from "@aglyn/aglyn/plugin-manager/plugin-site-cache";
|
|
18
|
+
export async function dropCachesForListing(firestore, listingId) {
|
|
19
|
+
const nothing = {
|
|
20
|
+
installsFound: 0,
|
|
21
|
+
dropped: 0,
|
|
22
|
+
skipped: 0,
|
|
23
|
+
complete: true
|
|
24
|
+
};
|
|
25
|
+
if (!listingId) return nothing;
|
|
26
|
+
let hostIds;
|
|
27
|
+
let installsFound;
|
|
28
|
+
try {
|
|
29
|
+
const installs = await firestore.collectionGroup('installs').where('listingId', '==', listingId).get();
|
|
30
|
+
if (installs.empty) return nothing;
|
|
31
|
+
installsFound = installs.size;
|
|
32
|
+
const hosts = new Set();
|
|
33
|
+
const orgIds = new Set();
|
|
34
|
+
for (const doc of installs.docs){
|
|
35
|
+
const owner = doc.ref.parent.parent;
|
|
36
|
+
if (!owner) continue;
|
|
37
|
+
if (owner.parent.id === 'hosts') hosts.add(owner.id);
|
|
38
|
+
else if (owner.parent.id === 'orgs') orgIds.add(owner.id);
|
|
39
|
+
}
|
|
40
|
+
// An org-tier pin applies to every host in the org, so it is the org's
|
|
41
|
+
// hosts that hold the cached HTML — the org itself renders nothing.
|
|
42
|
+
await Promise.all([
|
|
43
|
+
...orgIds
|
|
44
|
+
].map(async (orgId)=>{
|
|
45
|
+
const owned = await firestore.collection('hosts').where('orgId', '==', orgId).get();
|
|
46
|
+
for (const host of owned.docs)hosts.add(host.id);
|
|
47
|
+
}));
|
|
48
|
+
hostIds = [
|
|
49
|
+
...hosts
|
|
50
|
+
];
|
|
51
|
+
} catch (error) {
|
|
52
|
+
/*
|
|
53
|
+
* The READ failed, so nothing is known about which sites are affected —
|
|
54
|
+
* which is not the same as none being affected. `complete: false` for
|
|
55
|
+
* the same reason the shell uses it: a zero that reads as an answer is
|
|
56
|
+
* how a kill switch quietly does not take effect.
|
|
57
|
+
*/ console.error('[marketplace] could not find the sites running', listingId, error);
|
|
58
|
+
return {
|
|
59
|
+
installsFound: 0,
|
|
60
|
+
dropped: 0,
|
|
61
|
+
skipped: 0,
|
|
62
|
+
complete: false
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
const result = await dropPluginSiteCache({
|
|
66
|
+
hostIds,
|
|
67
|
+
reason: `marketplace listing ${listingId} revoked`
|
|
68
|
+
});
|
|
69
|
+
return _extends({}, result, {
|
|
70
|
+
installsFound
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
//# sourceMappingURL=revoke-cache-drop.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/server/revoke-cache-drop.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 {\n dropPluginSiteCache,\n type PluginSiteCacheResult,\n} from '@aglyn/aglyn/plugin-manager/plugin-site-cache'\nimport type { Firestore } from 'firebase-admin/firestore'\n\n/**\n * WHICH SITES ARE RUNNING THIS LISTING (AGL-1152, moved here by AGL-3080).\n *\n * ## Why a revocation needs it\n *\n * The tenant stamps each `marketplacePlugin` node with its pinned install\n * and its kill-switch state AT COMPOSE TIME, so a page cached before a\n * revocation goes on serving the pre-revocation answer until it re-renders.\n * The per-install revocation read is deliberately on a short TTL as a\n * security bound — but that bound is only consulted DURING a render, so on a\n * page nobody is requesting it bounds nothing at all. Dropping the cache is\n * what actually makes a kill switch take effect on already-cached HTML.\n *\n * ## Scope\n *\n * Installs live at BOTH `orgs/{orgId}/installs/{listingId}` (org-tier,\n * applies to every host in the org — AGL-237) and\n * `hosts/{hostId}/installs/{listingId}`, so the collection-group query finds\n * both and org hits are expanded to their hosts. Backed by the\n * `installs.listingId` COLLECTION_GROUP field override; Firestore\n * auto-creates single-field indexes at COLLECTION scope only.\n *\n * ## Why this half is the plugin's\n *\n * `installs` is this plugin's own collection — its `hostCollections` entry\n * names it — and what an org-tier pin means is its rule. It used to sit in\n * the console's `tenant-revalidate.ts` beside the fan-out, which meant the\n * console knew what an install pin was. The fan-out stayed there and is\n * reached through `dropPluginSiteCache`: dropping a site's pages takes the\n * tenant's revalidation paths and cache tags, which are the app's.\n *\n * BEST EFFORT, and never throws: the tenant still refuses a revoked plugin\n * at render time. This only shrinks the window in which cached HTML shows\n * the old answer. Read `complete` before reading `dropped` — see\n * {@link dropPluginSiteCache}.\n */\nexport interface RevokeCacheDropResult extends PluginSiteCacheResult {\n /** Install pins matched by the collection-group query. */\n installsFound: number\n}\n\nexport async function dropCachesForListing(\n firestore: Firestore,\n listingId: string,\n): Promise<RevokeCacheDropResult> {\n const nothing: RevokeCacheDropResult = {\n installsFound: 0,\n dropped: 0,\n skipped: 0,\n complete: true,\n }\n if (!listingId) return nothing\n let hostIds: string[]\n let installsFound: number\n try {\n const installs = await firestore\n .collectionGroup('installs')\n .where('listingId', '==', listingId)\n .get()\n if (installs.empty) return nothing\n installsFound = installs.size\n\n const hosts = new Set<string>()\n const orgIds = new Set<string>()\n for (const doc of installs.docs) {\n const owner = doc.ref.parent.parent\n if (!owner) continue\n if (owner.parent.id === 'hosts') hosts.add(owner.id)\n else if (owner.parent.id === 'orgs') orgIds.add(owner.id)\n }\n // An org-tier pin applies to every host in the org, so it is the org's\n // hosts that hold the cached HTML — the org itself renders nothing.\n await Promise.all(\n [...orgIds].map(async (orgId) => {\n const owned = await firestore\n .collection('hosts')\n .where('orgId', '==', orgId)\n .get()\n for (const host of owned.docs) hosts.add(host.id)\n }),\n )\n hostIds = [...hosts]\n } catch (error) {\n /*\n * The READ failed, so nothing is known about which sites are affected —\n * which is not the same as none being affected. `complete: false` for\n * the same reason the shell uses it: a zero that reads as an answer is\n * how a kill switch quietly does not take effect.\n */\n console.error(\n '[marketplace] could not find the sites running',\n listingId,\n error,\n )\n return { installsFound: 0, dropped: 0, skipped: 0, complete: false }\n }\n\n const result = await dropPluginSiteCache({\n hostIds,\n reason: `marketplace listing ${listingId} revoked`,\n })\n return { ...result, installsFound }\n}\n"],"names":["dropPluginSiteCache","dropCachesForListing","firestore","listingId","nothing","installsFound","dropped","skipped","complete","hostIds","installs","collectionGroup","where","get","empty","size","hosts","Set","orgIds","doc","docs","owner","ref","parent","id","add","Promise","all","map","orgId","owned","collection","host","error","console","result","reason"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED,SACEA,mBAAmB,QAEd,gDAA+C;AA4CtD,OAAO,eAAeC,qBACpBC,SAAoB,EACpBC,SAAiB;IAEjB,MAAMC,UAAiC;QACrCC,eAAe;QACfC,SAAS;QACTC,SAAS;QACTC,UAAU;IACZ;IACA,IAAI,CAACL,WAAW,OAAOC;IACvB,IAAIK;IACJ,IAAIJ;IACJ,IAAI;QACF,MAAMK,WAAW,MAAMR,UACpBS,eAAe,CAAC,YAChBC,KAAK,CAAC,aAAa,MAAMT,WACzBU,GAAG;QACN,IAAIH,SAASI,KAAK,EAAE,OAAOV;QAC3BC,gBAAgBK,SAASK,IAAI;QAE7B,MAAMC,QAAQ,IAAIC;QAClB,MAAMC,SAAS,IAAID;QACnB,KAAK,MAAME,OAAOT,SAASU,IAAI,CAAE;YAC/B,MAAMC,QAAQF,IAAIG,GAAG,CAACC,MAAM,CAACA,MAAM;YACnC,IAAI,CAACF,OAAO;YACZ,IAAIA,MAAME,MAAM,CAACC,EAAE,KAAK,SAASR,MAAMS,GAAG,CAACJ,MAAMG,EAAE;iBAC9C,IAAIH,MAAME,MAAM,CAACC,EAAE,KAAK,QAAQN,OAAOO,GAAG,CAACJ,MAAMG,EAAE;QAC1D;QACA,uEAAuE;QACvE,oEAAoE;QACpE,MAAME,QAAQC,GAAG,CACf;eAAIT;SAAO,CAACU,GAAG,CAAC,OAAOC;YACrB,MAAMC,QAAQ,MAAM5B,UACjB6B,UAAU,CAAC,SACXnB,KAAK,CAAC,SAAS,MAAMiB,OACrBhB,GAAG;YACN,KAAK,MAAMmB,QAAQF,MAAMV,IAAI,CAAEJ,MAAMS,GAAG,CAACO,KAAKR,EAAE;QAClD;QAEFf,UAAU;eAAIO;SAAM;IACtB,EAAE,OAAOiB,OAAO;QACd;;;;;KAKC,GACDC,QAAQD,KAAK,CACX,kDACA9B,WACA8B;QAEF,OAAO;YAAE5B,eAAe;YAAGC,SAAS;YAAGC,SAAS;YAAGC,UAAU;QAAM;IACrE;IAEA,MAAM2B,SAAS,MAAMnC,oBAAoB;QACvCS;QACA2B,QAAQ,CAAC,oBAAoB,EAAEjC,UAAU,QAAQ,CAAC;IACpD;IACA,OAAO,aAAKgC;QAAQ9B;;AACtB"}
|
package/src/lib/server.js
CHANGED
|
@@ -14,6 +14,10 @@
|
|
|
14
14
|
* See the License for the specific language governing permissions and
|
|
15
15
|
* limitations under the License.
|
|
16
16
|
*/ import { registerBillingWebhookHandler, registerPluginApiRoute, registerCustomFieldType } from "@aglyn/aglyn/server";
|
|
17
|
+
// Its own subpath, never the plugin-manager barrel: what a published page
|
|
18
|
+
// does not need, it must not import.
|
|
19
|
+
import { registerPluginRouteMetadata } from "@aglyn/aglyn/plugin-manager/plugin-route-metadata";
|
|
20
|
+
import { BUNDLE_ID } from "./constants/bundle-common.js";
|
|
17
21
|
import { marketplaceBillingWebhookHandler } from "./server/billing-webhook.js";
|
|
18
22
|
import { publishPluginHandler } from "./server/publish-plugin.js";
|
|
19
23
|
import { verificationRequestHandler } from "./server/verification-request.js";
|
|
@@ -31,6 +35,8 @@ import { installTemplateHandler } from "./server/install-template.js";
|
|
|
31
35
|
import { installThemeHandler } from "./server/install-theme.js";
|
|
32
36
|
import { previewImageHandler } from "./server/preview-image.js";
|
|
33
37
|
import { publishHandler } from "./server/publish.js";
|
|
38
|
+
import { marketplaceAdminReports } from "./server/admin-reports.js";
|
|
39
|
+
import { marketplaceAdminReviews } from "./server/admin-reviews.js";
|
|
34
40
|
import { reportHandler } from "./server/report.js";
|
|
35
41
|
import { reviewsHandler } from "./server/reviews.js";
|
|
36
42
|
import { publisherProfileSaveHandler } from "./server/publisher-profile-save.js";
|
|
@@ -57,6 +63,33 @@ import { updateArtifactHandler } from "./server/update-artifact.js";
|
|
|
57
63
|
// Server side of the rating custom field (AGL-434): validators run
|
|
58
64
|
// on import/write paths even when no client loaded the plugin.
|
|
59
65
|
registerCustomFieldType(RATING_FIELD);
|
|
66
|
+
/*
|
|
67
|
+
* What a listing link unfurls as (AGL-876, handed to the platform by
|
|
68
|
+
* AGL-3080).
|
|
69
|
+
*
|
|
70
|
+
* The card used to be built by a hand-written console route with a server
|
|
71
|
+
* layout of its own, which is what kept that route out of the generic
|
|
72
|
+
* plugin route — moving it would have dropped every listing's card with
|
|
73
|
+
* nothing red. The shell asks this instead, so the card survives the move
|
|
74
|
+
* and no console file reads `marketplaceListings` to build a head.
|
|
75
|
+
*
|
|
76
|
+
* One segment and one only: `/{org}/marketplace/{listingId}`. The hub, the
|
|
77
|
+
* publish page and every other address under this route answer `null`,
|
|
78
|
+
* which leaves the shell's own title exactly as it was.
|
|
79
|
+
*/ registerPluginRouteMetadata({
|
|
80
|
+
route: 'marketplace',
|
|
81
|
+
resolve: async (segments)=>{
|
|
82
|
+
if (segments.length !== 1) return null;
|
|
83
|
+
const { readListingForSocialCard } = await import("./server/listing-social-card.server.js");
|
|
84
|
+
const { listingSocialCard } = await import("./model/listing-social-card.js");
|
|
85
|
+
return listingSocialCard(await readListingForSocialCard(segments[0]));
|
|
86
|
+
}
|
|
87
|
+
}, // Named, because this registrar is called directly by the app's server
|
|
88
|
+
// loader rather than from inside a plugin `register` fn, where the
|
|
89
|
+
// loader's owner marker would be set.
|
|
90
|
+
{
|
|
91
|
+
pluginId: BUNDLE_ID
|
|
92
|
+
});
|
|
60
93
|
registerPluginApiRoute('marketplace/checkout', checkoutHandler);
|
|
61
94
|
registerPluginApiRoute('marketplace/connect', connectHandler);
|
|
62
95
|
registerPluginApiRoute('marketplace/install', installHandler);
|
|
@@ -77,6 +110,20 @@ import { updateArtifactHandler } from "./server/update-artifact.js";
|
|
|
77
110
|
registerPluginApiRoute('marketplace/preview-image', previewImageHandler);
|
|
78
111
|
registerPluginApiRoute('marketplace/publish', publishHandler);
|
|
79
112
|
registerPluginApiRoute('marketplace/report', reportHandler);
|
|
113
|
+
/*
|
|
114
|
+
* The staff end of that button (AGL-2310), moved out of
|
|
115
|
+
* `apps/console/app/api/admin/` by AGL-3080. `web:` because it is a plain
|
|
116
|
+
* fetch from this plugin's own staff page, not a plugin-request handler —
|
|
117
|
+
* the same shape the AI plugin's `ai/admin/*` routes use, and for the same
|
|
118
|
+
* reason: a staff route names no host, so there is no subject to resolve.
|
|
119
|
+
*/ registerPluginApiRoute('marketplace/admin/reports', {
|
|
120
|
+
web: marketplaceAdminReports
|
|
121
|
+
});
|
|
122
|
+
// The review queue (AGL-432, moved by AGL-3080). Same shape and the same
|
|
123
|
+
// reason: a staff route names no host, so there is no subject to resolve.
|
|
124
|
+
registerPluginApiRoute('marketplace/admin/reviews', {
|
|
125
|
+
web: marketplaceAdminReviews
|
|
126
|
+
});
|
|
80
127
|
registerPluginApiRoute('marketplace/reviews', reviewsHandler);
|
|
81
128
|
registerPluginApiRoute('marketplace/publisher-profile', publisherProfileSaveHandler);
|
|
82
129
|
registerPluginApiRoute('marketplace/verification-request', verificationRequestHandler);
|
package/src/lib/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../libs/plugins/marketplace/src/lib/server.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 {\n registerBillingWebhookHandler,\n registerPluginApiRoute,\n registerCustomFieldType,\n} from '@aglyn/aglyn/server'\nimport { marketplaceBillingWebhookHandler } from './server/billing-webhook'\nimport { publishPluginHandler } from './server/publish-plugin'\nimport { verificationRequestHandler } from './server/verification-request'\nimport { checkoutHandler } from './server/checkout'\nimport { connectHandler } from './server/connect'\nimport { installHandler } from './server/install'\nimport { installPluginHandler } from './server/install-plugin'\nimport { listingVersionsHandler } from './server/listing-versions'\nimport { RATING_FIELD } from './model/rating-field'\nimport { installDatasetSchemaHandler } from './server/install-dataset-schema'\nimport { installEmailStarterHandler } from './server/install-email-starter'\nimport { installEmailTemplateHandler } from './server/install-email-template'\nimport { installLayoutHandler } from './server/install-layout'\nimport { installTemplateHandler } from './server/install-template'\nimport { installThemeHandler } from './server/install-theme'\nimport { previewImageHandler } from './server/preview-image'\nimport { publishHandler } from './server/publish'\nimport { reportHandler } from './server/report'\nimport { reviewsHandler } from './server/reviews'\nimport { publisherProfileSaveHandler } from './server/publisher-profile-save'\nimport { publishDatasetSchemaHandler } from './server/publish-dataset-schema'\nimport { publishEmailStarterHandler } from './server/publish-email-starter'\nimport { publishEmailTemplateHandler } from './server/publish-email-template'\nimport { publishLayoutHandler } from './server/publish-layout'\nimport { publishTemplateHandler } from './server/publish-template'\nimport { publishThemeHandler } from './server/publish-theme'\nimport { updateArtifactHandler } from './server/update-artifact'\n\n/**\n * Registers the marketplace plugin's console-side API routes (AGL-396):\n * marketplace publish/install of templates and plugins, and the Stripe\n * Connect + checkout flows for paid listings.\n *\n * Upload-bodied routes register here too. The note that used to sit here —\n * that they had to stay as named console routes because the dispatcher\n * \"can't grant per-route bodyParser limits\" — was a Pages Router leftover:\n * `runLegacyHandler` reads the request through the same\n * `pluginRequestFromWeb` a named App Router route would, and imposes no\n * limit of its own. publish-plugin's 8 MB bundle has been going through the\n * dispatcher the whole time.\n */\nexport function registerMarketplaceConsoleApi(): void {\n // Server side of the rating custom field (AGL-434): validators run\n // on import/write paths even when no client loaded the plugin.\n registerCustomFieldType(RATING_FIELD)\n registerPluginApiRoute('marketplace/checkout', checkoutHandler)\n registerPluginApiRoute('marketplace/connect', connectHandler)\n registerPluginApiRoute('marketplace/install', installHandler)\n registerPluginApiRoute('marketplace/install-plugin', installPluginHandler)\n registerPluginApiRoute('marketplace/install-layout', installLayoutHandler)\n registerPluginApiRoute('marketplace/install-template', installTemplateHandler)\n // A theme install DOES change the running site (AGL-1020) — it is the site's\n // appearance — so this route also owns the ways back: `revert` and `reset`.\n registerPluginApiRoute('marketplace/install-theme', installThemeHandler)\n registerPluginApiRoute(\n 'marketplace/install-dataset-schema',\n installDatasetSchemaHandler,\n )\n registerPluginApiRoute(\n 'marketplace/install-email-starter',\n installEmailStarterHandler,\n )\n registerPluginApiRoute(\n 'marketplace/install-email-template',\n installEmailTemplateHandler,\n )\n // Updating a copied artifact is its own route (AGL-1018), not a flag on\n // install: install writes the publisher's version, this one reconciles it\n // with a copy that has diverged and refuses to overwrite silently.\n registerPluginApiRoute('marketplace/update-artifact', updateArtifactHandler)\n registerPluginApiRoute('marketplace/listing-versions', listingVersionsHandler)\n registerPluginApiRoute('marketplace/preview-image', previewImageHandler)\n registerPluginApiRoute('marketplace/publish', publishHandler)\n registerPluginApiRoute('marketplace/report', reportHandler)\n registerPluginApiRoute('marketplace/reviews', reviewsHandler)\n registerPluginApiRoute(\n 'marketplace/publisher-profile',\n publisherProfileSaveHandler,\n )\n registerPluginApiRoute(\n 'marketplace/verification-request',\n verificationRequestHandler,\n )\n registerPluginApiRoute('marketplace/publish-layout', publishLayoutHandler)\n registerPluginApiRoute('marketplace/publish-template', publishTemplateHandler)\n registerPluginApiRoute('marketplace/publish-theme', publishThemeHandler)\n registerPluginApiRoute(\n 'marketplace/publish-dataset-schema',\n publishDatasetSchemaHandler,\n )\n registerPluginApiRoute(\n 'marketplace/publish-email-starter',\n publishEmailStarterHandler,\n )\n registerPluginApiRoute(\n 'marketplace/publish-email-template',\n publishEmailTemplateHandler,\n )\n // Relocated console routes (AGL-418): URLs preserved via the dispatcher.\n registerPluginApiRoute('marketplace/publish-plugin', publishPluginHandler)\n // Marketplace purchases ride the platform Stripe webhook (AGL-418).\n registerBillingWebhookHandler(marketplaceBillingWebhookHandler)\n}\n"],"names":["registerBillingWebhookHandler","registerPluginApiRoute","registerCustomFieldType","marketplaceBillingWebhookHandler","publishPluginHandler","verificationRequestHandler","checkoutHandler","connectHandler","installHandler","installPluginHandler","listingVersionsHandler","RATING_FIELD","installDatasetSchemaHandler","installEmailStarterHandler","installEmailTemplateHandler","installLayoutHandler","installTemplateHandler","installThemeHandler","previewImageHandler","publishHandler","reportHandler","reviewsHandler","publisherProfileSaveHandler","publishDatasetSchemaHandler","publishEmailStarterHandler","publishEmailTemplateHandler","publishLayoutHandler","publishTemplateHandler","publishThemeHandler","updateArtifactHandler","registerMarketplaceConsoleApi"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,SACEA,6BAA6B,EAC7BC,sBAAsB,EACtBC,uBAAuB,QAClB,sBAAqB;AAC5B,SAASC,gCAAgC,QAAQ,8BAA0B;AAC3E,SAASC,oBAAoB,QAAQ,6BAAyB;AAC9D,SAASC,0BAA0B,QAAQ,mCAA+B;AAC1E,SAASC,eAAe,QAAQ,uBAAmB;AACnD,SAASC,cAAc,QAAQ,sBAAkB;AACjD,SAASC,cAAc,QAAQ,sBAAkB;AACjD,SAASC,oBAAoB,QAAQ,6BAAyB;AAC9D,SAASC,sBAAsB,QAAQ,+BAA2B;AAClE,SAASC,YAAY,QAAQ,0BAAsB;AACnD,SAASC,2BAA2B,QAAQ,qCAAiC;AAC7E,SAASC,0BAA0B,QAAQ,oCAAgC;AAC3E,SAASC,2BAA2B,QAAQ,qCAAiC;AAC7E,SAASC,oBAAoB,QAAQ,6BAAyB;AAC9D,SAASC,sBAAsB,QAAQ,+BAA2B;AAClE,SAASC,mBAAmB,QAAQ,4BAAwB;AAC5D,SAASC,mBAAmB,QAAQ,4BAAwB;AAC5D,SAASC,cAAc,QAAQ,sBAAkB;AACjD,SAASC,aAAa,QAAQ,qBAAiB;AAC/C,SAASC,cAAc,QAAQ,sBAAkB;AACjD,SAASC,2BAA2B,QAAQ,qCAAiC;AAC7E,SAASC,2BAA2B,QAAQ,qCAAiC;AAC7E,SAASC,0BAA0B,QAAQ,oCAAgC;AAC3E,SAASC,2BAA2B,QAAQ,qCAAiC;AAC7E,SAASC,oBAAoB,QAAQ,6BAAyB;AAC9D,SAASC,sBAAsB,QAAQ,+BAA2B;AAClE,SAASC,mBAAmB,QAAQ,4BAAwB;AAC5D,SAASC,qBAAqB,QAAQ,8BAA0B;AAEhE;;;;;;;;;;;;CAYC,GACD,OAAO,SAASC;IACd,mEAAmE;IACnE,+DAA+D;IAC/D5B,wBAAwBS;IACxBV,uBAAuB,wBAAwBK;IAC/CL,uBAAuB,uBAAuBM;IAC9CN,uBAAuB,uBAAuBO;IAC9CP,uBAAuB,8BAA8BQ;IACrDR,uBAAuB,8BAA8Bc;IACrDd,uBAAuB,gCAAgCe;IACvD,6EAA6E;IAC7E,4EAA4E;IAC5Ef,uBAAuB,6BAA6BgB;IACpDhB,uBACE,sCACAW;IAEFX,uBACE,qCACAY;IAEFZ,uBACE,sCACAa;IAEF,wEAAwE;IACxE,0EAA0E;IAC1E,mEAAmE;IACnEb,uBAAuB,+BAA+B4B;IACtD5B,uBAAuB,gCAAgCS;IACvDT,uBAAuB,6BAA6BiB;IACpDjB,uBAAuB,uBAAuBkB;IAC9ClB,uBAAuB,sBAAsBmB;IAC7CnB,uBAAuB,uBAAuBoB;IAC9CpB,uBACE,iCACAqB;IAEFrB,uBACE,oCACAI;IAEFJ,uBAAuB,8BAA8ByB;IACrDzB,uBAAuB,gCAAgC0B;IACvD1B,uBAAuB,6BAA6B2B;IACpD3B,uBACE,sCACAsB;IAEFtB,uBACE,qCACAuB;IAEFvB,uBACE,sCACAwB;IAEF,yEAAyE;IACzExB,uBAAuB,8BAA8BG;IACrD,oEAAoE;IACpEJ,8BAA8BG;AAChC"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/plugins/marketplace/src/lib/server.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 {\n registerBillingWebhookHandler,\n registerPluginApiRoute,\n registerCustomFieldType,\n} from '@aglyn/aglyn/server'\n// Its own subpath, never the plugin-manager barrel: what a published page\n// does not need, it must not import.\nimport { registerPluginRouteMetadata } from '@aglyn/aglyn/plugin-manager/plugin-route-metadata'\nimport { BUNDLE_ID } from './constants/bundle-common'\nimport { marketplaceBillingWebhookHandler } from './server/billing-webhook'\nimport { publishPluginHandler } from './server/publish-plugin'\nimport { verificationRequestHandler } from './server/verification-request'\nimport { checkoutHandler } from './server/checkout'\nimport { connectHandler } from './server/connect'\nimport { installHandler } from './server/install'\nimport { installPluginHandler } from './server/install-plugin'\nimport { listingVersionsHandler } from './server/listing-versions'\nimport { RATING_FIELD } from './model/rating-field'\nimport { installDatasetSchemaHandler } from './server/install-dataset-schema'\nimport { installEmailStarterHandler } from './server/install-email-starter'\nimport { installEmailTemplateHandler } from './server/install-email-template'\nimport { installLayoutHandler } from './server/install-layout'\nimport { installTemplateHandler } from './server/install-template'\nimport { installThemeHandler } from './server/install-theme'\nimport { previewImageHandler } from './server/preview-image'\nimport { publishHandler } from './server/publish'\nimport { marketplaceAdminReports } from './server/admin-reports'\nimport { marketplaceAdminReviews } from './server/admin-reviews'\nimport { reportHandler } from './server/report'\nimport { reviewsHandler } from './server/reviews'\nimport { publisherProfileSaveHandler } from './server/publisher-profile-save'\nimport { publishDatasetSchemaHandler } from './server/publish-dataset-schema'\nimport { publishEmailStarterHandler } from './server/publish-email-starter'\nimport { publishEmailTemplateHandler } from './server/publish-email-template'\nimport { publishLayoutHandler } from './server/publish-layout'\nimport { publishTemplateHandler } from './server/publish-template'\nimport { publishThemeHandler } from './server/publish-theme'\nimport { updateArtifactHandler } from './server/update-artifact'\n\n/**\n * Registers the marketplace plugin's console-side API routes (AGL-396):\n * marketplace publish/install of templates and plugins, and the Stripe\n * Connect + checkout flows for paid listings.\n *\n * Upload-bodied routes register here too. The note that used to sit here —\n * that they had to stay as named console routes because the dispatcher\n * \"can't grant per-route bodyParser limits\" — was a Pages Router leftover:\n * `runLegacyHandler` reads the request through the same\n * `pluginRequestFromWeb` a named App Router route would, and imposes no\n * limit of its own. publish-plugin's 8 MB bundle has been going through the\n * dispatcher the whole time.\n */\nexport function registerMarketplaceConsoleApi(): void {\n // Server side of the rating custom field (AGL-434): validators run\n // on import/write paths even when no client loaded the plugin.\n registerCustomFieldType(RATING_FIELD)\n /*\n * What a listing link unfurls as (AGL-876, handed to the platform by\n * AGL-3080).\n *\n * The card used to be built by a hand-written console route with a server\n * layout of its own, which is what kept that route out of the generic\n * plugin route — moving it would have dropped every listing's card with\n * nothing red. The shell asks this instead, so the card survives the move\n * and no console file reads `marketplaceListings` to build a head.\n *\n * One segment and one only: `/{org}/marketplace/{listingId}`. The hub, the\n * publish page and every other address under this route answer `null`,\n * which leaves the shell's own title exactly as it was.\n */\n registerPluginRouteMetadata(\n {\n route: 'marketplace',\n resolve: async (segments) => {\n if (segments.length !== 1) return null\n const { readListingForSocialCard } = await import(\n './server/listing-social-card.server'\n )\n const { listingSocialCard } = await import('./model/listing-social-card')\n return listingSocialCard(await readListingForSocialCard(segments[0]))\n },\n },\n // Named, because this registrar is called directly by the app's server\n // loader rather than from inside a plugin `register` fn, where the\n // loader's owner marker would be set.\n { pluginId: BUNDLE_ID },\n )\n registerPluginApiRoute('marketplace/checkout', checkoutHandler)\n registerPluginApiRoute('marketplace/connect', connectHandler)\n registerPluginApiRoute('marketplace/install', installHandler)\n registerPluginApiRoute('marketplace/install-plugin', installPluginHandler)\n registerPluginApiRoute('marketplace/install-layout', installLayoutHandler)\n registerPluginApiRoute('marketplace/install-template', installTemplateHandler)\n // A theme install DOES change the running site (AGL-1020) — it is the site's\n // appearance — so this route also owns the ways back: `revert` and `reset`.\n registerPluginApiRoute('marketplace/install-theme', installThemeHandler)\n registerPluginApiRoute(\n 'marketplace/install-dataset-schema',\n installDatasetSchemaHandler,\n )\n registerPluginApiRoute(\n 'marketplace/install-email-starter',\n installEmailStarterHandler,\n )\n registerPluginApiRoute(\n 'marketplace/install-email-template',\n installEmailTemplateHandler,\n )\n // Updating a copied artifact is its own route (AGL-1018), not a flag on\n // install: install writes the publisher's version, this one reconciles it\n // with a copy that has diverged and refuses to overwrite silently.\n registerPluginApiRoute('marketplace/update-artifact', updateArtifactHandler)\n registerPluginApiRoute('marketplace/listing-versions', listingVersionsHandler)\n registerPluginApiRoute('marketplace/preview-image', previewImageHandler)\n registerPluginApiRoute('marketplace/publish', publishHandler)\n registerPluginApiRoute('marketplace/report', reportHandler)\n /*\n * The staff end of that button (AGL-2310), moved out of\n * `apps/console/app/api/admin/` by AGL-3080. `web:` because it is a plain\n * fetch from this plugin's own staff page, not a plugin-request handler —\n * the same shape the AI plugin's `ai/admin/*` routes use, and for the same\n * reason: a staff route names no host, so there is no subject to resolve.\n */\n registerPluginApiRoute('marketplace/admin/reports', {\n web: marketplaceAdminReports,\n })\n // The review queue (AGL-432, moved by AGL-3080). Same shape and the same\n // reason: a staff route names no host, so there is no subject to resolve.\n registerPluginApiRoute('marketplace/admin/reviews', {\n web: marketplaceAdminReviews,\n })\n registerPluginApiRoute('marketplace/reviews', reviewsHandler)\n registerPluginApiRoute(\n 'marketplace/publisher-profile',\n publisherProfileSaveHandler,\n )\n registerPluginApiRoute(\n 'marketplace/verification-request',\n verificationRequestHandler,\n )\n registerPluginApiRoute('marketplace/publish-layout', publishLayoutHandler)\n registerPluginApiRoute('marketplace/publish-template', publishTemplateHandler)\n registerPluginApiRoute('marketplace/publish-theme', publishThemeHandler)\n registerPluginApiRoute(\n 'marketplace/publish-dataset-schema',\n publishDatasetSchemaHandler,\n )\n registerPluginApiRoute(\n 'marketplace/publish-email-starter',\n publishEmailStarterHandler,\n )\n registerPluginApiRoute(\n 'marketplace/publish-email-template',\n publishEmailTemplateHandler,\n )\n // Relocated console routes (AGL-418): URLs preserved via the dispatcher.\n registerPluginApiRoute('marketplace/publish-plugin', publishPluginHandler)\n // Marketplace purchases ride the platform Stripe webhook (AGL-418).\n registerBillingWebhookHandler(marketplaceBillingWebhookHandler)\n}\n"],"names":["registerBillingWebhookHandler","registerPluginApiRoute","registerCustomFieldType","registerPluginRouteMetadata","BUNDLE_ID","marketplaceBillingWebhookHandler","publishPluginHandler","verificationRequestHandler","checkoutHandler","connectHandler","installHandler","installPluginHandler","listingVersionsHandler","RATING_FIELD","installDatasetSchemaHandler","installEmailStarterHandler","installEmailTemplateHandler","installLayoutHandler","installTemplateHandler","installThemeHandler","previewImageHandler","publishHandler","marketplaceAdminReports","marketplaceAdminReviews","reportHandler","reviewsHandler","publisherProfileSaveHandler","publishDatasetSchemaHandler","publishEmailStarterHandler","publishEmailTemplateHandler","publishLayoutHandler","publishTemplateHandler","publishThemeHandler","updateArtifactHandler","registerMarketplaceConsoleApi","route","resolve","segments","length","readListingForSocialCard","listingSocialCard","pluginId","web"],"mappings":"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"}
|