@aglyn/plugins-marketplace 1.0.0-beta.143

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.
Files changed (165) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +7 -0
  3. package/package.json +51 -0
  4. package/src/index.d.ts +19 -0
  5. package/src/index.js +20 -0
  6. package/src/index.js.map +1 -0
  7. package/src/lib/components/artifact-update-dialog.component.d.ts +27 -0
  8. package/src/lib/components/artifact-update-dialog.component.js +244 -0
  9. package/src/lib/components/artifact-update-dialog.component.js.map +1 -0
  10. package/src/lib/components/host-plugins-card.component.d.ts +23 -0
  11. package/src/lib/components/host-plugins-card.component.js +566 -0
  12. package/src/lib/components/host-plugins-card.component.js.map +1 -0
  13. package/src/lib/components/listing-content.component.d.ts +57 -0
  14. package/src/lib/components/listing-content.component.js +1880 -0
  15. package/src/lib/components/listing-content.component.js.map +1 -0
  16. package/src/lib/components/listing-image.component.d.ts +46 -0
  17. package/src/lib/components/listing-image.component.js +38 -0
  18. package/src/lib/components/listing-image.component.js.map +1 -0
  19. package/src/lib/components/listing-reviews.component.d.ts +22 -0
  20. package/src/lib/components/listing-reviews.component.js +419 -0
  21. package/src/lib/components/listing-reviews.component.js.map +1 -0
  22. package/src/lib/components/marketplace-browse.component.d.ts +38 -0
  23. package/src/lib/components/marketplace-browse.component.js +802 -0
  24. package/src/lib/components/marketplace-browse.component.js.map +1 -0
  25. package/src/lib/components/plugin-site-set-panel.component.d.ts +24 -0
  26. package/src/lib/components/plugin-site-set-panel.component.js +137 -0
  27. package/src/lib/components/plugin-site-set-panel.component.js.map +1 -0
  28. package/src/lib/components/plugin-site-set.component.d.ts +43 -0
  29. package/src/lib/components/plugin-site-set.component.js +425 -0
  30. package/src/lib/components/plugin-site-set.component.js.map +1 -0
  31. package/src/lib/components/rating-input.component.d.ts +3 -0
  32. package/src/lib/components/rating-input.component.js +47 -0
  33. package/src/lib/components/rating-input.component.js.map +1 -0
  34. package/src/lib/components/report-target.component.d.ts +36 -0
  35. package/src/lib/components/report-target.component.js +181 -0
  36. package/src/lib/components/report-target.component.js.map +1 -0
  37. package/src/lib/components/uninstall-impact-dialog.component.d.ts +40 -0
  38. package/src/lib/components/uninstall-impact-dialog.component.js +244 -0
  39. package/src/lib/components/uninstall-impact-dialog.component.js.map +1 -0
  40. package/src/lib/constants/bundle-common.d.ts +8 -0
  41. package/src/lib/constants/bundle-common.js +9 -0
  42. package/src/lib/constants/bundle-common.js.map +1 -0
  43. package/src/lib/hooks/use-artifact-update.d.ts +41 -0
  44. package/src/lib/hooks/use-artifact-update.js +169 -0
  45. package/src/lib/hooks/use-artifact-update.js.map +1 -0
  46. package/src/lib/hooks/use-marketplace-actions.d.ts +37 -0
  47. package/src/lib/hooks/use-marketplace-actions.js +523 -0
  48. package/src/lib/hooks/use-marketplace-actions.js.map +1 -0
  49. package/src/lib/model/artifact-merge.d.ts +154 -0
  50. package/src/lib/model/artifact-merge.js +228 -0
  51. package/src/lib/model/artifact-merge.js.map +1 -0
  52. package/src/lib/model/index.d.ts +43 -0
  53. package/src/lib/model/index.js +41 -0
  54. package/src/lib/model/index.js.map +1 -0
  55. package/src/lib/model/marketplace-props.d.ts +57 -0
  56. package/src/lib/model/marketplace-props.js +441 -0
  57. package/src/lib/model/marketplace-props.js.map +1 -0
  58. package/src/lib/model/marketplace.d.ts +833 -0
  59. package/src/lib/model/marketplace.js +929 -0
  60. package/src/lib/model/marketplace.js.map +1 -0
  61. package/src/lib/model/rating-field.d.ts +24 -0
  62. package/src/lib/model/rating-field.js +33 -0
  63. package/src/lib/model/rating-field.js.map +1 -0
  64. package/src/lib/plugin.d.ts +33 -0
  65. package/src/lib/plugin.js +86 -0
  66. package/src/lib/plugin.js.map +1 -0
  67. package/src/lib/server/billing-webhook.d.ts +25 -0
  68. package/src/lib/server/billing-webhook.js +1062 -0
  69. package/src/lib/server/billing-webhook.js.map +1 -0
  70. package/src/lib/server/checkout.d.ts +76 -0
  71. package/src/lib/server/checkout.js +477 -0
  72. package/src/lib/server/checkout.js.map +1 -0
  73. package/src/lib/server/connect.d.ts +24 -0
  74. package/src/lib/server/connect.js +205 -0
  75. package/src/lib/server/connect.js.map +1 -0
  76. package/src/lib/server/install-dataset-schema.d.ts +32 -0
  77. package/src/lib/server/install-dataset-schema.js +255 -0
  78. package/src/lib/server/install-dataset-schema.js.map +1 -0
  79. package/src/lib/server/install-email-starter.d.ts +65 -0
  80. package/src/lib/server/install-email-starter.js +279 -0
  81. package/src/lib/server/install-email-starter.js.map +1 -0
  82. package/src/lib/server/install-email-template.d.ts +34 -0
  83. package/src/lib/server/install-email-template.js +252 -0
  84. package/src/lib/server/install-email-template.js.map +1 -0
  85. package/src/lib/server/install-layout.d.ts +29 -0
  86. package/src/lib/server/install-layout.js +323 -0
  87. package/src/lib/server/install-layout.js.map +1 -0
  88. package/src/lib/server/install-pin-counts.d.ts +173 -0
  89. package/src/lib/server/install-pin-counts.js +248 -0
  90. package/src/lib/server/install-pin-counts.js.map +1 -0
  91. package/src/lib/server/install-plugin.d.ts +18 -0
  92. package/src/lib/server/install-plugin.js +405 -0
  93. package/src/lib/server/install-plugin.js.map +1 -0
  94. package/src/lib/server/install-template.d.ts +37 -0
  95. package/src/lib/server/install-template.js +348 -0
  96. package/src/lib/server/install-template.js.map +1 -0
  97. package/src/lib/server/install-theme.d.ts +42 -0
  98. package/src/lib/server/install-theme.js +313 -0
  99. package/src/lib/server/install-theme.js.map +1 -0
  100. package/src/lib/server/install.d.ts +27 -0
  101. package/src/lib/server/install.js +276 -0
  102. package/src/lib/server/install.js.map +1 -0
  103. package/src/lib/server/listing-versions.d.ts +26 -0
  104. package/src/lib/server/listing-versions.js +378 -0
  105. package/src/lib/server/listing-versions.js.map +1 -0
  106. package/src/lib/server/preview-image.d.ts +37 -0
  107. package/src/lib/server/preview-image.js +216 -0
  108. package/src/lib/server/preview-image.js.map +1 -0
  109. package/src/lib/server/provenance.d.ts +100 -0
  110. package/src/lib/server/provenance.js +118 -0
  111. package/src/lib/server/provenance.js.map +1 -0
  112. package/src/lib/server/publish-dataset-schema.d.ts +31 -0
  113. package/src/lib/server/publish-dataset-schema.js +202 -0
  114. package/src/lib/server/publish-dataset-schema.js.map +1 -0
  115. package/src/lib/server/publish-email-starter.d.ts +44 -0
  116. package/src/lib/server/publish-email-starter.js +231 -0
  117. package/src/lib/server/publish-email-starter.js.map +1 -0
  118. package/src/lib/server/publish-email-template.d.ts +33 -0
  119. package/src/lib/server/publish-email-template.js +211 -0
  120. package/src/lib/server/publish-email-template.js.map +1 -0
  121. package/src/lib/server/publish-layout.d.ts +33 -0
  122. package/src/lib/server/publish-layout.js +243 -0
  123. package/src/lib/server/publish-layout.js.map +1 -0
  124. package/src/lib/server/publish-plugin.d.ts +18 -0
  125. package/src/lib/server/publish-plugin.js +485 -0
  126. package/src/lib/server/publish-plugin.js.map +1 -0
  127. package/src/lib/server/publish-preconditions.d.ts +86 -0
  128. package/src/lib/server/publish-preconditions.js +160 -0
  129. package/src/lib/server/publish-preconditions.js.map +1 -0
  130. package/src/lib/server/publish-template.d.ts +27 -0
  131. package/src/lib/server/publish-template.js +215 -0
  132. package/src/lib/server/publish-template.js.map +1 -0
  133. package/src/lib/server/publish-theme.d.ts +38 -0
  134. package/src/lib/server/publish-theme.js +186 -0
  135. package/src/lib/server/publish-theme.js.map +1 -0
  136. package/src/lib/server/publish.d.ts +26 -0
  137. package/src/lib/server/publish.js +204 -0
  138. package/src/lib/server/publish.js.map +1 -0
  139. package/src/lib/server/publisher-profile-save.d.ts +30 -0
  140. package/src/lib/server/publisher-profile-save.js +226 -0
  141. package/src/lib/server/publisher-profile-save.js.map +1 -0
  142. package/src/lib/server/publisher-profile.d.ts +86 -0
  143. package/src/lib/server/publisher-profile.js +116 -0
  144. package/src/lib/server/publisher-profile.js.map +1 -0
  145. package/src/lib/server/purchase-entitlement.d.ts +83 -0
  146. package/src/lib/server/purchase-entitlement.js +118 -0
  147. package/src/lib/server/purchase-entitlement.js.map +1 -0
  148. package/src/lib/server/report.d.ts +32 -0
  149. package/src/lib/server/report.js +101 -0
  150. package/src/lib/server/report.js.map +1 -0
  151. package/src/lib/server/reviews.d.ts +41 -0
  152. package/src/lib/server/reviews.js +240 -0
  153. package/src/lib/server/reviews.js.map +1 -0
  154. package/src/lib/server/update-artifact.d.ts +19 -0
  155. package/src/lib/server/update-artifact.js +569 -0
  156. package/src/lib/server/update-artifact.js.map +1 -0
  157. package/src/lib/server/verification-request.d.ts +31 -0
  158. package/src/lib/server/verification-request.js +173 -0
  159. package/src/lib/server/verification-request.js.map +1 -0
  160. package/src/lib/server/version-stats.d.ts +202 -0
  161. package/src/lib/server/version-stats.js +305 -0
  162. package/src/lib/server/version-stats.js.map +1 -0
  163. package/src/lib/server.d.ts +30 -0
  164. package/src/lib/server.js +95 -0
  165. package/src/lib/server.js.map +1 -0
