@aglyn/plugins-marketplace 1.0.0-beta.150 → 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 +14 -14
- 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/model/marketplace-paths.d.ts +10 -0
- package/src/lib/model/marketplace-paths.js +12 -1
- package/src/lib/model/marketplace-paths.js.map +1 -1
- 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/plugin.js +26 -0
- package/src/lib/plugin.js.map +1 -1
- 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/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 +6 -0
- package/src/lib/server.js.map +1 -1
|
@@ -0,0 +1,1438 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/ 'use client';
|
|
17
|
+
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
18
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
19
|
+
import { PageHeaderHelp, PageHeaderRecord, resolveStaffRoleGate, SUPER_STAFF_ONLY_REASON } from "@aglyn/aglyn";
|
|
20
|
+
import { AppLink, CardDisplay } from "@aglyn/shared-ui-jsx";
|
|
21
|
+
import BlockedControl from "@aglyn/shared-ui-jsx/components/blocked-control.component";
|
|
22
|
+
import { listingPath, orgMarketplacePath, publisherPath } from "../model/marketplace-paths.js";
|
|
23
|
+
import { useSnackbar } from "@aglyn/shared-ui-snackstack";
|
|
24
|
+
import { Alert, Box, Button, Checkbox, Chip, Divider, FormControlLabel, Link as MuiLink, MenuItem, Skeleton, Stack, TextField, ToggleButton, ToggleButtonGroup, Tooltip, Typography } from "@mui/material";
|
|
25
|
+
import { useCallback, useEffect, useState } from "react";
|
|
26
|
+
import { useUser } from "@aglyn/tenant-feature-instance";
|
|
27
|
+
import { MarkdownLiteView } from "@aglyn/aglyn-markdown-editor";
|
|
28
|
+
import { pluginDocsHelp } from "@aglyn/aglyn";
|
|
29
|
+
import { PLUGIN_REVIEW_CHECKLIST } from "../model/plugin-review-checklist.js";
|
|
30
|
+
import { PLUGIN_REJECTION_CATEGORIES, pluginRejectionCategory, rejectionHeadline, rejectionInputError } from "../model/plugin-rejection-categories.js";
|
|
31
|
+
import { PUBLISHER_ATTESTATION } from "@aglyn/aglyn/app-utils/publisher-attestation";
|
|
32
|
+
import { authorizedFetch } from "@aglyn/shared-util-http/authorized-token";
|
|
33
|
+
import { reviewStatusMeaning } from "../model/plugin-review-status.js";
|
|
34
|
+
/** Verifier findings read as a wall of text otherwise; group by severity. */ const SEVERITY_ORDER = [
|
|
35
|
+
'error',
|
|
36
|
+
'warn',
|
|
37
|
+
'warning',
|
|
38
|
+
'info'
|
|
39
|
+
];
|
|
40
|
+
/**
|
|
41
|
+
* One plugin submission or listed plugin, in full (AGL-960).
|
|
42
|
+
*
|
|
43
|
+
* The index is for scanning; this is where a reviewer reads the manifest,
|
|
44
|
+
* weighs the verifier findings and acts. Every consequential action lives
|
|
45
|
+
* here rather than on the list, so nobody grants realm trust or takes a
|
|
46
|
+
* plugin down while skimming rows.
|
|
47
|
+
*/ export function PluginReviewDetail({ basePath, segments, staffRole, staffPaths }) {
|
|
48
|
+
var _ref, _ref1, _ref2, _ref3, _ref4, _ref5, _ref6, _ref7, _ref8, _ref9, _ref10;
|
|
49
|
+
var _pluginRejectionCategory, _detail_verifier, _detail_verifier1, _reviewCapabilities_network, _reviewCapabilities_events, _reviewVersionEntry_elements, _detail_publisherAgreement, _detail_publisherAgreement1, _detail_verificationRequest, _detail_verificationRequest1;
|
|
50
|
+
/*
|
|
51
|
+
* The row this page is: the first segment beneath the queue. The surface
|
|
52
|
+
* owns its subtree, so it also owns saying "no such thing" — which the
|
|
53
|
+
* body below already does for a listing that was withdrawn while a link
|
|
54
|
+
* to it sat in a reviewer's inbox.
|
|
55
|
+
*/ const params = {
|
|
56
|
+
listingId: String((_ref = segments == null ? void 0 : segments[0]) != null ? _ref : '')
|
|
57
|
+
};
|
|
58
|
+
/*
|
|
59
|
+
* Granting realm trust is super-only on the server (AGL-2131). The claim
|
|
60
|
+
* is the app's and arrives as a prop; `null` is "still reading the token"
|
|
61
|
+
* and must never render as a refusal, or every reviewer sees a disabled
|
|
62
|
+
* button for a beat.
|
|
63
|
+
*/ const superGate = resolveStaffRoleGate(staffRole, [
|
|
64
|
+
'super'
|
|
65
|
+
]);
|
|
66
|
+
const listingId = String((_ref1 = params == null ? void 0 : params.listingId) != null ? _ref1 : '');
|
|
67
|
+
const { data: user } = useUser();
|
|
68
|
+
const { enqueueSnackbar } = useSnackbar();
|
|
69
|
+
const [detail, setDetail] = useState(null);
|
|
70
|
+
// Rendered by default (AGL-989) — the reviewer's first question is what a
|
|
71
|
+
// buyer sees; Source is for auditing exactly what was submitted.
|
|
72
|
+
const [readmeView, setReadmeView] = useState('rendered');
|
|
73
|
+
const [loaded, setLoaded] = useState(false);
|
|
74
|
+
const [busy, setBusy] = useState(false);
|
|
75
|
+
const [reason, setReason] = useState('');
|
|
76
|
+
// Structured rejection (AGL-977). The comment stays — it is what tells a
|
|
77
|
+
// publisher what to fix — but it is no longer the only thing recorded.
|
|
78
|
+
const [rejectCategory, setRejectCategory] = useState('');
|
|
79
|
+
// The same predicate the route enforces, so the button and the server
|
|
80
|
+
// cannot disagree about what a complete rejection looks like.
|
|
81
|
+
const rejectBlocked = rejectionInputError(rejectCategory, reason);
|
|
82
|
+
const rejectCategoryNeedsComment = Boolean((_pluginRejectionCategory = pluginRejectionCategory(rejectCategory)) == null ? void 0 : _pluginRejectionCategory.requiresComment);
|
|
83
|
+
const [takedownReason, setTakedownReason] = useState('');
|
|
84
|
+
// Which version is being reviewed. Empty = let the server pick the oldest
|
|
85
|
+
// one still awaiting a verdict, which is the work queue for this listing.
|
|
86
|
+
const [selectedVersion, setSelectedVersion] = useState('');
|
|
87
|
+
const refresh = useCallback(async ()=>{
|
|
88
|
+
if (!listingId) return;
|
|
89
|
+
const response = await authorizedFetch(user, `/api/marketplace/admin/reviews?listingId=${encodeURIComponent(listingId)}` + (selectedVersion ? `&version=${encodeURIComponent(selectedVersion)}` : ''));
|
|
90
|
+
if (response.ok) setDetail(await response.json());
|
|
91
|
+
setLoaded(true);
|
|
92
|
+
}, [
|
|
93
|
+
user,
|
|
94
|
+
listingId,
|
|
95
|
+
selectedVersion
|
|
96
|
+
]);
|
|
97
|
+
useEffect(()=>{
|
|
98
|
+
if (user) void refresh();
|
|
99
|
+
}, [
|
|
100
|
+
user,
|
|
101
|
+
refresh
|
|
102
|
+
]);
|
|
103
|
+
const post = useCallback(async (payload, success)=>{
|
|
104
|
+
setBusy(true);
|
|
105
|
+
try {
|
|
106
|
+
const response = await authorizedFetch(user, '/api/marketplace/admin/reviews', {
|
|
107
|
+
method: 'POST',
|
|
108
|
+
headers: {
|
|
109
|
+
'Content-Type': 'application/json'
|
|
110
|
+
},
|
|
111
|
+
body: JSON.stringify(_extends({
|
|
112
|
+
listingId
|
|
113
|
+
}, payload))
|
|
114
|
+
});
|
|
115
|
+
const result = await response.json().catch(()=>({}));
|
|
116
|
+
if (response.ok) {
|
|
117
|
+
enqueueSnackbar(success, {
|
|
118
|
+
variant: 'success'
|
|
119
|
+
});
|
|
120
|
+
await refresh();
|
|
121
|
+
} else {
|
|
122
|
+
var _ref;
|
|
123
|
+
enqueueSnackbar((_ref = result == null ? void 0 : result.error) != null ? _ref : 'Action failed', {
|
|
124
|
+
variant: 'error',
|
|
125
|
+
allowDuplicate: true
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
} finally{
|
|
129
|
+
setBusy(false);
|
|
130
|
+
}
|
|
131
|
+
}, [
|
|
132
|
+
user,
|
|
133
|
+
listingId,
|
|
134
|
+
enqueueSnackbar,
|
|
135
|
+
refresh
|
|
136
|
+
]);
|
|
137
|
+
const signRealm = useCallback(async (version, action)=>{
|
|
138
|
+
setBusy(true);
|
|
139
|
+
try {
|
|
140
|
+
const response = await authorizedFetch(user, '/api/admin/sign-plugin', {
|
|
141
|
+
method: 'POST',
|
|
142
|
+
headers: {
|
|
143
|
+
'Content-Type': 'application/json'
|
|
144
|
+
},
|
|
145
|
+
body: JSON.stringify(_extends({
|
|
146
|
+
listingId,
|
|
147
|
+
version
|
|
148
|
+
}, action === 'revoke' ? {
|
|
149
|
+
action: 'revoke'
|
|
150
|
+
} : {}))
|
|
151
|
+
});
|
|
152
|
+
const result = await response.json().catch(()=>({}));
|
|
153
|
+
if (response.ok) {
|
|
154
|
+
enqueueSnackbar(action === 'revoke' ? `v${version} returned to the sandbox` : `v${version} signed for the app realm`, {
|
|
155
|
+
variant: 'success'
|
|
156
|
+
});
|
|
157
|
+
await refresh();
|
|
158
|
+
} else {
|
|
159
|
+
var _ref;
|
|
160
|
+
enqueueSnackbar((_ref = result == null ? void 0 : result.error) != null ? _ref : 'Signing failed', {
|
|
161
|
+
variant: 'error',
|
|
162
|
+
allowDuplicate: true
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
} finally{
|
|
166
|
+
setBusy(false);
|
|
167
|
+
}
|
|
168
|
+
}, [
|
|
169
|
+
user,
|
|
170
|
+
listingId,
|
|
171
|
+
enqueueSnackbar,
|
|
172
|
+
refresh
|
|
173
|
+
]);
|
|
174
|
+
/**
|
|
175
|
+
* Per-version kill switch (AGL-1085). Distinct from `takedown`, which
|
|
176
|
+
* revokes EVERY version and hides the listing: stopping one version's
|
|
177
|
+
* bytes must not also stop the approved version customers are running.
|
|
178
|
+
*/ const setVersionRevoked = useCallback(async (version, revoke)=>{
|
|
179
|
+
await post({
|
|
180
|
+
action: revoke ? 'revoke-version' : 'unrevoke-version',
|
|
181
|
+
version
|
|
182
|
+
}, revoke ? `v${version} stopped — running installs render a placeholder on next load` : `v${version} allowed to run again`);
|
|
183
|
+
}, [
|
|
184
|
+
post
|
|
185
|
+
]);
|
|
186
|
+
const takedown = useCallback(async ()=>{
|
|
187
|
+
if (!detail) return;
|
|
188
|
+
if (!detail.hidden && !takedownReason.trim()) {
|
|
189
|
+
return void enqueueSnackbar('Taking a plugin down needs a reason', {
|
|
190
|
+
variant: 'warning',
|
|
191
|
+
allowDuplicate: true
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
await post({
|
|
195
|
+
action: detail.hidden ? 'unhide' : 'hide',
|
|
196
|
+
reason: takedownReason
|
|
197
|
+
}, detail.hidden ? `${detail.displayName} restored` : `${detail.displayName} taken down — running installs stop on next load`);
|
|
198
|
+
setTakedownReason('');
|
|
199
|
+
}, [
|
|
200
|
+
detail,
|
|
201
|
+
takedownReason,
|
|
202
|
+
post,
|
|
203
|
+
enqueueSnackbar
|
|
204
|
+
]);
|
|
205
|
+
const status = reviewStatusMeaning((_ref2 = detail == null ? void 0 : detail.reviewStatus) != null ? _ref2 : '');
|
|
206
|
+
const blocked = ((_ref3 = detail == null ? void 0 : detail.checklistOutstanding.length) != null ? _ref3 : 0) > 0;
|
|
207
|
+
/**
|
|
208
|
+
* The version the approve/reject buttons act on (AGL-1085). Rejecting does
|
|
209
|
+
* not stop bytes that are already pinned — the runtime resolves a pin by
|
|
210
|
+
* {version, sha256} and only asks whether it is revoked — so the reviewer
|
|
211
|
+
* needs this version's live-install count at the moment they decide, not
|
|
212
|
+
* on some other page.
|
|
213
|
+
*/ const reviewEntry = detail == null ? void 0 : detail.versions.find((entry)=>entry.version === (detail == null ? void 0 : detail.reviewVersion));
|
|
214
|
+
const liveOnReviewVersion = (_ref4 = reviewEntry == null ? void 0 : reviewEntry.activeInstalls) != null ? _ref4 : 0;
|
|
215
|
+
/**
|
|
216
|
+
* Where a reviewer actually goes to check each item (AGL-973).
|
|
217
|
+
*
|
|
218
|
+
* Without these, the checklist asks people to "read the bundle source"
|
|
219
|
+
* with no way to reach it — the bundle lives in a private bucket behind
|
|
220
|
+
* the plugin origin, and its URL is content-addressed, so nobody could
|
|
221
|
+
* construct it by hand. An unactionable checklist gets ticked without
|
|
222
|
+
* being done, which is worse than no checklist.
|
|
223
|
+
*/ const artifactUrl = (version)=>{
|
|
224
|
+
var _process_env_NEXT_PUBLIC_PLUGIN_ORIGIN;
|
|
225
|
+
const entry = detail == null ? void 0 : detail.versions.find((item)=>item.version === version);
|
|
226
|
+
if (!(entry == null ? void 0 : entry.sha256)) return null;
|
|
227
|
+
const path = `/artifacts/${detail == null ? void 0 : detail.listingId}/${version}/${entry.sha256}.bundle`;
|
|
228
|
+
const origin = (_process_env_NEXT_PUBLIC_PLUGIN_ORIGIN = process.env.NEXT_PUBLIC_PLUGIN_ORIGIN) != null ? _process_env_NEXT_PUBLIC_PLUGIN_ORIGIN : '';
|
|
229
|
+
// The plugin origin just edge-rewrites to the console's own artifact
|
|
230
|
+
// route, so fall back to that directly when the origin is unset — as it
|
|
231
|
+
// is on every dev machine. Without the fallback the "read the bundle
|
|
232
|
+
// source" link silently vanished exactly where it matters most, and a
|
|
233
|
+
// reviewer had no way to reach the code they are certifying.
|
|
234
|
+
return origin ? `${origin.replace(/\/+$/, '')}${path}` : `/api/plugin-artifacts/${detail == null ? void 0 : detail.listingId}/${version}/${entry.sha256}.bundle`;
|
|
235
|
+
};
|
|
236
|
+
/**
|
|
237
|
+
* The stored object in the Cloud console (AGL-990) — the served bundle is
|
|
238
|
+
* what a reviewer READS, this is where they see the bytes: size, upload
|
|
239
|
+
* time, content type, generation. The bucket is not in the Firebase
|
|
240
|
+
* console, so nobody finds this by hand.
|
|
241
|
+
*/ const bucketUrl = (version)=>{
|
|
242
|
+
const entry = detail == null ? void 0 : detail.versions.find((item)=>item.version === version);
|
|
243
|
+
if (!(entry == null ? void 0 : entry.sha256) || !(detail == null ? void 0 : detail.artifactsBucket)) return null;
|
|
244
|
+
const object = `artifacts/${detail.listingId}/${version}/${entry.sha256}.bundle`;
|
|
245
|
+
return 'https://console.cloud.google.com/storage/browser/_details/' + `${detail.artifactsBucket}/${object}`;
|
|
246
|
+
};
|
|
247
|
+
/**
|
|
248
|
+
* Every string a publisher wrote on one declared element (AGL-2486).
|
|
249
|
+
*
|
|
250
|
+
* Derived by WALKING the entry rather than by reading named fields, so text
|
|
251
|
+
* added to the manifest later — the way `tags` and `keywords` became
|
|
252
|
+
* searched after `description` became rendered — shows up here without
|
|
253
|
+
* anyone remembering this page. The criterion asks a reviewer to check the
|
|
254
|
+
* publisher's text, so the page has to show all of it, including the search
|
|
255
|
+
* terms that rank without ever rendering.
|
|
256
|
+
*/ const publisherTextOf = (element)=>{
|
|
257
|
+
const lines = [];
|
|
258
|
+
const walk = (value, path)=>{
|
|
259
|
+
if (lines.length >= 40) return;
|
|
260
|
+
if (typeof value === 'string') {
|
|
261
|
+
if (value.trim()) lines.push(`${path}: ${value}`);
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
if (Array.isArray(value)) {
|
|
265
|
+
value.forEach((entry, index)=>walk(entry, `${path}[${index}]`));
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
if (value && typeof value === 'object') {
|
|
269
|
+
for (const [key, entry] of Object.entries(value)){
|
|
270
|
+
walk(entry, `${path}.${key}`);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
for (const [key, value] of Object.entries(element != null ? element : {})){
|
|
275
|
+
// The icon is an mdi NAME looked up in our own set, not publisher prose.
|
|
276
|
+
if (key === 'id' || key === 'icon') continue;
|
|
277
|
+
walk(value, key);
|
|
278
|
+
}
|
|
279
|
+
return lines;
|
|
280
|
+
};
|
|
281
|
+
const checklistLink = (itemId)=>{
|
|
282
|
+
if (!detail) return null;
|
|
283
|
+
// The version approved before this one — what a diff should be against.
|
|
284
|
+
const previousApproved = detail.versions.filter((entry)=>entry.version !== detail.reviewVersion && entry.reviewState === 'approved').at(0);
|
|
285
|
+
switch(itemId){
|
|
286
|
+
case 'provenance':
|
|
287
|
+
return detail.repositoryUrl ? {
|
|
288
|
+
href: detail.repositoryUrl,
|
|
289
|
+
label: 'Open repository',
|
|
290
|
+
external: true
|
|
291
|
+
} : null;
|
|
292
|
+
case 'publisher':
|
|
293
|
+
var _ref;
|
|
294
|
+
return {
|
|
295
|
+
href: (_ref = staffPaths == null ? void 0 : staffPaths.orgDetail(detail.publisherId)) != null ? _ref : '',
|
|
296
|
+
label: 'Publisher workspace'
|
|
297
|
+
};
|
|
298
|
+
case 'source-read':
|
|
299
|
+
{
|
|
300
|
+
const href = artifactUrl(detail.reviewVersion);
|
|
301
|
+
return href ? {
|
|
302
|
+
href,
|
|
303
|
+
label: `Read v${detail.reviewVersion} bundle`,
|
|
304
|
+
external: true
|
|
305
|
+
} : null;
|
|
306
|
+
}
|
|
307
|
+
case 'diff':
|
|
308
|
+
{
|
|
309
|
+
const href = previousApproved ? artifactUrl(previousApproved.version) : null;
|
|
310
|
+
return href ? {
|
|
311
|
+
href,
|
|
312
|
+
label: `Previous approved: v${previousApproved == null ? void 0 : previousApproved.version}`,
|
|
313
|
+
external: true
|
|
314
|
+
} : null;
|
|
315
|
+
}
|
|
316
|
+
case 'element-metadata':
|
|
317
|
+
// Where the copy is READ, which is the whole difficulty with this
|
|
318
|
+
// one: the picker lives in the besigner on whatever site the
|
|
319
|
+
// reviewer installed it to, so the listing is the closest thing to
|
|
320
|
+
// a starting point this page can hand them.
|
|
321
|
+
return detail.publisherSlug ? {
|
|
322
|
+
href: listingPath(orgMarketplacePath(detail.publisherSlug), detail.listingId),
|
|
323
|
+
label: 'Install, then open the element picker'
|
|
324
|
+
} : null;
|
|
325
|
+
case 'behaviour':
|
|
326
|
+
return detail.publisherSlug ? {
|
|
327
|
+
href: listingPath(orgMarketplacePath(detail.publisherSlug), detail.listingId),
|
|
328
|
+
label: 'Marketplace listing'
|
|
329
|
+
} : null;
|
|
330
|
+
case 'license':
|
|
331
|
+
case 'support':
|
|
332
|
+
return detail.homepageUrl ? {
|
|
333
|
+
href: detail.homepageUrl,
|
|
334
|
+
label: 'Homepage',
|
|
335
|
+
external: true
|
|
336
|
+
} : detail.repositoryUrl ? {
|
|
337
|
+
href: detail.repositoryUrl,
|
|
338
|
+
label: 'Repository',
|
|
339
|
+
external: true
|
|
340
|
+
} : null;
|
|
341
|
+
default:
|
|
342
|
+
return null;
|
|
343
|
+
}
|
|
344
|
+
};
|
|
345
|
+
// Everything in the Security card is a statement about the version under
|
|
346
|
+
// review, whose capabilities are what the verifier checked against.
|
|
347
|
+
const reviewVersionEntry = detail == null ? void 0 : detail.versions.find((entry)=>entry.version === (detail == null ? void 0 : detail.reviewVersion));
|
|
348
|
+
const reviewCapabilities = reviewVersionEntry == null ? void 0 : reviewVersionEntry.capabilities;
|
|
349
|
+
const findings = ((_ref5 = detail == null ? void 0 : (_detail_verifier = detail.verifier) == null ? void 0 : _detail_verifier.problems) != null ? _ref5 : []).slice().sort((a, b)=>SEVERITY_ORDER.indexOf(a.level) - SEVERITY_ORDER.indexOf(b.level));
|
|
350
|
+
// The per-check summary (AGL-1087). Findings hang off the check that
|
|
351
|
+
// produced them, so a reviewer reads "why" beside "what", and the counts
|
|
352
|
+
// below say in one line whether this verdict is mostly green or mostly
|
|
353
|
+
// questions. Findings from a checker that did not tag them fall through to
|
|
354
|
+
// their own list rather than disappearing.
|
|
355
|
+
const checks = (_ref6 = detail == null ? void 0 : (_detail_verifier1 = detail.verifier) == null ? void 0 : _detail_verifier1.checks) != null ? _ref6 : [];
|
|
356
|
+
const findingsByCheck = new Map();
|
|
357
|
+
const untaggedFindings = [];
|
|
358
|
+
for (const finding of findings){
|
|
359
|
+
var _findingsByCheck_get;
|
|
360
|
+
const id = finding.check;
|
|
361
|
+
if (!id || !checks.some((check)=>check.id === id)) {
|
|
362
|
+
untaggedFindings.push(finding);
|
|
363
|
+
continue;
|
|
364
|
+
}
|
|
365
|
+
findingsByCheck.set(id, [
|
|
366
|
+
...(_findingsByCheck_get = findingsByCheck.get(id)) != null ? _findingsByCheck_get : [],
|
|
367
|
+
finding
|
|
368
|
+
]);
|
|
369
|
+
}
|
|
370
|
+
const countBy = (status)=>checks.filter((check)=>check.status === status).length;
|
|
371
|
+
return /*#__PURE__*/ _jsxs(_Fragment, {
|
|
372
|
+
children: [
|
|
373
|
+
/*#__PURE__*/ _jsx(PageHeaderRecord, {
|
|
374
|
+
title: (_ref7 = detail == null ? void 0 : detail.displayName) != null ? _ref7 : ''
|
|
375
|
+
}),
|
|
376
|
+
/*#__PURE__*/ _jsx(PageHeaderHelp, {
|
|
377
|
+
topic: "publisherHandbook",
|
|
378
|
+
anchor: "#review-what-happens-after-you-publish"
|
|
379
|
+
}),
|
|
380
|
+
!loaded ? /*#__PURE__*/ _jsxs(Stack, {
|
|
381
|
+
spacing: 2,
|
|
382
|
+
children: [
|
|
383
|
+
/*#__PURE__*/ _jsx(Skeleton, {
|
|
384
|
+
variant: "rounded",
|
|
385
|
+
height: 120
|
|
386
|
+
}),
|
|
387
|
+
/*#__PURE__*/ _jsx(Skeleton, {
|
|
388
|
+
variant: "rounded",
|
|
389
|
+
height: 220
|
|
390
|
+
})
|
|
391
|
+
]
|
|
392
|
+
}) : !detail ? /*#__PURE__*/ _jsx(Alert, {
|
|
393
|
+
severity: "warning",
|
|
394
|
+
children: 'That listing no longer exists.'
|
|
395
|
+
}) : /*#__PURE__*/ _jsxs(Stack, {
|
|
396
|
+
spacing: 3,
|
|
397
|
+
children: [
|
|
398
|
+
/*#__PURE__*/ _jsx(CardDisplay, {
|
|
399
|
+
header: "Status",
|
|
400
|
+
help: pluginDocsHelp('publisherHandbook', {
|
|
401
|
+
anchor: '#review-what-happens-after-you-publish',
|
|
402
|
+
excerpt: 'Where this version stands: submitted, listed, verified, or ' + 'rejected — and what each state lets a customer do.'
|
|
403
|
+
}),
|
|
404
|
+
contentGutterX: true,
|
|
405
|
+
contentGutterY: true,
|
|
406
|
+
children: /*#__PURE__*/ _jsxs(Stack, {
|
|
407
|
+
spacing: 1.5,
|
|
408
|
+
children: [
|
|
409
|
+
/*#__PURE__*/ _jsxs(Stack, {
|
|
410
|
+
direction: "row",
|
|
411
|
+
spacing: 1,
|
|
412
|
+
sx: {
|
|
413
|
+
alignItems: 'center',
|
|
414
|
+
flexWrap: 'wrap'
|
|
415
|
+
},
|
|
416
|
+
children: [
|
|
417
|
+
/*#__PURE__*/ _jsx(Chip, {
|
|
418
|
+
size: "small",
|
|
419
|
+
color: status.color,
|
|
420
|
+
label: status.label
|
|
421
|
+
}),
|
|
422
|
+
/*#__PURE__*/ _jsx(Chip, {
|
|
423
|
+
size: "small",
|
|
424
|
+
variant: "outlined",
|
|
425
|
+
label: `v${detail.latestVersion || '—'}`
|
|
426
|
+
}),
|
|
427
|
+
/*#__PURE__*/ _jsx(Chip, {
|
|
428
|
+
size: "small",
|
|
429
|
+
variant: "outlined",
|
|
430
|
+
label: detail.priceUsd > 0 ? `$${detail.priceUsd}` : 'Free'
|
|
431
|
+
}),
|
|
432
|
+
/*#__PURE__*/ _jsx(Chip, {
|
|
433
|
+
size: "small",
|
|
434
|
+
variant: "outlined",
|
|
435
|
+
label: `${detail.activeInstalls} install${detail.activeInstalls === 1 ? '' : 's'}`
|
|
436
|
+
}),
|
|
437
|
+
detail.hidden ? /*#__PURE__*/ _jsx(Chip, {
|
|
438
|
+
size: "small",
|
|
439
|
+
color: "error",
|
|
440
|
+
label: "Taken down — revoked"
|
|
441
|
+
}) : null,
|
|
442
|
+
detail.unpublished ? /*#__PURE__*/ _jsx(Chip, {
|
|
443
|
+
size: "small",
|
|
444
|
+
color: "warning",
|
|
445
|
+
label: "Unpublished by the publisher"
|
|
446
|
+
}) : null
|
|
447
|
+
]
|
|
448
|
+
}),
|
|
449
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
450
|
+
variant: "caption",
|
|
451
|
+
color: "text.secondary",
|
|
452
|
+
children: status.meaning
|
|
453
|
+
}),
|
|
454
|
+
/*#__PURE__*/ _jsxs(Typography, {
|
|
455
|
+
variant: "body2",
|
|
456
|
+
color: "text.secondary",
|
|
457
|
+
children: [
|
|
458
|
+
'Publisher: ',
|
|
459
|
+
detail.publisherSlug ? /*#__PURE__*/ _jsx(AppLink, {
|
|
460
|
+
// The storefront route takes a handle now
|
|
461
|
+
// (AGL-1001). This payload carries the org's slug
|
|
462
|
+
// and name, not the publisher profile's handle, so
|
|
463
|
+
// it passes the id — which that page still
|
|
464
|
+
// resolves, the same fallback that keeps existing
|
|
465
|
+
// links working.
|
|
466
|
+
href: publisherPath(orgMarketplacePath(detail.publisherSlug), detail.publisherId),
|
|
467
|
+
children: detail.publisherName
|
|
468
|
+
}) : detail.publisherName,
|
|
469
|
+
` · ${detail.listingId}`
|
|
470
|
+
]
|
|
471
|
+
}),
|
|
472
|
+
[
|
|
473
|
+
'listed',
|
|
474
|
+
'verified'
|
|
475
|
+
].includes(detail.reviewStatus) && detail.checklistOutstanding.length ? /*#__PURE__*/ _jsx(Alert, {
|
|
476
|
+
severity: "warning",
|
|
477
|
+
children: `Live in the marketplace with no recorded review for these bytes (${detail.checklistOutstanding.length} required item(s) outstanding). Work through the checklist, or delist while you do.`
|
|
478
|
+
}) : null,
|
|
479
|
+
detail.rejectionReason ? /*#__PURE__*/ _jsx(Alert, {
|
|
480
|
+
severity: "error",
|
|
481
|
+
children: `Rejected: ${rejectionHeadline(detail.rejectionCategory, detail.rejectionReason)}${detail.rejectionReason && pluginRejectionCategory(detail.rejectionCategory) ? ` — ${detail.rejectionReason}` : ''}`
|
|
482
|
+
}) : null,
|
|
483
|
+
detail.hidden ? /*#__PURE__*/ _jsx(Alert, {
|
|
484
|
+
severity: "error",
|
|
485
|
+
children: detail.hiddenReason ? `Taken down: ${detail.hiddenReason}` : 'Taken down (no reason recorded)'
|
|
486
|
+
}) : null
|
|
487
|
+
]
|
|
488
|
+
})
|
|
489
|
+
}),
|
|
490
|
+
/*#__PURE__*/ _jsx(CardDisplay, {
|
|
491
|
+
header: "Overview",
|
|
492
|
+
help: pluginDocsHelp('publisherHandbook', {
|
|
493
|
+
excerpt: 'The listing as a customer sees it — name, description, media and ' + 'links, all publisher-supplied.'
|
|
494
|
+
}),
|
|
495
|
+
contentGutterX: true,
|
|
496
|
+
contentGutterY: true,
|
|
497
|
+
children: /*#__PURE__*/ _jsxs(Stack, {
|
|
498
|
+
spacing: 1.5,
|
|
499
|
+
children: [
|
|
500
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
501
|
+
variant: "body2",
|
|
502
|
+
children: detail.description || 'No description.'
|
|
503
|
+
}),
|
|
504
|
+
/*#__PURE__*/ _jsxs(Stack, {
|
|
505
|
+
direction: "row",
|
|
506
|
+
spacing: 1,
|
|
507
|
+
sx: {
|
|
508
|
+
alignItems: 'center',
|
|
509
|
+
flexWrap: 'wrap'
|
|
510
|
+
},
|
|
511
|
+
children: [
|
|
512
|
+
detail.license ? /*#__PURE__*/ _jsx(Chip, {
|
|
513
|
+
size: "small",
|
|
514
|
+
variant: "outlined",
|
|
515
|
+
label: detail.license
|
|
516
|
+
}) : /*#__PURE__*/ _jsx(Chip, {
|
|
517
|
+
size: "small",
|
|
518
|
+
color: "warning",
|
|
519
|
+
label: "No license"
|
|
520
|
+
}),
|
|
521
|
+
detail.categories.map((category)=>/*#__PURE__*/ _jsx(Chip, {
|
|
522
|
+
size: "small",
|
|
523
|
+
label: category
|
|
524
|
+
}, category))
|
|
525
|
+
]
|
|
526
|
+
}),
|
|
527
|
+
detail.homepageUrl || detail.repositoryUrl ? /*#__PURE__*/ _jsxs(Typography, {
|
|
528
|
+
variant: "body2",
|
|
529
|
+
color: "text.secondary",
|
|
530
|
+
children: [
|
|
531
|
+
detail.homepageUrl ? `Homepage: ${detail.homepageUrl}` : '',
|
|
532
|
+
detail.homepageUrl && detail.repositoryUrl ? ' · ' : '',
|
|
533
|
+
detail.repositoryUrl ? `Repository: ${detail.repositoryUrl}` : ''
|
|
534
|
+
]
|
|
535
|
+
}) : null,
|
|
536
|
+
/*#__PURE__*/ _jsx(Divider, {}),
|
|
537
|
+
/*#__PURE__*/ _jsxs(Stack, {
|
|
538
|
+
direction: "row",
|
|
539
|
+
sx: {
|
|
540
|
+
alignItems: 'center',
|
|
541
|
+
justifyContent: 'space-between',
|
|
542
|
+
gap: 1
|
|
543
|
+
},
|
|
544
|
+
children: [
|
|
545
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
546
|
+
variant: "subtitle2",
|
|
547
|
+
children: 'README'
|
|
548
|
+
}),
|
|
549
|
+
/*#__PURE__*/ _jsxs(ToggleButtonGroup, {
|
|
550
|
+
exclusive: true,
|
|
551
|
+
size: "small",
|
|
552
|
+
value: readmeView,
|
|
553
|
+
onChange: (_event, next)=>{
|
|
554
|
+
if (next) setReadmeView(next);
|
|
555
|
+
},
|
|
556
|
+
children: [
|
|
557
|
+
/*#__PURE__*/ _jsx(ToggleButton, {
|
|
558
|
+
value: "rendered",
|
|
559
|
+
children: /*#__PURE__*/ _jsx(Typography, {
|
|
560
|
+
variant: "caption",
|
|
561
|
+
children: 'Rendered'
|
|
562
|
+
})
|
|
563
|
+
}),
|
|
564
|
+
/*#__PURE__*/ _jsx(ToggleButton, {
|
|
565
|
+
value: "source",
|
|
566
|
+
children: /*#__PURE__*/ _jsx(Typography, {
|
|
567
|
+
variant: "caption",
|
|
568
|
+
children: 'Source'
|
|
569
|
+
})
|
|
570
|
+
})
|
|
571
|
+
]
|
|
572
|
+
})
|
|
573
|
+
]
|
|
574
|
+
}),
|
|
575
|
+
detail.readme ? // Framed (AGL-991): everything inside this border is
|
|
576
|
+
// publisher-submitted content, not our page. On a review
|
|
577
|
+
// screen that boundary is the whole job.
|
|
578
|
+
/*#__PURE__*/ _jsx(Box, {
|
|
579
|
+
sx: {
|
|
580
|
+
maxHeight: 480,
|
|
581
|
+
overflowY: 'auto',
|
|
582
|
+
border: 1,
|
|
583
|
+
borderColor: 'divider',
|
|
584
|
+
borderRadius: 1,
|
|
585
|
+
p: 2,
|
|
586
|
+
bgcolor: 'action.hover'
|
|
587
|
+
},
|
|
588
|
+
children: readmeView === 'rendered' ? /*#__PURE__*/ _jsx(MarkdownLiteView, {
|
|
589
|
+
source: detail.readme
|
|
590
|
+
}) : /*#__PURE__*/ _jsx(Typography, {
|
|
591
|
+
variant: "body2",
|
|
592
|
+
component: "pre",
|
|
593
|
+
sx: {
|
|
594
|
+
whiteSpace: 'pre-wrap',
|
|
595
|
+
fontFamily: 'monospace',
|
|
596
|
+
fontSize: 12,
|
|
597
|
+
m: 0
|
|
598
|
+
},
|
|
599
|
+
children: detail.readme
|
|
600
|
+
})
|
|
601
|
+
}) : /*#__PURE__*/ _jsx(Alert, {
|
|
602
|
+
severity: "warning",
|
|
603
|
+
children: 'No README — publishers are expected to ship one.'
|
|
604
|
+
})
|
|
605
|
+
]
|
|
606
|
+
})
|
|
607
|
+
}),
|
|
608
|
+
/*#__PURE__*/ _jsx(CardDisplay, {
|
|
609
|
+
header: "Security",
|
|
610
|
+
help: pluginDocsHelp('sandboxSecurity', {
|
|
611
|
+
excerpt: 'What the bundle asked for, against what the sandbox enforces ' + 'regardless of what it asked for.'
|
|
612
|
+
}),
|
|
613
|
+
contentGutterX: true,
|
|
614
|
+
contentGutterY: true,
|
|
615
|
+
children: /*#__PURE__*/ _jsxs(Stack, {
|
|
616
|
+
spacing: 1.5,
|
|
617
|
+
children: [
|
|
618
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
619
|
+
variant: "body2",
|
|
620
|
+
color: "text.secondary",
|
|
621
|
+
children: `Declared network: ${(reviewCapabilities == null ? void 0 : (_reviewCapabilities_network = reviewCapabilities.network) == null ? void 0 : _reviewCapabilities_network.join(', ')) || 'none'} · events: ${(reviewCapabilities == null ? void 0 : (_reviewCapabilities_events = reviewCapabilities.events) == null ? void 0 : _reviewCapabilities_events.join(', ')) || 'none'}`
|
|
622
|
+
}),
|
|
623
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
624
|
+
variant: "body2",
|
|
625
|
+
color: "text.secondary",
|
|
626
|
+
children: `Host ABI: declared ${(_ref8 = reviewVersionEntry == null ? void 0 : reviewVersionEntry.hostAbi) != null ? _ref8 : 'none (legacy)'}, platform runs ${detail.platformHostAbi}`
|
|
627
|
+
}),
|
|
628
|
+
/*#__PURE__*/ _jsxs(Stack, {
|
|
629
|
+
spacing: 0.5,
|
|
630
|
+
children: [
|
|
631
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
632
|
+
variant: "body2",
|
|
633
|
+
color: "text.secondary",
|
|
634
|
+
children: `Declared elements: ${(_ref9 = reviewVersionEntry == null ? void 0 : (_reviewVersionEntry_elements = reviewVersionEntry.elements) == null ? void 0 : _reviewVersionEntry_elements.length) != null ? _ref9 : 0} — publisher text the picker renders and ranks its search on, none of it on the listing page.`
|
|
635
|
+
}),
|
|
636
|
+
((_ref10 = reviewVersionEntry == null ? void 0 : reviewVersionEntry.elements) != null ? _ref10 : []).map((element)=>/*#__PURE__*/ _jsxs(Stack, {
|
|
637
|
+
sx: {
|
|
638
|
+
pl: 1
|
|
639
|
+
},
|
|
640
|
+
children: [
|
|
641
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
642
|
+
variant: "caption",
|
|
643
|
+
color: "text.secondary",
|
|
644
|
+
children: `${String(element['displayName'] || element['id'])}${element['category'] ? ` · ${String(element['category'])}` : ''}`
|
|
645
|
+
}),
|
|
646
|
+
publisherTextOf(element).map((line)=>/*#__PURE__*/ _jsx(Typography, {
|
|
647
|
+
variant: "caption",
|
|
648
|
+
color: "text.secondary",
|
|
649
|
+
sx: {
|
|
650
|
+
pl: 1,
|
|
651
|
+
fontFamily: 'monospace',
|
|
652
|
+
fontSize: 11,
|
|
653
|
+
wordBreak: 'break-word'
|
|
654
|
+
},
|
|
655
|
+
children: line
|
|
656
|
+
}, line))
|
|
657
|
+
]
|
|
658
|
+
}, String(element['id'])))
|
|
659
|
+
]
|
|
660
|
+
}),
|
|
661
|
+
!detail.verifier ? /*#__PURE__*/ _jsx(Alert, {
|
|
662
|
+
severity: "warning",
|
|
663
|
+
children: 'The static verifier has not run for this version — ' + 'no artifact was reachable. Treat this as unchecked, ' + 'not as clean.'
|
|
664
|
+
}) : detail.verifier.error ? /*#__PURE__*/ _jsx(Alert, {
|
|
665
|
+
severity: "warning",
|
|
666
|
+
children: `Verifier could not run: ${detail.verifier.error}`
|
|
667
|
+
}) : /*#__PURE__*/ _jsxs(Stack, {
|
|
668
|
+
spacing: 1,
|
|
669
|
+
children: [
|
|
670
|
+
/*#__PURE__*/ _jsxs(Typography, {
|
|
671
|
+
variant: "body2",
|
|
672
|
+
color: "text.secondary",
|
|
673
|
+
children: [
|
|
674
|
+
checks.length ? [
|
|
675
|
+
`${countBy('pass')} passed`,
|
|
676
|
+
countBy('fail') ? `${countBy('fail')} failed` : '',
|
|
677
|
+
countBy('question') ? `${countBy('question')} to question` : '',
|
|
678
|
+
countBy('unknown') ? `${countBy('unknown')} could not run` : ''
|
|
679
|
+
].filter(Boolean).join(' · ') : detail.verifier.ok ? 'Static verifier found nothing.' : 'Static verifier found problems.',
|
|
680
|
+
detail.verifierCached ? ' · stored verdict for these exact bytes' : ''
|
|
681
|
+
]
|
|
682
|
+
}),
|
|
683
|
+
checks.map((check)=>{
|
|
684
|
+
var _findingsByCheck_get;
|
|
685
|
+
// The state has to survive being read aloud and being
|
|
686
|
+
// read by someone who cannot separate the red from the
|
|
687
|
+
// green (AGL-1089), so it is named in text as well as
|
|
688
|
+
// drawn — this is a page people skim to decide whether
|
|
689
|
+
// code runs in other people's workspaces.
|
|
690
|
+
const marker = check.status === 'pass' ? {
|
|
691
|
+
glyph: '✓',
|
|
692
|
+
color: 'success.main',
|
|
693
|
+
name: 'Passed'
|
|
694
|
+
} : check.status === 'fail' ? {
|
|
695
|
+
glyph: '✕',
|
|
696
|
+
color: 'error.main',
|
|
697
|
+
name: 'Failed'
|
|
698
|
+
} : check.status === 'question' ? {
|
|
699
|
+
glyph: '?',
|
|
700
|
+
color: 'warning.main',
|
|
701
|
+
name: 'Worth questioning'
|
|
702
|
+
} : {
|
|
703
|
+
glyph: '—',
|
|
704
|
+
color: 'text.disabled',
|
|
705
|
+
name: 'Not checked'
|
|
706
|
+
};
|
|
707
|
+
return /*#__PURE__*/ _jsxs(Stack, {
|
|
708
|
+
spacing: 0.5,
|
|
709
|
+
children: [
|
|
710
|
+
/*#__PURE__*/ _jsxs(Stack, {
|
|
711
|
+
direction: "row",
|
|
712
|
+
spacing: 1,
|
|
713
|
+
sx: {
|
|
714
|
+
alignItems: 'baseline'
|
|
715
|
+
},
|
|
716
|
+
children: [
|
|
717
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
718
|
+
variant: "body2",
|
|
719
|
+
"aria-label": `${marker.name}:`,
|
|
720
|
+
sx: {
|
|
721
|
+
color: marker.color,
|
|
722
|
+
width: 16
|
|
723
|
+
},
|
|
724
|
+
children: marker.glyph
|
|
725
|
+
}),
|
|
726
|
+
/*#__PURE__*/ _jsxs(Typography, {
|
|
727
|
+
variant: "body2",
|
|
728
|
+
color: check.status === 'unknown' ? 'text.secondary' : undefined,
|
|
729
|
+
children: [
|
|
730
|
+
check.label,
|
|
731
|
+
check.status === 'unknown' ? ' — not checked' : ''
|
|
732
|
+
]
|
|
733
|
+
}),
|
|
734
|
+
check.detail ? /*#__PURE__*/ _jsx(Typography, {
|
|
735
|
+
variant: "caption",
|
|
736
|
+
color: "text.secondary",
|
|
737
|
+
children: check.detail
|
|
738
|
+
}) : null
|
|
739
|
+
]
|
|
740
|
+
}),
|
|
741
|
+
((_findingsByCheck_get = findingsByCheck.get(check.id)) != null ? _findingsByCheck_get : []).map((problem, index)=>/*#__PURE__*/ _jsx(Alert, {
|
|
742
|
+
severity: problem.level === 'error' ? 'error' : 'warning',
|
|
743
|
+
sx: {
|
|
744
|
+
ml: 3
|
|
745
|
+
},
|
|
746
|
+
children: problem.message
|
|
747
|
+
}, `${check.id}-${index}`))
|
|
748
|
+
]
|
|
749
|
+
}, check.id);
|
|
750
|
+
}),
|
|
751
|
+
untaggedFindings.map((problem, index)=>/*#__PURE__*/ _jsx(Alert, {
|
|
752
|
+
severity: problem.level === 'error' ? 'error' : 'warning',
|
|
753
|
+
children: problem.message
|
|
754
|
+
}, `untagged-${index}`)),
|
|
755
|
+
checks.length ? /*#__PURE__*/ _jsx(Typography, {
|
|
756
|
+
variant: "caption",
|
|
757
|
+
color: "text.secondary",
|
|
758
|
+
children: 'A full column of ticks means these shapes were ' + 'not found in code the parser could read. It is ' + 'not a statement that the plugin is safe — that ' + 'is the checklist below.'
|
|
759
|
+
}) : null
|
|
760
|
+
]
|
|
761
|
+
})
|
|
762
|
+
]
|
|
763
|
+
})
|
|
764
|
+
}),
|
|
765
|
+
/*#__PURE__*/ _jsx(CardDisplay, {
|
|
766
|
+
header: `Publisher attestation — v${detail.reviewVersion} (${detail.attestation.length}/${PUBLISHER_ATTESTATION.length})`,
|
|
767
|
+
help: pluginDocsHelp('publisherHandbook', {
|
|
768
|
+
anchor: '#the-two-badges-and-what-each-one-promises',
|
|
769
|
+
excerpt: 'What the publisher asserted about THIS version. Their claim, not ' + 'our finding — the checklist below is ours.'
|
|
770
|
+
}),
|
|
771
|
+
contentGutterX: true,
|
|
772
|
+
contentGutterY: true,
|
|
773
|
+
children: /*#__PURE__*/ _jsxs(Stack, {
|
|
774
|
+
spacing: 1,
|
|
775
|
+
children: [
|
|
776
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
777
|
+
variant: "body2",
|
|
778
|
+
color: "text.secondary",
|
|
779
|
+
children: detail.attestedBy ? `Stated by ${detail.attestedBy}${detail.attestedAt ? ` on ${new Date(detail.attestedAt).toLocaleDateString()}` : ''}, against this version's exact bytes.` : 'Nothing recorded for these bytes.'
|
|
780
|
+
}),
|
|
781
|
+
detail.attestation.length ? null : /*#__PURE__*/ _jsx(Alert, {
|
|
782
|
+
severity: "info",
|
|
783
|
+
children: 'Published before publishers were asked to attest, or ' + 'republished since. Check everything yourself — the ' + 'absence of a claim is not a claim.'
|
|
784
|
+
}),
|
|
785
|
+
PUBLISHER_ATTESTATION.map((item)=>{
|
|
786
|
+
const claimed = detail.attestation.includes(item.id);
|
|
787
|
+
return /*#__PURE__*/ _jsx(Stack, {
|
|
788
|
+
spacing: 0.25,
|
|
789
|
+
children: /*#__PURE__*/ _jsxs(Stack, {
|
|
790
|
+
direction: "row",
|
|
791
|
+
spacing: 1,
|
|
792
|
+
sx: {
|
|
793
|
+
alignItems: 'center',
|
|
794
|
+
flexWrap: 'wrap'
|
|
795
|
+
},
|
|
796
|
+
children: [
|
|
797
|
+
/*#__PURE__*/ _jsxs(Typography, {
|
|
798
|
+
variant: "body2",
|
|
799
|
+
color: claimed ? undefined : 'text.secondary',
|
|
800
|
+
children: [
|
|
801
|
+
claimed ? '✓' : '—',
|
|
802
|
+
" ",
|
|
803
|
+
item.label
|
|
804
|
+
]
|
|
805
|
+
}),
|
|
806
|
+
item.updateOnly ? /*#__PURE__*/ _jsx(Chip, {
|
|
807
|
+
size: "small",
|
|
808
|
+
variant: "outlined",
|
|
809
|
+
label: "Updates only"
|
|
810
|
+
}) : null
|
|
811
|
+
]
|
|
812
|
+
})
|
|
813
|
+
}, item.id);
|
|
814
|
+
}),
|
|
815
|
+
/*#__PURE__*/ _jsx(Divider, {
|
|
816
|
+
sx: {
|
|
817
|
+
my: 1
|
|
818
|
+
}
|
|
819
|
+
}),
|
|
820
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
821
|
+
variant: "body2",
|
|
822
|
+
color: "text.secondary",
|
|
823
|
+
children: ((_detail_publisherAgreement = detail.publisherAgreement) == null ? void 0 : _detail_publisherAgreement.state) === 'current' ? `Publisher agreement v${detail.publisherAgreement.version} accepted` + (detail.publisherAgreement.acceptedAt ? ` on ${new Date(detail.publisherAgreement.acceptedAt).toLocaleDateString()}` : '') + '.' : ((_detail_publisherAgreement1 = detail.publisherAgreement) == null ? void 0 : _detail_publisherAgreement1.state) === 'outdated' ? `Publisher agreement: on v${detail.publisherAgreement.version}, ` + `current is v${detail.publisherAgreement.required}. This ` + 'version was published under the older terms.' : 'No publisher agreement on record — this org ' + 'published before one existed. Weigh a takedown ' + 'against the Terms of Service, not against terms ' + 'nobody accepted.'
|
|
824
|
+
})
|
|
825
|
+
]
|
|
826
|
+
})
|
|
827
|
+
}),
|
|
828
|
+
/*#__PURE__*/ _jsx(CardDisplay, {
|
|
829
|
+
header: `Review checklist — v${detail.reviewVersion} (${PLUGIN_REVIEW_CHECKLIST.filter((item)=>{
|
|
830
|
+
var _detail_checklist;
|
|
831
|
+
return (_detail_checklist = detail.checklist) == null ? void 0 : _detail_checklist[item.id];
|
|
832
|
+
}).length}/${PLUGIN_REVIEW_CHECKLIST.length})`,
|
|
833
|
+
help: pluginDocsHelp('publisherHandbook', {
|
|
834
|
+
anchor: '#review-what-happens-after-you-publish',
|
|
835
|
+
excerpt: 'The checklist a reviewer works through. Re-earned per version — a ' + 'new release starts with none of it ticked.'
|
|
836
|
+
}),
|
|
837
|
+
contentGutterX: true,
|
|
838
|
+
contentGutterY: true,
|
|
839
|
+
children: /*#__PURE__*/ _jsxs(Stack, {
|
|
840
|
+
spacing: 1,
|
|
841
|
+
children: [
|
|
842
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
843
|
+
variant: "body2",
|
|
844
|
+
color: "text.secondary",
|
|
845
|
+
children: 'The static verifier is a lint, not a boundary — it ' + 'parses the bundle and reports shapes (computed access ' + 'on a global, undeclared network calls), but it cannot ' + 'judge what the code is for, and code it cannot see ' + 'through it says nothing about. These are the checks ' + 'only a person can make. Ticks are recorded against ' + "this version's exact bytes and reset if it is " + 'republished.'
|
|
846
|
+
}),
|
|
847
|
+
detail.checklistOutstanding.length ? /*#__PURE__*/ _jsx(Alert, {
|
|
848
|
+
severity: "info",
|
|
849
|
+
children: `${detail.checklistOutstanding.length} required item(s) outstanding — List and Verify stay blocked until they are done. Rejecting never needs the checklist.`
|
|
850
|
+
}) : /*#__PURE__*/ _jsx(Alert, {
|
|
851
|
+
severity: "success",
|
|
852
|
+
children: 'Every required item is recorded for these bytes.'
|
|
853
|
+
}),
|
|
854
|
+
PLUGIN_REVIEW_CHECKLIST.map((item)=>{
|
|
855
|
+
var _detail_checklist;
|
|
856
|
+
return /*#__PURE__*/ _jsxs(Stack, {
|
|
857
|
+
spacing: 0.25,
|
|
858
|
+
sx: {
|
|
859
|
+
pl: 0.5
|
|
860
|
+
},
|
|
861
|
+
children: [
|
|
862
|
+
/*#__PURE__*/ _jsx(FormControlLabel, {
|
|
863
|
+
control: /*#__PURE__*/ _jsx(Checkbox, {
|
|
864
|
+
size: "small",
|
|
865
|
+
checked: Boolean((_detail_checklist = detail.checklist) == null ? void 0 : _detail_checklist[item.id]),
|
|
866
|
+
disabled: busy,
|
|
867
|
+
onChange: (event)=>void post({
|
|
868
|
+
action: 'checklist',
|
|
869
|
+
version: detail.reviewVersion,
|
|
870
|
+
itemId: item.id,
|
|
871
|
+
checked: event.target.checked
|
|
872
|
+
}, event.target.checked ? 'Recorded' : 'Cleared')
|
|
873
|
+
}),
|
|
874
|
+
label: /*#__PURE__*/ _jsxs(Stack, {
|
|
875
|
+
direction: "row",
|
|
876
|
+
spacing: 1,
|
|
877
|
+
sx: {
|
|
878
|
+
alignItems: 'center',
|
|
879
|
+
flexWrap: 'wrap'
|
|
880
|
+
},
|
|
881
|
+
children: [
|
|
882
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
883
|
+
variant: "body2",
|
|
884
|
+
children: item.label
|
|
885
|
+
}),
|
|
886
|
+
item.required ? /*#__PURE__*/ _jsx(Chip, {
|
|
887
|
+
size: "small",
|
|
888
|
+
label: "Required"
|
|
889
|
+
}) : null,
|
|
890
|
+
item.realmOnly ? /*#__PURE__*/ _jsx(Chip, {
|
|
891
|
+
size: "small",
|
|
892
|
+
variant: "outlined",
|
|
893
|
+
label: "Realm trust"
|
|
894
|
+
}) : null
|
|
895
|
+
]
|
|
896
|
+
})
|
|
897
|
+
}),
|
|
898
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
899
|
+
variant: "caption",
|
|
900
|
+
color: "text.secondary",
|
|
901
|
+
sx: {
|
|
902
|
+
pl: 4
|
|
903
|
+
},
|
|
904
|
+
children: item.detail
|
|
905
|
+
}),
|
|
906
|
+
(()=>{
|
|
907
|
+
const link = checklistLink(item.id);
|
|
908
|
+
// The stored object sits beside what it serves
|
|
909
|
+
// (AGL-990) — read the code, then inspect the bytes.
|
|
910
|
+
const bucket = item.id === 'source-read' && detail ? bucketUrl(detail.reviewVersion) : null;
|
|
911
|
+
if (!link && !bucket) return null;
|
|
912
|
+
// External targets (the raw bundle, the publisher's
|
|
913
|
+
// repo) are plain anchors on purpose; internal ones
|
|
914
|
+
// go through AppLink so the SPA does not full-reload.
|
|
915
|
+
return /*#__PURE__*/ _jsxs(Stack, {
|
|
916
|
+
direction: "row",
|
|
917
|
+
spacing: 2,
|
|
918
|
+
sx: {
|
|
919
|
+
pl: 4,
|
|
920
|
+
flexWrap: 'wrap'
|
|
921
|
+
},
|
|
922
|
+
children: [
|
|
923
|
+
link ? /*#__PURE__*/ _jsx(Typography, {
|
|
924
|
+
variant: "caption",
|
|
925
|
+
children: link.external ? /*#__PURE__*/ _jsx(MuiLink, {
|
|
926
|
+
href: link.href,
|
|
927
|
+
target: "_blank",
|
|
928
|
+
rel: "noopener noreferrer",
|
|
929
|
+
children: `${link.label} ↗`
|
|
930
|
+
}) : /*#__PURE__*/ _jsx(AppLink, {
|
|
931
|
+
href: link.href,
|
|
932
|
+
children: link.label
|
|
933
|
+
})
|
|
934
|
+
}) : null,
|
|
935
|
+
bucket ? /*#__PURE__*/ _jsx(Typography, {
|
|
936
|
+
variant: "caption",
|
|
937
|
+
children: /*#__PURE__*/ _jsx(MuiLink, {
|
|
938
|
+
href: bucket,
|
|
939
|
+
target: "_blank",
|
|
940
|
+
rel: "noopener noreferrer",
|
|
941
|
+
children: 'Object in Cloud Storage ↗'
|
|
942
|
+
})
|
|
943
|
+
}) : null
|
|
944
|
+
]
|
|
945
|
+
});
|
|
946
|
+
})()
|
|
947
|
+
]
|
|
948
|
+
}, item.id);
|
|
949
|
+
})
|
|
950
|
+
]
|
|
951
|
+
})
|
|
952
|
+
}),
|
|
953
|
+
/*#__PURE__*/ _jsx(CardDisplay, {
|
|
954
|
+
header: "Review verdict",
|
|
955
|
+
contentGutterX: true,
|
|
956
|
+
contentGutterY: true,
|
|
957
|
+
help: pluginDocsHelp('manifestAndEnvs', {
|
|
958
|
+
anchor: '#review--trust-lifecycle',
|
|
959
|
+
excerpt: 'Move this submission through the review lifecycle — list, verify, or reject with a reason.'
|
|
960
|
+
}),
|
|
961
|
+
children: /*#__PURE__*/ _jsxs(Stack, {
|
|
962
|
+
spacing: 2,
|
|
963
|
+
children: [
|
|
964
|
+
/*#__PURE__*/ _jsxs(Stack, {
|
|
965
|
+
spacing: 0.5,
|
|
966
|
+
children: [
|
|
967
|
+
/*#__PURE__*/ _jsxs(Stack, {
|
|
968
|
+
direction: "row",
|
|
969
|
+
spacing: 1,
|
|
970
|
+
sx: {
|
|
971
|
+
alignItems: 'center',
|
|
972
|
+
flexWrap: 'wrap'
|
|
973
|
+
},
|
|
974
|
+
children: [
|
|
975
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
976
|
+
variant: "body2",
|
|
977
|
+
children: 'Currently:'
|
|
978
|
+
}),
|
|
979
|
+
/*#__PURE__*/ _jsx(Chip, {
|
|
980
|
+
size: "small",
|
|
981
|
+
color: status.color,
|
|
982
|
+
label: status.label
|
|
983
|
+
}),
|
|
984
|
+
status.live ? /*#__PURE__*/ _jsx(Chip, {
|
|
985
|
+
size: "small",
|
|
986
|
+
color: "warning",
|
|
987
|
+
label: "Live to customers"
|
|
988
|
+
}) : /*#__PURE__*/ _jsx(Chip, {
|
|
989
|
+
size: "small",
|
|
990
|
+
variant: "outlined",
|
|
991
|
+
label: "Not installable"
|
|
992
|
+
})
|
|
993
|
+
]
|
|
994
|
+
}),
|
|
995
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
996
|
+
variant: "caption",
|
|
997
|
+
color: "text.secondary",
|
|
998
|
+
children: status.meaning
|
|
999
|
+
})
|
|
1000
|
+
]
|
|
1001
|
+
}),
|
|
1002
|
+
/*#__PURE__*/ _jsx(Divider, {}),
|
|
1003
|
+
/*#__PURE__*/ _jsxs(Stack, {
|
|
1004
|
+
spacing: 0.5,
|
|
1005
|
+
children: [
|
|
1006
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
1007
|
+
variant: "subtitle2",
|
|
1008
|
+
children: `Verdict on v${detail.reviewVersion}`
|
|
1009
|
+
}),
|
|
1010
|
+
/*#__PURE__*/ _jsxs(Stack, {
|
|
1011
|
+
direction: "row",
|
|
1012
|
+
spacing: 1,
|
|
1013
|
+
sx: {
|
|
1014
|
+
alignItems: 'center',
|
|
1015
|
+
flexWrap: 'wrap'
|
|
1016
|
+
},
|
|
1017
|
+
children: [
|
|
1018
|
+
/*#__PURE__*/ _jsx(TextField, {
|
|
1019
|
+
size: "small",
|
|
1020
|
+
select: true,
|
|
1021
|
+
value: detail.reviewVersion,
|
|
1022
|
+
onChange: (event)=>setSelectedVersion(event.target.value),
|
|
1023
|
+
sx: {
|
|
1024
|
+
minWidth: 160
|
|
1025
|
+
},
|
|
1026
|
+
children: detail.versions.map((entry)=>/*#__PURE__*/ _jsx(MenuItem, {
|
|
1027
|
+
value: entry.version,
|
|
1028
|
+
children: `v${entry.version} · ${entry.reviewState}`
|
|
1029
|
+
}, entry.version))
|
|
1030
|
+
}),
|
|
1031
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
1032
|
+
size: "small",
|
|
1033
|
+
variant: "contained",
|
|
1034
|
+
color: "success",
|
|
1035
|
+
disabled: busy || blocked,
|
|
1036
|
+
onClick: ()=>void post({
|
|
1037
|
+
action: 'approve-version',
|
|
1038
|
+
version: detail.reviewVersion
|
|
1039
|
+
}, `v${detail.reviewVersion} approved`),
|
|
1040
|
+
children: 'Approve version'
|
|
1041
|
+
}),
|
|
1042
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
1043
|
+
size: "small",
|
|
1044
|
+
color: "error",
|
|
1045
|
+
disabled: busy,
|
|
1046
|
+
onClick: ()=>void post({
|
|
1047
|
+
action: 'reject-version',
|
|
1048
|
+
version: detail.reviewVersion,
|
|
1049
|
+
reason
|
|
1050
|
+
}, `v${detail.reviewVersion} rejected`),
|
|
1051
|
+
children: 'Reject version'
|
|
1052
|
+
})
|
|
1053
|
+
]
|
|
1054
|
+
}),
|
|
1055
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
1056
|
+
variant: "caption",
|
|
1057
|
+
color: "text.secondary",
|
|
1058
|
+
children: 'Approving makes these bytes the version new ' + 'installs receive. Existing installs are pinned and ' + 'do not move. A pending version is never installed ' + 'by anyone but its publisher, so an update cannot ' + 'ship past review.'
|
|
1059
|
+
}),
|
|
1060
|
+
blocked ? /*#__PURE__*/ _jsx(Alert, {
|
|
1061
|
+
severity: "info",
|
|
1062
|
+
sx: {
|
|
1063
|
+
mt: 0.5
|
|
1064
|
+
},
|
|
1065
|
+
children: `Blocked: ${detail.checklistOutstanding.length} required checklist item(s) outstanding for these bytes.`
|
|
1066
|
+
}) : null,
|
|
1067
|
+
liveOnReviewVersion > 0 ? /*#__PURE__*/ _jsx(Alert, {
|
|
1068
|
+
severity: (reviewEntry == null ? void 0 : reviewEntry.revoked) ? 'success' : 'warning',
|
|
1069
|
+
sx: {
|
|
1070
|
+
mt: 0.5
|
|
1071
|
+
},
|
|
1072
|
+
action: /*#__PURE__*/ _jsx(Button, {
|
|
1073
|
+
size: "small",
|
|
1074
|
+
color: "inherit",
|
|
1075
|
+
disabled: busy,
|
|
1076
|
+
onClick: ()=>void setVersionRevoked(detail.reviewVersion, !(reviewEntry == null ? void 0 : reviewEntry.revoked)),
|
|
1077
|
+
children: (reviewEntry == null ? void 0 : reviewEntry.revoked) ? 'Allow again' : 'Stop these bytes'
|
|
1078
|
+
}),
|
|
1079
|
+
children: (reviewEntry == null ? void 0 : reviewEntry.revoked) ? `v${detail.reviewVersion} is stopped. The ` + `${liveOnReviewVersion} site${liveOnReviewVersion === 1 ? ' pinned to it renders' : 's pinned to it render'} a placeholder instead.` : `${liveOnReviewVersion} site${liveOnReviewVersion === 1 ? ' is' : 's are'} running v${detail.reviewVersion} right now. ` + 'Rejecting it stops new installs but leaves ' + 'those running — stop the bytes if they should ' + 'not be executing.'
|
|
1080
|
+
}) : null
|
|
1081
|
+
]
|
|
1082
|
+
}),
|
|
1083
|
+
/*#__PURE__*/ _jsx(Divider, {}),
|
|
1084
|
+
/*#__PURE__*/ _jsxs(Stack, {
|
|
1085
|
+
spacing: 0.5,
|
|
1086
|
+
children: [
|
|
1087
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
1088
|
+
variant: "subtitle2",
|
|
1089
|
+
children: 'Marketplace listing'
|
|
1090
|
+
}),
|
|
1091
|
+
/*#__PURE__*/ _jsxs(Stack, {
|
|
1092
|
+
direction: "row",
|
|
1093
|
+
spacing: 1,
|
|
1094
|
+
sx: {
|
|
1095
|
+
alignItems: 'center',
|
|
1096
|
+
flexWrap: 'wrap'
|
|
1097
|
+
},
|
|
1098
|
+
children: [
|
|
1099
|
+
![
|
|
1100
|
+
'listed',
|
|
1101
|
+
'verified'
|
|
1102
|
+
].includes(detail.reviewStatus) ? /*#__PURE__*/ _jsx(Tooltip, {
|
|
1103
|
+
title: blocked ? `Complete the review checklist first — ` + `${detail.checklistOutstanding.length} required ` + `item(s) outstanding for this version's bytes` : 'Makes this plugin browsable in the ' + 'marketplace and installable by every ' + 'workspace. This is the step that puts the ' + 'code in front of customers.',
|
|
1104
|
+
children: /*#__PURE__*/ _jsx("span", {
|
|
1105
|
+
children: /*#__PURE__*/ _jsx(Button, {
|
|
1106
|
+
size: "small",
|
|
1107
|
+
color: "warning",
|
|
1108
|
+
variant: "outlined",
|
|
1109
|
+
disabled: busy || blocked,
|
|
1110
|
+
onClick: ()=>void post({
|
|
1111
|
+
action: 'list'
|
|
1112
|
+
}, 'Listed — installable by every workspace'),
|
|
1113
|
+
children: 'List in marketplace'
|
|
1114
|
+
})
|
|
1115
|
+
})
|
|
1116
|
+
}) : null,
|
|
1117
|
+
detail.reviewStatus === 'submitted' ? /*#__PURE__*/ _jsx(Button, {
|
|
1118
|
+
size: "small",
|
|
1119
|
+
variant: "outlined",
|
|
1120
|
+
disabled: busy,
|
|
1121
|
+
onClick: ()=>void post({
|
|
1122
|
+
action: 'start-review'
|
|
1123
|
+
}, 'Marked in review'),
|
|
1124
|
+
children: 'Start review'
|
|
1125
|
+
}) : null,
|
|
1126
|
+
detail.reviewStatus !== 'verified' ? // Say WHY it is disabled, at the button (AGL-1121).
|
|
1127
|
+
// The checklist alert lives elsewhere on the page, so
|
|
1128
|
+
// a reviewer met a greyed-out control with no stated
|
|
1129
|
+
// cause and reasonably read it as broken. The tooltip
|
|
1130
|
+
// names the count and where to clear it; a disabled
|
|
1131
|
+
// MUI Button swallows pointer events, so the wrapper
|
|
1132
|
+
// is what the tooltip can attach to.
|
|
1133
|
+
/*#__PURE__*/ _jsx(Tooltip, {
|
|
1134
|
+
title: blocked ? `Complete the review checklist first — ` + `${detail.checklistOutstanding.length} required ` + `item(s) outstanding for this version's bytes` : '',
|
|
1135
|
+
children: /*#__PURE__*/ _jsx("span", {
|
|
1136
|
+
children: /*#__PURE__*/ _jsx(Button, {
|
|
1137
|
+
size: "small",
|
|
1138
|
+
color: "success",
|
|
1139
|
+
variant: "outlined",
|
|
1140
|
+
disabled: busy || blocked,
|
|
1141
|
+
onClick: ()=>void post({
|
|
1142
|
+
action: 'verify'
|
|
1143
|
+
}, 'Verified'),
|
|
1144
|
+
children: 'Verify ✓ (badge)'
|
|
1145
|
+
})
|
|
1146
|
+
})
|
|
1147
|
+
}) : null,
|
|
1148
|
+
((_detail_verificationRequest = detail.verificationRequest) == null ? void 0 : _detail_verificationRequest.declineReason) ? /*#__PURE__*/ _jsx(Chip, {
|
|
1149
|
+
size: "small",
|
|
1150
|
+
color: "warning",
|
|
1151
|
+
variant: "outlined",
|
|
1152
|
+
sx: {
|
|
1153
|
+
maxWidth: 420
|
|
1154
|
+
},
|
|
1155
|
+
label: `Previously declined: ${detail.verificationRequest.declineReason}`
|
|
1156
|
+
}) : null,
|
|
1157
|
+
((_detail_verificationRequest1 = detail.verificationRequest) == null ? void 0 : _detail_verificationRequest1.state) === 'pending' ? /*#__PURE__*/ _jsx(Button, {
|
|
1158
|
+
size: "small",
|
|
1159
|
+
color: "warning",
|
|
1160
|
+
variant: "outlined",
|
|
1161
|
+
disabled: busy,
|
|
1162
|
+
onClick: ()=>{
|
|
1163
|
+
const reason = window.prompt('Why is verification declined? The publisher is ' + 'told this, and cannot ask again for 30 days.');
|
|
1164
|
+
// `prompt` returns null on cancel and '' on an
|
|
1165
|
+
// empty submit. Neither is a decline — the server
|
|
1166
|
+
// requires a reason, and sending one anyway would
|
|
1167
|
+
// surface as an error the reviewer did not cause.
|
|
1168
|
+
if (!(reason == null ? void 0 : reason.trim())) return;
|
|
1169
|
+
void post({
|
|
1170
|
+
action: 'decline-verification',
|
|
1171
|
+
reason
|
|
1172
|
+
}, 'Verification declined');
|
|
1173
|
+
},
|
|
1174
|
+
children: 'Decline verification'
|
|
1175
|
+
}) : null,
|
|
1176
|
+
detail.private ? /*#__PURE__*/ _jsx(Chip, {
|
|
1177
|
+
size: "small",
|
|
1178
|
+
label: "Private — never listed"
|
|
1179
|
+
}) : null
|
|
1180
|
+
]
|
|
1181
|
+
})
|
|
1182
|
+
]
|
|
1183
|
+
}),
|
|
1184
|
+
/*#__PURE__*/ _jsx(Divider, {}),
|
|
1185
|
+
/*#__PURE__*/ _jsxs(Stack, {
|
|
1186
|
+
spacing: 0.5,
|
|
1187
|
+
children: [
|
|
1188
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
1189
|
+
variant: "subtitle2",
|
|
1190
|
+
children: 'Step back'
|
|
1191
|
+
}),
|
|
1192
|
+
/*#__PURE__*/ _jsxs(Stack, {
|
|
1193
|
+
direction: "row",
|
|
1194
|
+
spacing: 1,
|
|
1195
|
+
sx: {
|
|
1196
|
+
alignItems: 'center',
|
|
1197
|
+
flexWrap: 'wrap'
|
|
1198
|
+
},
|
|
1199
|
+
children: [
|
|
1200
|
+
/*#__PURE__*/ _jsxs(TextField, {
|
|
1201
|
+
select: true,
|
|
1202
|
+
size: "small",
|
|
1203
|
+
label: "Rejection reason",
|
|
1204
|
+
value: rejectCategory,
|
|
1205
|
+
onChange: (event)=>setRejectCategory(event.target.value),
|
|
1206
|
+
// `displayEmpty` is not enough on a MUI Select: an
|
|
1207
|
+
// empty value renders NOTHING without a rendered
|
|
1208
|
+
// option, so the field reads as broken.
|
|
1209
|
+
slotProps: {
|
|
1210
|
+
select: {
|
|
1211
|
+
displayEmpty: true
|
|
1212
|
+
},
|
|
1213
|
+
inputLabel: {
|
|
1214
|
+
shrink: true
|
|
1215
|
+
}
|
|
1216
|
+
},
|
|
1217
|
+
sx: {
|
|
1218
|
+
minWidth: 260
|
|
1219
|
+
},
|
|
1220
|
+
children: [
|
|
1221
|
+
/*#__PURE__*/ _jsx(MenuItem, {
|
|
1222
|
+
value: "",
|
|
1223
|
+
children: 'Select a reason…'
|
|
1224
|
+
}),
|
|
1225
|
+
PLUGIN_REJECTION_CATEGORIES.map((entry)=>/*#__PURE__*/ _jsx(MenuItem, {
|
|
1226
|
+
value: entry.id,
|
|
1227
|
+
children: entry.label
|
|
1228
|
+
}, entry.id))
|
|
1229
|
+
]
|
|
1230
|
+
}),
|
|
1231
|
+
/*#__PURE__*/ _jsx(TextField, {
|
|
1232
|
+
size: "small",
|
|
1233
|
+
placeholder: rejectCategoryNeedsComment ? 'Comment (required for “Other”)' : 'Comment (optional)',
|
|
1234
|
+
value: reason,
|
|
1235
|
+
onChange: (event)=>setReason(event.target.value),
|
|
1236
|
+
sx: {
|
|
1237
|
+
minWidth: 260
|
|
1238
|
+
}
|
|
1239
|
+
}),
|
|
1240
|
+
status.live ? /*#__PURE__*/ _jsx(Button, {
|
|
1241
|
+
size: "small",
|
|
1242
|
+
color: "warning",
|
|
1243
|
+
disabled: busy,
|
|
1244
|
+
onClick: ()=>void post({
|
|
1245
|
+
action: 'delist',
|
|
1246
|
+
reason
|
|
1247
|
+
}, 'Delisted — back in review'),
|
|
1248
|
+
children: 'Delist'
|
|
1249
|
+
}) : null,
|
|
1250
|
+
detail.reviewStatus === 'verified' ? /*#__PURE__*/ _jsx(Button, {
|
|
1251
|
+
size: "small",
|
|
1252
|
+
disabled: busy,
|
|
1253
|
+
onClick: ()=>void post({
|
|
1254
|
+
action: 'unverify'
|
|
1255
|
+
}, 'Badge removed'),
|
|
1256
|
+
children: 'Unverify'
|
|
1257
|
+
}) : null,
|
|
1258
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
1259
|
+
size: "small",
|
|
1260
|
+
color: "error",
|
|
1261
|
+
// Disabled rather than left to fail server-side: the
|
|
1262
|
+
// route validates too, but a reviewer should not have
|
|
1263
|
+
// to press a button to learn the form is incomplete.
|
|
1264
|
+
disabled: busy || Boolean(rejectBlocked),
|
|
1265
|
+
onClick: ()=>void post({
|
|
1266
|
+
action: 'reject',
|
|
1267
|
+
reason,
|
|
1268
|
+
category: rejectCategory
|
|
1269
|
+
}, 'Rejected'),
|
|
1270
|
+
children: 'Reject'
|
|
1271
|
+
})
|
|
1272
|
+
]
|
|
1273
|
+
}),
|
|
1274
|
+
rejectBlocked ? /*#__PURE__*/ _jsx(Typography, {
|
|
1275
|
+
variant: "caption",
|
|
1276
|
+
color: "text.secondary",
|
|
1277
|
+
children: rejectBlocked
|
|
1278
|
+
}) : null,
|
|
1279
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
1280
|
+
variant: "caption",
|
|
1281
|
+
color: "text.secondary",
|
|
1282
|
+
children: 'Delist pulls it from the marketplace and blocks new ' + 'installs; existing installs keep working. Unverify ' + 'only drops the badge. Reject notifies the publisher. ' + 'None of these stop code already running — that is ' + 'the danger zone.'
|
|
1283
|
+
})
|
|
1284
|
+
]
|
|
1285
|
+
})
|
|
1286
|
+
]
|
|
1287
|
+
})
|
|
1288
|
+
}),
|
|
1289
|
+
/*#__PURE__*/ _jsx(CardDisplay, {
|
|
1290
|
+
header: "Versions",
|
|
1291
|
+
help: pluginDocsHelp('publisherHandbook', {
|
|
1292
|
+
anchor: '#versioning--updates',
|
|
1293
|
+
excerpt: 'Every version this publisher has submitted. Review lives on the ' + 'VERSION, so each one is judged on its own.'
|
|
1294
|
+
}),
|
|
1295
|
+
contentGutterX: true,
|
|
1296
|
+
contentGutterY: true,
|
|
1297
|
+
children: /*#__PURE__*/ _jsxs(Stack, {
|
|
1298
|
+
spacing: 1,
|
|
1299
|
+
children: [
|
|
1300
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
1301
|
+
variant: "body2",
|
|
1302
|
+
color: "text.secondary",
|
|
1303
|
+
children: 'Granting realm trust signs a version to run inside ' + 'the app realm instead of the sandbox iframe. ' + 'Super-staff only, audited.'
|
|
1304
|
+
}),
|
|
1305
|
+
/*#__PURE__*/ _jsx(Alert, {
|
|
1306
|
+
severity: "info",
|
|
1307
|
+
children: 'This is a different axis from the review verdict above. ' + 'Listing and verifying apply to the whole listing and ' + 'decide who may install it. Trust applies to ONE ' + 'version and decides where its code runs: sandboxed in ' + 'a cross-origin iframe capped by the manifest CSP, or ' + 'inside the app realm with neither of those between it ' + 'and user data. A verified plugin can be sandboxed, and ' + 'a sandboxed version of a verified plugin is the normal ' + 'case — realm trust is the exception that needs a ' + 'reason.'
|
|
1308
|
+
}),
|
|
1309
|
+
detail.versions.map((entry)=>{
|
|
1310
|
+
var _superGate_reason;
|
|
1311
|
+
return /*#__PURE__*/ _jsxs(Stack, {
|
|
1312
|
+
direction: "row",
|
|
1313
|
+
spacing: 1,
|
|
1314
|
+
sx: {
|
|
1315
|
+
alignItems: 'center',
|
|
1316
|
+
flexWrap: 'wrap'
|
|
1317
|
+
},
|
|
1318
|
+
children: [
|
|
1319
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
1320
|
+
variant: "body2",
|
|
1321
|
+
sx: {
|
|
1322
|
+
minWidth: 64
|
|
1323
|
+
},
|
|
1324
|
+
children: `v${entry.version}`
|
|
1325
|
+
}),
|
|
1326
|
+
entry.version === detail.latestVersion ? /*#__PURE__*/ _jsx(Chip, {
|
|
1327
|
+
size: "small",
|
|
1328
|
+
label: "Latest"
|
|
1329
|
+
}) : null,
|
|
1330
|
+
/*#__PURE__*/ _jsx(Chip, {
|
|
1331
|
+
size: "small",
|
|
1332
|
+
color: entry.reviewState === 'approved' ? 'success' : entry.reviewState === 'rejected' ? 'error' : 'warning',
|
|
1333
|
+
variant: "outlined",
|
|
1334
|
+
label: entry.grandfathered ? 'approved (grandfathered)' : entry.reviewState
|
|
1335
|
+
}),
|
|
1336
|
+
/*#__PURE__*/ _jsx(Chip, {
|
|
1337
|
+
size: "small",
|
|
1338
|
+
color: entry.trust === 'realm' ? 'success' : 'default',
|
|
1339
|
+
variant: entry.trust === 'realm' ? 'filled' : 'outlined',
|
|
1340
|
+
label: entry.trust === 'realm' ? 'Realm-trusted' : 'Sandboxed'
|
|
1341
|
+
}),
|
|
1342
|
+
entry.revoked ? /*#__PURE__*/ _jsx(Chip, {
|
|
1343
|
+
size: "small",
|
|
1344
|
+
color: "error",
|
|
1345
|
+
label: "Stopped"
|
|
1346
|
+
}) : entry.reviewState === 'rejected' && entry.activeInstalls > 0 ? /*#__PURE__*/ _jsx(Chip, {
|
|
1347
|
+
size: "small",
|
|
1348
|
+
color: "error",
|
|
1349
|
+
variant: "outlined",
|
|
1350
|
+
label: "Rejected but still running"
|
|
1351
|
+
}) : null,
|
|
1352
|
+
/*#__PURE__*/ _jsxs(Typography, {
|
|
1353
|
+
variant: "caption",
|
|
1354
|
+
color: "text.secondary",
|
|
1355
|
+
children: [
|
|
1356
|
+
entry.publishedAt ? new Date(entry.publishedAt).toLocaleDateString() : '—',
|
|
1357
|
+
` · ${entry.sha256.slice(0, 12)}`,
|
|
1358
|
+
entry.activeInstalls ? ` · ${entry.activeInstalls} pinned here` : ''
|
|
1359
|
+
]
|
|
1360
|
+
}),
|
|
1361
|
+
/*#__PURE__*/ _jsx(BlockedControl, {
|
|
1362
|
+
blocked: superGate.blocked,
|
|
1363
|
+
reason: (_superGate_reason = superGate.reason) != null ? _superGate_reason : SUPER_STAFF_ONLY_REASON,
|
|
1364
|
+
children: /*#__PURE__*/ _jsx(Button, {
|
|
1365
|
+
size: "small",
|
|
1366
|
+
color: entry.trust === 'realm' ? 'error' : 'success',
|
|
1367
|
+
disabled: busy,
|
|
1368
|
+
onClick: ()=>void signRealm(entry.version, entry.trust === 'realm' ? 'revoke' : 'grant'),
|
|
1369
|
+
children: entry.trust === 'realm' ? 'Revoke realm trust' : 'Grant realm trust'
|
|
1370
|
+
})
|
|
1371
|
+
}),
|
|
1372
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
1373
|
+
size: "small",
|
|
1374
|
+
color: entry.revoked ? 'success' : 'error',
|
|
1375
|
+
disabled: busy,
|
|
1376
|
+
onClick: ()=>void setVersionRevoked(entry.version, !entry.revoked),
|
|
1377
|
+
children: entry.revoked ? 'Allow again' : 'Stop this version'
|
|
1378
|
+
})
|
|
1379
|
+
]
|
|
1380
|
+
}, entry.version);
|
|
1381
|
+
})
|
|
1382
|
+
]
|
|
1383
|
+
})
|
|
1384
|
+
}),
|
|
1385
|
+
/*#__PURE__*/ _jsx(CardDisplay, {
|
|
1386
|
+
header: "Danger zone",
|
|
1387
|
+
help: pluginDocsHelp('publisherHandbook', {
|
|
1388
|
+
anchor: '#review-what-happens-after-you-publish',
|
|
1389
|
+
excerpt: 'Rejecting a version is not a kill switch — it stops new installs ' + 'and leaves existing ones running.'
|
|
1390
|
+
}),
|
|
1391
|
+
contentGutterX: true,
|
|
1392
|
+
contentGutterY: true,
|
|
1393
|
+
children: /*#__PURE__*/ _jsxs(Stack, {
|
|
1394
|
+
spacing: 1.5,
|
|
1395
|
+
children: [
|
|
1396
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
1397
|
+
variant: "body2",
|
|
1398
|
+
color: "text.secondary",
|
|
1399
|
+
children: 'Taking a plugin down de-lists it AND writes the kill ' + 'switch: every workspace that already installed it ' + 'stops loading it on the next render, and new ' + 'installs are refused. Restoring clears both.'
|
|
1400
|
+
}),
|
|
1401
|
+
/*#__PURE__*/ _jsxs(Stack, {
|
|
1402
|
+
direction: "row",
|
|
1403
|
+
spacing: 1,
|
|
1404
|
+
sx: {
|
|
1405
|
+
alignItems: 'center',
|
|
1406
|
+
flexWrap: 'wrap'
|
|
1407
|
+
},
|
|
1408
|
+
children: [
|
|
1409
|
+
detail.hidden ? null : /*#__PURE__*/ _jsx(TextField, {
|
|
1410
|
+
size: "small",
|
|
1411
|
+
placeholder: "Takedown reason",
|
|
1412
|
+
value: takedownReason,
|
|
1413
|
+
onChange: (event)=>setTakedownReason(event.target.value),
|
|
1414
|
+
sx: {
|
|
1415
|
+
minWidth: 260
|
|
1416
|
+
}
|
|
1417
|
+
}),
|
|
1418
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
1419
|
+
size: "small",
|
|
1420
|
+
variant: detail.hidden ? 'text' : 'outlined',
|
|
1421
|
+
color: detail.hidden ? 'success' : 'error',
|
|
1422
|
+
disabled: busy,
|
|
1423
|
+
onClick: ()=>void takedown(),
|
|
1424
|
+
children: detail.hidden ? 'Restore listing' : 'Take down'
|
|
1425
|
+
})
|
|
1426
|
+
]
|
|
1427
|
+
})
|
|
1428
|
+
]
|
|
1429
|
+
})
|
|
1430
|
+
})
|
|
1431
|
+
]
|
|
1432
|
+
})
|
|
1433
|
+
]
|
|
1434
|
+
});
|
|
1435
|
+
}
|
|
1436
|
+
export default PluginReviewDetail;
|
|
1437
|
+
|
|
1438
|
+
//# sourceMappingURL=plugin-review-detail.component.js.map
|