@@ -0,0 +1,485 @@
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 { checkEntitlement, checkPluginBundle, createResourceUid, MAX_PLUGIN_BUNDLE_BYTES, pluginArtifactPath, PLUGIN_VERIFIER_VERSION, validatePluginManifest } from "@aglyn/aglyn/server";
18
+ import { firebaseAdmin, getOrgForUser, notifyStaff } from "@aglyn/tenant-data-admin";
19
+ import { resolveOrgPermissions } from "@aglyn/tenant-runtime/org-permissions";
20
+ import { canActAsPublisher, resolvePublisherProfile } from "./publisher-profile.js";
21
+ import { publishPreconditionRefusal } from "./publish-preconditions.js";
22
+ import { attestationLabels, missingAttestations, missingAttestationSubjects, PUBLISHER_ATTESTATION } from "@aglyn/aglyn/app-utils/publisher-attestation";
23
+ import { createHash } from "crypto";
24
+ import { marketplacePriceRefusal, missingPublicListingContent, validateListingContent } from "../model/marketplace.js";
25
+ // Base64 bundle bodies: a small JS plugin bundle, capped generously.
26
+ const MAX_BUNDLE_BYTES = 5 * 1024 * 1024;
27
+ /**
28
+ * Publishes an executable plugin to the marketplace (AGL-45), per the AGL-43
29
+ * artifact pipeline — relocated from the console app route into the
30
+ * marketplace plugin (AGL-418); URL `/api/marketplace/publish-plugin` is
31
+ * preserved through the dispatcher. Runs server-side so validation
32
+ * (manifest schema, size caps) can't be bypassed: the bundle is
33
+ * content-addressed (sha256) and written IMMUTABLY to the isolated
34
+ * artifacts bucket, and the version entry records the hash the loader
35
+ * verifies before executing. Env-gated on `PLUGIN_ARTIFACTS_BUCKET` —
36
+ * 501 degrades like other platform infra. Requirements: publish
37
+ * permission, marketplace profile, Pro plan; paid listings need completed
38
+ * Stripe Connect onboarding.
39
+ */ const updateListingContent = async (req, res)=>{
40
+ var _ref, _req_headers_authorization, _req_body;
41
+ var _req_body1;
42
+ const listingId = String((_ref = (_req_body1 = req.body) == null ? void 0 : _req_body1.listingId) != null ? _ref : '');
43
+ if (!listingId) return res.status(400).json({
44
+ error: 'Missing listingId'
45
+ });
46
+ const authorization = String((_req_headers_authorization = req.headers.authorization) != null ? _req_headers_authorization : '');
47
+ const idToken = authorization.startsWith('Bearer ') ? authorization.slice('Bearer '.length) : undefined;
48
+ if (!idToken) return res.status(401).json({
49
+ error: 'Unauthenticated'
50
+ });
51
+ const verdict = validateListingContent((_req_body = req.body) != null ? _req_body : {});
52
+ if (!verdict.ok) return res.status(400).json({
53
+ error: verdict.error
54
+ });
55
+ try {
56
+ var _req_body2, _req_body3;
57
+ const decoded = await firebaseAdmin.app().auth().verifyIdToken(idToken);
58
+ const listingRef = firebaseAdmin.app().firestore().collection('marketplaceListings').doc(listingId);
59
+ const listing = (await listingRef.get()).data();
60
+ if (!listing || listing.deletedAt) {
61
+ return res.status(404).json({
62
+ error: 'Unknown listing'
63
+ });
64
+ }
65
+ // Org-owned listings (AGL-652): a uid never equals an org id, so the
66
+ // old identity comparison would lock publishers out of their own listing.
67
+ const isPublisher = await canActAsPublisher(firebaseAdmin.app().firestore(), decoded.uid, listing.profileId);
68
+ if (!isPublisher && decoded['staff'] !== true) {
69
+ return res.status(403).json({
70
+ error: 'Not your listing'
71
+ });
72
+ }
73
+ // Name and description are the whole first impression of a listing in
74
+ // browse, and until AGL-793 they were the only listing-content fields with
75
+ // no edit path — a typo in either could only be corrected by publishing a
76
+ // fake new version, which signals "there's an update" to every installer.
77
+ // Same caps the publish routes apply. An empty name is ignored rather than
78
+ // accepted: a nameless listing is unrecognisable in the catalogue.
79
+ const description = (_req_body2 = req.body) == null ? void 0 : _req_body2.description;
80
+ const displayName = (_req_body3 = req.body) == null ? void 0 : _req_body3.displayName;
81
+ const nextName = typeof displayName === 'string' ? displayName.trim().slice(0, 80) : '';
82
+ await listingRef.set(_extends({}, verdict.content, typeof description === 'string' ? {
83
+ description: description.slice(0, 500)
84
+ } : {}, nextName ? {
85
+ displayName: nextName
86
+ } : {}, {
87
+ updatedAt: firebaseAdmin.firestore.FieldValue.serverTimestamp()
88
+ }), {
89
+ merge: true
90
+ });
91
+ return res.status(200).json({
92
+ ok: true
93
+ });
94
+ } catch (error) {
95
+ console.error(error);
96
+ return res.status(500).json({
97
+ error: 'Listing update failed'
98
+ });
99
+ }
100
+ };
101
+ /**
102
+ * Flip a listing between private and public (AGL-968/994).
103
+ *
104
+ * Deliberately NOT a re-review. Per-version `reviewState` and the listing's
105
+ * `reviewStatus` are left exactly as they are: approval is a statement about
106
+ * specific bytes (AGL-966), and who may install them was never part of that
107
+ * statement. Sending an already-approved bundle back through the queue
108
+ * because its audience widened would only teach publishers to start public.
109
+ *
110
+ * Going public does require what a public listing needs — see
111
+ * {@link missingPublicListingContent}. Going private is unconditional: it
112
+ * only ever removes reach.
113
+ */ const setListingVisibility = async (req, res)=>{
114
+ var _ref, _req_headers_authorization;
115
+ var _req_body, _req_body1;
116
+ const listingId = String((_ref = (_req_body = req.body) == null ? void 0 : _req_body.listingId) != null ? _ref : '');
117
+ const visibility = ((_req_body1 = req.body) == null ? void 0 : _req_body1.visibility) === 'private' ? 'private' : 'public';
118
+ if (!listingId) return res.status(400).json({
119
+ error: 'Missing listingId'
120
+ });
121
+ const authorization = String((_req_headers_authorization = req.headers.authorization) != null ? _req_headers_authorization : '');
122
+ const idToken = authorization.startsWith('Bearer ') ? authorization.slice('Bearer '.length) : undefined;
123
+ if (!idToken) return res.status(401).json({
124
+ error: 'Unauthenticated'
125
+ });
126
+ try {
127
+ const decoded = await firebaseAdmin.app().auth().verifyIdToken(idToken);
128
+ const firestore = firebaseAdmin.app().firestore();
129
+ const listingRef = firestore.collection('marketplaceListings').doc(listingId);
130
+ const listing = (await listingRef.get()).data();
131
+ if (!listing || listing.deletedAt) {
132
+ return res.status(404).json({
133
+ error: 'Unknown listing'
134
+ });
135
+ }
136
+ const isPublisher = await canActAsPublisher(firestore, decoded.uid, listing.profileId);
137
+ if (!isPublisher && decoded['staff'] !== true) {
138
+ return res.status(403).json({
139
+ error: 'Not your listing'
140
+ });
141
+ }
142
+ if (visibility === 'public') {
143
+ const missing = missingPublicListingContent(listing);
144
+ if (missing.length) {
145
+ return res.status(400).json({
146
+ error: 'A marketplace listing needs ' + missing.join(', ') + '. Add them in Edit, then publish it.',
147
+ missing
148
+ });
149
+ }
150
+ }
151
+ await listingRef.set({
152
+ visibility,
153
+ updatedAt: firebaseAdmin.firestore.FieldValue.serverTimestamp()
154
+ }, {
155
+ merge: true
156
+ });
157
+ return res.status(200).json({
158
+ ok: true,
159
+ visibility
160
+ });
161
+ } catch (error) {
162
+ console.error(error);
163
+ return res.status(500).json({
164
+ error: 'Visibility change failed'
165
+ });
166
+ }
167
+ };
168
+ export const publishPluginHandler = async (req, res)=>{
169
+ var _req_body, _ref, _ref1, _ref2, _ref3, _ref4, _ref5, _headers_authorization;
170
+ var _req_body1, _req_body2;
171
+ if (req.method !== 'POST') {
172
+ return res.status(405).json({
173
+ error: 'Method not allowed'
174
+ });
175
+ }
176
+ // Content-only listing edits (AGL-430): publishers refresh the docs the
177
+ // detail page renders (readme/screenshots/links/…) without shipping a
178
+ // new bundle. Publisher-or-staff, validated exactly like publish.
179
+ if (((_req_body1 = req.body) == null ? void 0 : _req_body1.action) === 'update-listing') {
180
+ return updateListingContent(req, res);
181
+ }
182
+ // Private ⇄ public (AGL-968/994), without shipping a new bundle.
183
+ if (((_req_body2 = req.body) == null ? void 0 : _req_body2.action) === 'set-visibility') {
184
+ return setListingVisibility(req, res);
185
+ }
186
+ const body = (_req_body = req.body) != null ? _req_body : {};
187
+ const headers = req.headers;
188
+ const displayName = String((_ref = body == null ? void 0 : body.displayName) != null ? _ref : '').slice(0, 80);
189
+ const description = String((_ref1 = body == null ? void 0 : body.description) != null ? _ref1 : '').slice(0, 500);
190
+ const category = String((_ref2 = body == null ? void 0 : body.category) != null ? _ref2 : '').slice(0, 40);
191
+ const changelog = String((_ref3 = body == null ? void 0 : body.changelog) != null ? _ref3 : '').slice(0, 1000);
192
+ const priceUsd = Math.round(Number((_ref4 = body == null ? void 0 : body.priceUsd) != null ? _ref4 : 0)) || 0;
193
+ const bundleBase64 = String((_ref5 = body == null ? void 0 : body.bundle) != null ? _ref5 : '');
194
+ // Listing content (AGL-430): optional publisher docs for the detail page.
195
+ const contentVerdict = validateListingContent(body != null ? body : {});
196
+ if (!contentVerdict.ok) {
197
+ return res.status(400).json({
198
+ error: contentVerdict.error
199
+ });
200
+ }
201
+ // The price floor and ceiling, from ONE validator (AGL-2343): a paid listing
202
+ // under `MARKETPLACE_MIN_PRICE_USD` loses money on every sale, because
203
+ // marketplace checkout is a destination charge whose Stripe fee is debited
204
+ // from the platform's balance. Also enforced in `publishPreconditionRefusal`
205
+ // below, so a door that drops this line is still covered.
206
+ const priceRefusal = marketplacePriceRefusal(priceUsd);
207
+ if (priceRefusal) {
208
+ return res.status(400).json({
209
+ error: priceRefusal
210
+ });
211
+ }
212
+ if (!displayName.trim() || !bundleBase64) {
213
+ return res.status(400).json({
214
+ error: 'Missing displayName or bundle'
215
+ });
216
+ }
217
+ const validation = validatePluginManifest(body == null ? void 0 : body.manifest);
218
+ if (validation.ok === false) {
219
+ return res.status(422).json({
220
+ error: validation.error
221
+ });
222
+ }
223
+ const manifest = validation.manifest;
224
+ const authorization = (_headers_authorization = headers.authorization) != null ? _headers_authorization : '';
225
+ const idToken = authorization.startsWith('Bearer ') ? authorization.slice('Bearer '.length) : undefined;
226
+ if (!idToken) return res.status(401).json({
227
+ error: 'Unauthenticated'
228
+ });
229
+ try {
230
+ var _ref6, _membership_orgId, _ref7, _ref8, _manifest_contributes;
231
+ var _manifest_capabilities, _req_body3, _contentVerdict_content;
232
+ const decoded = await firebaseAdmin.app().auth().verifyIdToken(idToken);
233
+ // Act on the org the client is in (AGL-868), not a guessed first-org — a
234
+ // member can belong to several, and the plugin publishes under one of them.
235
+ const requestedOrgId = String((_ref6 = body == null ? void 0 : body.orgId) != null ? _ref6 : '').trim() || undefined;
236
+ const membership = await resolveOrgPermissions(decoded.uid, {
237
+ orgId: requestedOrgId
238
+ });
239
+ if (!membership.permissions.publishToMarketplace) {
240
+ return res.status(403).json({
241
+ error: 'Your organization role does not allow publishing to the marketplace'
242
+ });
243
+ }
244
+ const firestore = firebaseAdmin.app().firestore();
245
+ // Plan gate rides the acting org's doc (AGL-238/868).
246
+ const orgForUser = await getOrgForUser(decoded.uid, (_membership_orgId = membership.orgId) != null ? _membership_orgId : requestedOrgId);
247
+ const org = (_ref7 = orgForUser == null ? void 0 : orgForUser.org) != null ? _ref7 : {};
248
+ if (!checkEntitlement(org, 'marketplaceSelling')) {
249
+ return res.status(403).json({
250
+ error: 'Publishing to the marketplace requires a Pro plan'
251
+ });
252
+ }
253
+ // Publish rate limit (AGL-437): a runaway or abusive publisher can't
254
+ // flood the artifacts bucket/review queue — 20 publishes per UTC day.
255
+ // Scoped to the publishing ORG (AGL-652): per-user it would multiply by
256
+ // headcount, letting a big team flood the queue 20 publishes at a time.
257
+ const dayKey = new Date().toISOString().slice(0, 10);
258
+ if (!(orgForUser == null ? void 0 : orgForUser.orgId)) {
259
+ return res.status(409).json({
260
+ error: 'No publishing organization'
261
+ });
262
+ }
263
+ const limiterRef = firestore.collection('publisherProfiles').doc(orgForUser.orgId).collection('meta').doc('publishWindow');
264
+ const allowed = await firestore.runTransaction(async (transaction)=>{
265
+ var _data, _window_count;
266
+ const window = (_data = (await transaction.get(limiterRef)).data()) != null ? _data : {};
267
+ const count = window.dayKey === dayKey ? Number((_window_count = window.count) != null ? _window_count : 0) : 0;
268
+ if (count >= 20) return false;
269
+ transaction.set(limiterRef, {
270
+ dayKey,
271
+ count: count + 1
272
+ });
273
+ return true;
274
+ });
275
+ if (!allowed) {
276
+ return res.status(429).json({
277
+ error: 'Daily publish limit reached — try again tomorrow'
278
+ });
279
+ }
280
+ const publisher = await resolvePublisherProfile(firestore, orgForUser.orgId);
281
+ // Profile, payouts AND the publisher agreement, in one gate
282
+ // (AGL-2282) — see `publishPreconditionRefusal`.
283
+ const refusal = publishPreconditionRefusal(publisher, {
284
+ priceUsd,
285
+ sells: 'plugins'
286
+ });
287
+ if (refusal) return res.status(refusal.status).json(refusal.body);
288
+ const bundle = Buffer.from(bundleBase64, 'base64');
289
+ if (!bundle.length || bundle.length > MAX_BUNDLE_BYTES) {
290
+ return res.status(413).json({
291
+ error: 'Bundle is empty or too large'
292
+ });
293
+ }
294
+ // Static verification (AGL-426): the same checks as the local
295
+ // `verify-plugin-bundle.mjs`, so a bundle that passes there publishes
296
+ // here. Entry exports, self-containment, forbidden APIs, size.
297
+ //
298
+ // The manifest goes in too (AGL-964): the checker diffs the bundle's
299
+ // network calls against the origins declared here, and a call to an
300
+ // origin the manifest omits is refused at publish rather than silently
301
+ // blocked by the CSP at runtime, where the publisher would meet it as a
302
+ // mystery in production.
303
+ //
304
+ // And what register() registers is compared with the manifest's
305
+ // `contributes` (AGL-3116), which a new version must carry whenever it
306
+ // registers anything: the loaders place a plugin by that declaration
307
+ // alone, so a registration it omits is a surface that never loads.
308
+ const verification = checkPluginBundle(bundle.toString('utf8'), {
309
+ maxBytes: MAX_PLUGIN_BUNDLE_BYTES,
310
+ declaredNetwork: (_ref8 = (_manifest_capabilities = manifest.capabilities) == null ? void 0 : _manifest_capabilities.network) != null ? _ref8 : [],
311
+ declaredContributions: (_manifest_contributes = manifest.contributes) != null ? _manifest_contributes : null,
312
+ requireContributions: true
313
+ });
314
+ if (!verification.ok) {
315
+ return res.status(422).json({
316
+ error: 'Bundle failed verification',
317
+ problems: verification.problems
318
+ });
319
+ }
320
+ const sha256 = createHash('sha256').update(new Uint8Array(bundle)).digest('hex');
321
+ // Isolated artifacts bucket (AGL-43 §3). Without it configured we
322
+ // refuse rather than store executable code in the app bucket.
323
+ const artifactsBucket = process.env.PLUGIN_ARTIFACTS_BUCKET;
324
+ if (!artifactsBucket) {
325
+ return res.status(501).json({
326
+ error: 'Plugin publishing is not configured (missing ' + 'PLUGIN_ARTIFACTS_BUCKET).'
327
+ });
328
+ }
329
+ // One listing per publisher+plugin id: re-publishing bumps the version.
330
+ const existing = await firestore.collection('marketplaceListings').where('profileId', '==', publisher.orgId).where('pluginId', '==', manifest.id).limit(1).get();
331
+ const listingRef = existing.empty ? firestore.collection('marketplaceListings').doc(createResourceUid()) : existing.docs[0].ref;
332
+ // Pre-submission attestation (AGL-969). Blocks the publish rather than
333
+ // warning, because the whole point is that these answers exist BEFORE a
334
+ // reviewer spends time on the bundle.
335
+ //
336
+ // Whether this is an update is decided here, from whether a listing
337
+ // already exists — never from a client flag, which would make "this is
338
+ // my first version" a way to skip the changelog item.
339
+ const ticked = (Array.isArray(body == null ? void 0 : body.attestation) ? body.attestation.map((id)=>String(id)) : []).filter((id)=>PUBLISHER_ATTESTATION.some((item)=>item.id === id));
340
+ const isUpdate = !existing.empty;
341
+ const missing = missingAttestations(ticked, isUpdate);
342
+ if (missing.length) {
343
+ return res.status(428).json({
344
+ error: 'Confirm the pre-submission checklist before publishing: ' + attestationLabels(missing).join('; '),
345
+ missingAttestations: missing
346
+ });
347
+ }
348
+ // An attestation needs something to be about (AGL-1076). `repository`
349
+ // was confirmable in a form with no repository field, so listings
350
+ // reached review carrying a signed claim that a URL we never collected
351
+ // was public and matched — and the reviewer's first link went nowhere.
352
+ //
353
+ // Checked against the SUBMISSION, not the listing doc: a first publish
354
+ // has no listing, and on an update the tick is a statement about what
355
+ // this publish declares. `contentVerdict.content` is the normalized
356
+ // value about to be stored, so the gate and the write cannot disagree.
357
+ const missingSubjects = missingAttestationSubjects(contentVerdict.content, isUpdate);
358
+ if (missingSubjects.length) {
359
+ return res.status(428).json({
360
+ error: 'These are confirmed above but not filled in: ' + missingSubjects.map((subject)=>subject.label).join('; '),
361
+ missingAttestationSubjects: missingSubjects.map((subject)=>subject.field)
362
+ });
363
+ }
364
+ // Immutable content-addressed write — a new build is a new object, so a
365
+ // consumer's pinned version can never be overwritten underneath it.
366
+ const objectPath = pluginArtifactPath(listingRef.id, manifest.version, sha256);
367
+ const bucket = firebaseAdmin.app().storage().bucket(artifactsBucket);
368
+ const file = bucket.file(objectPath);
369
+ const [alreadyStored] = await file.exists();
370
+ if (!alreadyStored) {
371
+ await file.save(bundle, {
372
+ contentType: 'application/javascript',
373
+ metadata: {
374
+ cacheControl: 'public, max-age=31536000, immutable'
375
+ }
376
+ });
377
+ }
378
+ const now = firebaseAdmin.firestore.FieldValue.serverTimestamp();
379
+ await listingRef.set(_extends({
380
+ type: 'plugin',
381
+ artifactType: 'plugin',
382
+ profileId: publisher.orgId,
383
+ pluginId: manifest.id,
384
+ displayName: displayName.trim()
385
+ }, description.trim() && {
386
+ description: description.trim()
387
+ }, category.trim() && {
388
+ category: category.trim()
389
+ }, {
390
+ priceUsd
391
+ }, contentVerdict.content, {
392
+ latestVersion: manifest.version,
393
+ // Mirror of `pluginVersions/{latestVersion}.reviewState`, for the
394
+ // marketplace (AGL-1121). Browse reads listings directly and cannot
395
+ // afford a version subdoc read per card, so the newest bytes' verdict
396
+ // has to live here to be shown at all.
397
+ //
398
+ // Reset to 'pending' on EVERY publish, including a bump on a listing
399
+ // that staff already verified. That is the whole point: the listing's
400
+ // `reviewStatus` deliberately survives a version bump, so before this
401
+ // the marketplace badged brand-new, unreviewed bytes as reviewed.
402
+ latestVersionReviewState: 'pending',
403
+ deletedAt: null
404
+ }, existing.empty && {
405
+ createdAt: now,
406
+ reviewStatus: 'submitted'
407
+ }, existing.empty && {
408
+ visibility: ((_req_body3 = req.body) == null ? void 0 : _req_body3.visibility) === 'private' ? 'private' : 'public'
409
+ }, {
410
+ updatedAt: now
411
+ }), {
412
+ merge: true
413
+ });
414
+ // Version snapshots are server-only; the loader reads sha256 + manifest
415
+ // to verify integrity and stamp CSP before executing.
416
+ await listingRef.collection('pluginVersions').doc(manifest.version).set(_extends({
417
+ version: manifest.version,
418
+ sha256,
419
+ objectPath,
420
+ manifest
421
+ }, changelog.trim() && {
422
+ changelog: changelog.trim()
423
+ }, ((_contentVerdict_content = contentVerdict.content) == null ? void 0 : _contentVerdict_content.repositoryUrl) && {
424
+ repositoryUrl: contentVerdict.content.repositoryUrl
425
+ }, {
426
+ publishedAt: now,
427
+ // What the publisher stated about these bytes (AGL-969). Same shape
428
+ // as the staff `reviewChecklist` and pinned to the same sha256, so
429
+ // a republish under this version string re-asks rather than
430
+ // inheriting an attestation made about code that has since changed.
431
+ //
432
+ // `by` is the uid that submitted, not the org: takedown and removal
433
+ // are defensible because a named person said this on a date, and an
434
+ // org id names nobody.
435
+ publisherAttestation: Object.fromEntries(ticked.map((id)=>[
436
+ id,
437
+ {
438
+ by: decoded.uid,
439
+ at: now,
440
+ sha256
441
+ }
442
+ ])),
443
+ // Every version starts UNREVIEWED (AGL-966). This doc is written
444
+ // with .set() and no merge, so a republish of the same version
445
+ // string also resets it — new bytes, new review, by construction.
446
+ reviewState: 'pending',
447
+ // Keep the verdict we just computed (AGL-962). The bundle is
448
+ // immutable and content-addressed, so this result holds for as long
449
+ // as the checker does — the review page reads it instead of
450
+ // re-downloading a megabyte per view. `verifierVersion` is what
451
+ // makes that safe: bump PLUGIN_VERIFIER_VERSION and every stored
452
+ // verdict is recomputed on next read.
453
+ verification: {
454
+ ok: verification.ok,
455
+ problems: verification.problems,
456
+ sha256,
457
+ verifierVersion: PLUGIN_VERIFIER_VERSION,
458
+ checkedAt: now
459
+ }
460
+ }));
461
+ // Tell staff a version is waiting (AGL-970). Nothing announced a
462
+ // submission before this: reviewers had to notice a new row by
463
+ // visiting the queue, and an UPDATE to an already-listed plugin
464
+ // produced no row at all. Persistent per-user notifications, so it
465
+ // survives a refresh and stays until someone acts on it.
466
+ await notifyStaff({
467
+ type: 'marketplace.review',
468
+ title: `${displayName.trim()} v${manifest.version} needs review`,
469
+ body: existing.empty ? 'A new plugin was submitted to the marketplace.' : 'A new version was published. The previously approved version ' + 'keeps installing until this one is reviewed.',
470
+ link: `/admin/plugin-reviews/${listingRef.id}`
471
+ }).catch(()=>undefined);
472
+ return res.status(200).json({
473
+ listingId: listingRef.id,
474
+ version: manifest.version,
475
+ sha256
476
+ });
477
+ } catch (error) {
478
+ console.error(error);
479
+ return res.status(500).json({
480
+ error: 'Publish failed'
481
+ });
482
+ }
483
+ };
484
+
485
+ //# sourceMappingURL=publish-plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/server/publish-plugin.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 checkEntitlement,\n checkPluginBundle,\n createResourceUid,\n MAX_PLUGIN_BUNDLE_BYTES,\n type PluginApiHandler,\n pluginArtifactPath,\n PLUGIN_VERIFIER_VERSION,\n validatePluginManifest,\n} from '@aglyn/aglyn/server'\nimport {\n firebaseAdmin,\n getOrgForUser,\n notifyStaff,\n} from '@aglyn/tenant-data-admin'\nimport { resolveOrgPermissions } from '@aglyn/tenant-runtime/org-permissions'\nimport {\n canActAsPublisher,\n resolvePublisherProfile,\n} from './publisher-profile'\nimport { publishPreconditionRefusal } from './publish-preconditions'\nimport {\n attestationLabels,\n missingAttestations,\n missingAttestationSubjects,\n PUBLISHER_ATTESTATION,\n} from '@aglyn/aglyn/app-utils/publisher-attestation'\nimport { createHash } from 'crypto'\nimport {\n marketplacePriceRefusal,\n missingPublicListingContent,\n validateListingContent,\n} from '../model/marketplace'\n\n// Base64 bundle bodies: a small JS plugin bundle, capped generously.\nconst MAX_BUNDLE_BYTES = 5 * 1024 * 1024\n\n/**\n * Publishes an executable plugin to the marketplace (AGL-45), per the AGL-43\n * artifact pipeline — relocated from the console app route into the\n * marketplace plugin (AGL-418); URL `/api/marketplace/publish-plugin` is\n * preserved through the dispatcher. Runs server-side so validation\n * (manifest schema, size caps) can't be bypassed: the bundle is\n * content-addressed (sha256) and written IMMUTABLY to the isolated\n * artifacts bucket, and the version entry records the hash the loader\n * verifies before executing. Env-gated on `PLUGIN_ARTIFACTS_BUCKET` —\n * 501 degrades like other platform infra. Requirements: publish\n * permission, marketplace profile, Pro plan; paid listings need completed\n * Stripe Connect onboarding.\n */\nconst updateListingContent: PluginApiHandler = async (req, res) => {\n const listingId = String(req.body?.listingId ?? '')\n if (!listingId) return res.status(400).json({ error: 'Missing listingId' })\n const authorization = String(req.headers.authorization ?? '')\n const idToken = authorization.startsWith('Bearer ')\n ? authorization.slice('Bearer '.length)\n : undefined\n if (!idToken) return res.status(401).json({ error: 'Unauthenticated' })\n const verdict = validateListingContent(req.body ?? {})\n if (!verdict.ok) return res.status(400).json({ error: verdict.error })\n try {\n const decoded = await firebaseAdmin.app().auth().verifyIdToken(idToken)\n const listingRef = firebaseAdmin\n .app()\n .firestore()\n .collection('marketplaceListings')\n .doc(listingId)\n const listing = (await listingRef.get()).data()\n if (!listing || listing.deletedAt) {\n return res.status(404).json({ error: 'Unknown listing' })\n }\n // Org-owned listings (AGL-652): a uid never equals an org id, so the\n // old identity comparison would lock publishers out of their own listing.\n const isPublisher = await canActAsPublisher(\n firebaseAdmin.app().firestore(),\n decoded.uid,\n listing.profileId,\n )\n if (!isPublisher && decoded['staff'] !== true) {\n return res.status(403).json({ error: 'Not your listing' })\n }\n // Name and description are the whole first impression of a listing in\n // browse, and until AGL-793 they were the only listing-content fields with\n // no edit path — a typo in either could only be corrected by publishing a\n // fake new version, which signals \"there's an update\" to every installer.\n // Same caps the publish routes apply. An empty name is ignored rather than\n // accepted: a nameless listing is unrecognisable in the catalogue.\n const description = req.body?.description\n const displayName = req.body?.displayName\n const nextName =\n typeof displayName === 'string' ? displayName.trim().slice(0, 80) : ''\n await listingRef.set(\n {\n ...verdict.content,\n ...(typeof description === 'string'\n ? { description: description.slice(0, 500) }\n : {}),\n ...(nextName ? { displayName: nextName } : {}),\n updatedAt: firebaseAdmin.firestore.FieldValue.serverTimestamp(),\n },\n { merge: true },\n )\n return res.status(200).json({ ok: true })\n } catch (error) {\n console.error(error)\n return res.status(500).json({ error: 'Listing update failed' })\n }\n}\n\n/**\n * Flip a listing between private and public (AGL-968/994).\n *\n * Deliberately NOT a re-review. Per-version `reviewState` and the listing's\n * `reviewStatus` are left exactly as they are: approval is a statement about\n * specific bytes (AGL-966), and who may install them was never part of that\n * statement. Sending an already-approved bundle back through the queue\n * because its audience widened would only teach publishers to start public.\n *\n * Going public does require what a public listing needs — see\n * {@link missingPublicListingContent}. Going private is unconditional: it\n * only ever removes reach.\n */\nconst setListingVisibility: PluginApiHandler = async (req, res) => {\n const listingId = String(req.body?.listingId ?? '')\n const visibility = req.body?.visibility === 'private' ? 'private' : 'public'\n if (!listingId) return res.status(400).json({ error: 'Missing listingId' })\n const authorization = String(req.headers.authorization ?? '')\n const idToken = authorization.startsWith('Bearer ')\n ? authorization.slice('Bearer '.length)\n : undefined\n if (!idToken) return res.status(401).json({ error: 'Unauthenticated' })\n try {\n const decoded = await firebaseAdmin.app().auth().verifyIdToken(idToken)\n const firestore = firebaseAdmin.app().firestore()\n const listingRef = firestore.collection('marketplaceListings').doc(listingId)\n const listing = (await listingRef.get()).data()\n if (!listing || listing.deletedAt) {\n return res.status(404).json({ error: 'Unknown listing' })\n }\n const isPublisher = await canActAsPublisher(\n firestore,\n decoded.uid,\n listing.profileId,\n )\n if (!isPublisher && decoded['staff'] !== true) {\n return res.status(403).json({ error: 'Not your listing' })\n }\n if (visibility === 'public') {\n const missing = missingPublicListingContent(listing)\n if (missing.length) {\n return res.status(400).json({\n error:\n 'A marketplace listing needs ' +\n missing.join(', ') +\n '. Add them in Edit, then publish it.',\n missing,\n })\n }\n }\n await listingRef.set(\n {\n visibility,\n updatedAt: firebaseAdmin.firestore.FieldValue.serverTimestamp(),\n },\n { merge: true },\n )\n return res.status(200).json({ ok: true, visibility })\n } catch (error) {\n console.error(error)\n return res.status(500).json({ error: 'Visibility change failed' })\n }\n}\n\nexport const publishPluginHandler: PluginApiHandler = async (req, res) => {\n if (req.method !== 'POST') {\n return res.status(405).json({ error: 'Method not allowed' })\n }\n // Content-only listing edits (AGL-430): publishers refresh the docs the\n // detail page renders (readme/screenshots/links/…) without shipping a\n // new bundle. Publisher-or-staff, validated exactly like publish.\n if (req.body?.action === 'update-listing') {\n return updateListingContent(req, res)\n }\n // Private ⇄ public (AGL-968/994), without shipping a new bundle.\n if (req.body?.action === 'set-visibility') {\n return setListingVisibility(req, res)\n }\n const body = req.body ?? {}\n const headers = req.headers as Partial<Record<string, string>>\n const displayName = String(body?.displayName ?? '').slice(0, 80)\n const description = String(body?.description ?? '').slice(0, 500)\n const category = String(body?.category ?? '').slice(0, 40)\n const changelog = String(body?.changelog ?? '').slice(0, 1000)\n const priceUsd = Math.round(Number(body?.priceUsd ?? 0)) || 0\n const bundleBase64 = String(body?.bundle ?? '')\n // Listing content (AGL-430): optional publisher docs for the detail page.\n const contentVerdict = validateListingContent(body ?? {})\n if (!contentVerdict.ok) {\n return res.status(400).json({ error: contentVerdict.error })\n }\n // The price floor and ceiling, from ONE validator (AGL-2343): a paid listing\n // under `MARKETPLACE_MIN_PRICE_USD` loses money on every sale, because\n // marketplace checkout is a destination charge whose Stripe fee is debited\n // from the platform's balance. Also enforced in `publishPreconditionRefusal`\n // below, so a door that drops this line is still covered.\n const priceRefusal = marketplacePriceRefusal(priceUsd)\n if (priceRefusal) {\n return res.status(400).json({ error: priceRefusal })\n }\n if (!displayName.trim() || !bundleBase64) {\n return res.status(400).json({ error: 'Missing displayName or bundle' })\n }\n\n const validation = validatePluginManifest(body?.manifest)\n if (validation.ok === false) {\n return res.status(422).json({ error: validation.error })\n }\n const manifest = validation.manifest\n\n const authorization = headers.authorization ?? ''\n const idToken = authorization.startsWith('Bearer ')\n ? authorization.slice('Bearer '.length)\n : undefined\n if (!idToken) return res.status(401).json({ error: 'Unauthenticated' })\n\n try {\n const decoded = await firebaseAdmin.app().auth().verifyIdToken(idToken)\n // Act on the org the client is in (AGL-868), not a guessed first-org — a\n // member can belong to several, and the plugin publishes under one of them.\n const requestedOrgId = String(body?.orgId ?? '').trim() || undefined\n const membership = await resolveOrgPermissions(decoded.uid, {\n orgId: requestedOrgId,\n })\n if (!membership.permissions.publishToMarketplace) {\n return res.status(403).json({\n error:\n 'Your organization role does not allow publishing to the marketplace',\n })\n }\n const firestore = firebaseAdmin.app().firestore()\n\n // Plan gate rides the acting org's doc (AGL-238/868).\n const orgForUser = await getOrgForUser(\n decoded.uid,\n membership.orgId ?? requestedOrgId,\n )\n const org = orgForUser?.org ?? {}\n if (!checkEntitlement(org, 'marketplaceSelling')) {\n return res\n .status(403)\n .json({ error: 'Publishing to the marketplace requires a Pro plan' })\n }\n\n // Publish rate limit (AGL-437): a runaway or abusive publisher can't\n // flood the artifacts bucket/review queue — 20 publishes per UTC day.\n // Scoped to the publishing ORG (AGL-652): per-user it would multiply by\n // headcount, letting a big team flood the queue 20 publishes at a time.\n const dayKey = new Date().toISOString().slice(0, 10)\n if (!orgForUser?.orgId) {\n return res.status(409).json({ error: 'No publishing organization' })\n }\n const limiterRef = firestore\n .collection('publisherProfiles')\n .doc(orgForUser.orgId)\n .collection('meta')\n .doc('publishWindow')\n const allowed = await firestore.runTransaction(async (transaction) => {\n const window = (await transaction.get(limiterRef)).data() ?? {}\n const count = window.dayKey === dayKey ? Number(window.count ?? 0) : 0\n if (count >= 20) return false\n transaction.set(limiterRef, { dayKey, count: count + 1 })\n return true\n })\n if (!allowed) {\n return res\n .status(429)\n .json({ error: 'Daily publish limit reached — try again tomorrow' })\n }\n\n const publisher = await resolvePublisherProfile(firestore, orgForUser.orgId)\n // Profile, payouts AND the publisher agreement, in one gate\n // (AGL-2282) — see `publishPreconditionRefusal`.\n const refusal = publishPreconditionRefusal(publisher, {\n priceUsd,\n sells: 'plugins',\n })\n if (refusal) return res.status(refusal.status).json(refusal.body)\n\n const bundle = Buffer.from(bundleBase64, 'base64')\n if (!bundle.length || bundle.length > MAX_BUNDLE_BYTES) {\n return res.status(413).json({ error: 'Bundle is empty or too large' })\n }\n // Static verification (AGL-426): the same checks as the local\n // `verify-plugin-bundle.mjs`, so a bundle that passes there publishes\n // here. Entry exports, self-containment, forbidden APIs, size.\n //\n // The manifest goes in too (AGL-964): the checker diffs the bundle's\n // network calls against the origins declared here, and a call to an\n // origin the manifest omits is refused at publish rather than silently\n // blocked by the CSP at runtime, where the publisher would meet it as a\n // mystery in production.\n //\n // And what register() registers is compared with the manifest's\n // `contributes` (AGL-3116), which a new version must carry whenever it\n // registers anything: the loaders place a plugin by that declaration\n // alone, so a registration it omits is a surface that never loads.\n const verification = checkPluginBundle(bundle.toString('utf8'), {\n maxBytes: MAX_PLUGIN_BUNDLE_BYTES,\n declaredNetwork: manifest.capabilities?.network ?? [],\n declaredContributions: manifest.contributes ?? null,\n requireContributions: true,\n })\n if (!verification.ok) {\n return res.status(422).json({\n error: 'Bundle failed verification',\n problems: verification.problems,\n })\n }\n const sha256 = createHash('sha256')\n .update(new Uint8Array(bundle))\n .digest('hex')\n\n // Isolated artifacts bucket (AGL-43 §3). Without it configured we\n // refuse rather than store executable code in the app bucket.\n const artifactsBucket = process.env.PLUGIN_ARTIFACTS_BUCKET\n if (!artifactsBucket) {\n return res.status(501).json({\n error:\n 'Plugin publishing is not configured (missing ' +\n 'PLUGIN_ARTIFACTS_BUCKET).',\n })\n }\n\n // One listing per publisher+plugin id: re-publishing bumps the version.\n const existing = await firestore\n .collection('marketplaceListings')\n .where('profileId', '==', publisher.orgId)\n .where('pluginId', '==', manifest.id)\n .limit(1)\n .get()\n const listingRef = existing.empty\n ? firestore.collection('marketplaceListings').doc(createResourceUid())\n : existing.docs[0].ref\n\n // Pre-submission attestation (AGL-969). Blocks the publish rather than\n // warning, because the whole point is that these answers exist BEFORE a\n // reviewer spends time on the bundle.\n //\n // Whether this is an update is decided here, from whether a listing\n // already exists — never from a client flag, which would make \"this is\n // my first version\" a way to skip the changelog item.\n const ticked = (\n Array.isArray(body?.attestation)\n ? body.attestation.map((id: unknown) => String(id))\n : []\n ).filter((id: string) =>\n PUBLISHER_ATTESTATION.some((item) => item.id === id),\n )\n const isUpdate = !existing.empty\n const missing = missingAttestations(ticked, isUpdate)\n if (missing.length) {\n return res.status(428).json({\n error:\n 'Confirm the pre-submission checklist before publishing: ' +\n attestationLabels(missing).join('; '),\n missingAttestations: missing,\n })\n }\n\n // An attestation needs something to be about (AGL-1076). `repository`\n // was confirmable in a form with no repository field, so listings\n // reached review carrying a signed claim that a URL we never collected\n // was public and matched — and the reviewer's first link went nowhere.\n //\n // Checked against the SUBMISSION, not the listing doc: a first publish\n // has no listing, and on an update the tick is a statement about what\n // this publish declares. `contentVerdict.content` is the normalized\n // value about to be stored, so the gate and the write cannot disagree.\n const missingSubjects = missingAttestationSubjects(\n contentVerdict.content as Record<string, unknown>,\n isUpdate,\n )\n if (missingSubjects.length) {\n return res.status(428).json({\n error:\n 'These are confirmed above but not filled in: ' +\n missingSubjects.map((subject) => subject.label).join('; '),\n missingAttestationSubjects: missingSubjects.map(\n (subject) => subject.field,\n ),\n })\n }\n\n // Immutable content-addressed write — a new build is a new object, so a\n // consumer's pinned version can never be overwritten underneath it.\n const objectPath = pluginArtifactPath(\n listingRef.id,\n manifest.version,\n sha256,\n )\n const bucket = firebaseAdmin.app().storage().bucket(artifactsBucket)\n const file = bucket.file(objectPath)\n const [alreadyStored] = await file.exists()\n if (!alreadyStored) {\n await file.save(bundle, {\n contentType: 'application/javascript',\n metadata: {\n cacheControl: 'public, max-age=31536000, immutable',\n },\n })\n }\n\n const now = firebaseAdmin.firestore.FieldValue.serverTimestamp()\n await listingRef.set(\n {\n type: 'plugin',\n artifactType: 'plugin',\n profileId: publisher.orgId,\n pluginId: manifest.id,\n displayName: displayName.trim(),\n ...(description.trim() && { description: description.trim() }),\n ...(category.trim() && { category: category.trim() }),\n priceUsd,\n ...contentVerdict.content,\n latestVersion: manifest.version,\n // Mirror of `pluginVersions/{latestVersion}.reviewState`, for the\n // marketplace (AGL-1121). Browse reads listings directly and cannot\n // afford a version subdoc read per card, so the newest bytes' verdict\n // has to live here to be shown at all.\n //\n // Reset to 'pending' on EVERY publish, including a bump on a listing\n // that staff already verified. That is the whole point: the listing's\n // `reviewStatus` deliberately survives a version bump, so before this\n // the marketplace badged brand-new, unreviewed bytes as reviewed.\n latestVersionReviewState: 'pending',\n deletedAt: null,\n // Review queue (AGL-432): first publish enters as 'submitted';\n // version bumps keep whatever status staff granted — which is safe\n // now that installs resolve the newest APPROVED version rather than\n // `latestVersion` (AGL-966). A listed plugin stays listed on its\n // reviewed bytes while the new version waits in the queue.\n ...(existing.empty && { createdAt: now, reviewStatus: 'submitted' }),\n // Private plugins never reach the marketplace but take the same\n // review path (AGL-968). Only settable on first publish; changing\n // visibility later is a separate, deliberate action.\n ...(existing.empty && {\n visibility: req.body?.visibility === 'private' ? 'private' : 'public',\n }),\n updatedAt: now,\n },\n { merge: true },\n )\n // Version snapshots are server-only; the loader reads sha256 + manifest\n // to verify integrity and stamp CSP before executing.\n await listingRef\n .collection('pluginVersions')\n .doc(manifest.version)\n .set({\n version: manifest.version,\n sha256,\n objectPath,\n manifest,\n ...(changelog.trim() && { changelog: changelog.trim() }),\n // The repository as declared FOR THESE BYTES (AGL-1076). The listing\n // carries whatever the latest publish said, and a publisher may move\n // or rename a repo between versions — a reviewer looking at v1.0.2\n // needs the link that was attested against v1.0.2's sha256, which is\n // the same reasoning that pins the attestation itself.\n ...(contentVerdict.content?.repositoryUrl && {\n repositoryUrl: contentVerdict.content.repositoryUrl,\n }),\n publishedAt: now,\n // What the publisher stated about these bytes (AGL-969). Same shape\n // as the staff `reviewChecklist` and pinned to the same sha256, so\n // a republish under this version string re-asks rather than\n // inheriting an attestation made about code that has since changed.\n //\n // `by` is the uid that submitted, not the org: takedown and removal\n // are defensible because a named person said this on a date, and an\n // org id names nobody.\n publisherAttestation: Object.fromEntries(\n ticked.map((id: string) => [id, { by: decoded.uid, at: now, sha256 }]),\n ),\n // Every version starts UNREVIEWED (AGL-966). This doc is written\n // with .set() and no merge, so a republish of the same version\n // string also resets it — new bytes, new review, by construction.\n reviewState: 'pending',\n // Keep the verdict we just computed (AGL-962). The bundle is\n // immutable and content-addressed, so this result holds for as long\n // as the checker does — the review page reads it instead of\n // re-downloading a megabyte per view. `verifierVersion` is what\n // makes that safe: bump PLUGIN_VERIFIER_VERSION and every stored\n // verdict is recomputed on next read.\n verification: {\n ok: verification.ok,\n problems: verification.problems,\n sha256,\n verifierVersion: PLUGIN_VERIFIER_VERSION,\n checkedAt: now,\n },\n })\n\n // Tell staff a version is waiting (AGL-970). Nothing announced a\n // submission before this: reviewers had to notice a new row by\n // visiting the queue, and an UPDATE to an already-listed plugin\n // produced no row at all. Persistent per-user notifications, so it\n // survives a refresh and stays until someone acts on it.\n await notifyStaff({\n type: 'marketplace.review',\n title: `${displayName.trim()} v${manifest.version} needs review`,\n body: existing.empty\n ? 'A new plugin was submitted to the marketplace.'\n : 'A new version was published. The previously approved version ' +\n 'keeps installing until this one is reviewed.',\n link: `/admin/plugin-reviews/${listingRef.id}`,\n }).catch(() => undefined)\n\n return res\n .status(200)\n .json({ listingId: listingRef.id, version: manifest.version, sha256 })\n } catch (error) {\n console.error(error)\n return res.status(500).json({ error: 'Publish failed' })\n }\n}\n"],"names":["checkEntitlement","checkPluginBundle","createResourceUid","MAX_PLUGIN_BUNDLE_BYTES","pluginArtifactPath","PLUGIN_VERIFIER_VERSION","validatePluginManifest","firebaseAdmin","getOrgForUser","notifyStaff","resolveOrgPermissions","canActAsPublisher","resolvePublisherProfile","publishPreconditionRefusal","attestationLabels","missingAttestations","missingAttestationSubjects","PUBLISHER_ATTESTATION","createHash","marketplacePriceRefusal","missingPublicListingContent","validateListingContent","MAX_BUNDLE_BYTES","updateListingContent","req","res","listingId","String","body","status","json","error","authorization","headers","idToken","startsWith","slice","length","undefined","verdict","ok","decoded","app","auth","verifyIdToken","listingRef","firestore","collection","doc","listing","get","data","deletedAt","isPublisher","uid","profileId","description","displayName","nextName","trim","set","content","updatedAt","FieldValue","serverTimestamp","merge","console","setListingVisibility","visibility","missing","join","publishPluginHandler","method","action","category","changelog","priceUsd","Math","round","Number","bundleBase64","bundle","contentVerdict","priceRefusal","validation","manifest","membership","requestedOrgId","orgId","permissions","publishToMarketplace","orgForUser","org","dayKey","Date","toISOString","limiterRef","allowed","runTransaction","transaction","window","count","publisher","refusal","sells","Buffer","from","verification","toString","maxBytes","declaredNetwork","capabilities","network","declaredContributions","contributes","requireContributions","problems","sha256","update","Uint8Array","digest","artifactsBucket","process","env","PLUGIN_ARTIFACTS_BUCKET","existing","where","id","limit","empty","docs","ref","ticked","Array","isArray","attestation","map","filter","some","item","isUpdate","missingSubjects","subject","label","field","objectPath","version","bucket","storage","file","alreadyStored","exists","save","contentType","metadata","cacheControl","now","type","artifactType","pluginId","latestVersion","latestVersionReviewState","createdAt","reviewStatus","repositoryUrl","publishedAt","publisherAttestation","Object","fromEntries","by","at","reviewState","verifierVersion","checkedAt","title","link","catch"],"mappings":"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"}