@dev.fast/whiteboard 0.4.0 → 0.4.1-preview.20261026.138
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/dist/{account-alias-2yTAxoBx.js → account-alias-BPlPnzQM.js} +7 -7
- package/dist/{agent-cli-CHNDd4Ga.js → agent-cli-BbCf9uid.js} +11 -11
- package/dist/agent-cli-ipN_JmzG.js +2 -0
- package/dist/{agent-client-Dc0vHFJt.js → agent-client-DfpM8F5N.js} +3 -3
- package/dist/{authoring-tools-D8EkIDjm.js → authoring-tools-bm39DRoV.js} +23 -10
- package/dist/{background-server-DyWnPJyZ.js → background-server--0EeCCeV.js} +1 -1
- package/dist/{background-server-DHWHzpo5.js → background-server-B--5p72o.js} +6 -6
- package/dist/build-info.json +1 -1
- package/dist/{cli-qZomwJmK.js → cli-DBTQIroM.js} +2 -2
- package/dist/{cli-runner-DFBaVGij.js → cli-runner-CRFCdb7G.js} +25 -25
- package/dist/cli.js +3 -3
- package/dist/{fuzzy-match-CfSxqzBF.js → fuzzy-match-l3mJZ1Ge.js} +5 -5
- package/dist/{headless-host-dwlqhqMY.js → headless-host-5ei3nHS8.js} +1 -1
- package/dist/{headless-host-DJhcab6r.js → headless-host-BkJ9OUQY.js} +131 -299
- package/dist/{client-p9WB8Sj0.js → import-Bwvf9Wos.js} +505 -390
- package/dist/{lens-selection-9byzEgvU.js → lens-selection-EuZdag4Q.js} +1 -1
- package/dist/{local-data-DHedfhxw.js → local-data-BCSeTyic.js} +98 -154
- package/dist/{mcp-Br-r_wwR.js → mcp-_Xg1gQuA.js} +17 -8
- package/dist/{package-paths-BeYHYjeR.js → package-paths-Bg731FHS.js} +2 -2
- package/dist/profile-C8zL4u3L.js +2 -0
- package/dist/{profile-D9wdlPgw.js → profile-xzrUFgbT.js} +4 -4
- package/dist/{remote-attach-DSGpTOJ4.js → remote-attach-DF25Epoa.js} +4 -4
- package/dist/{remote-extensions-WtJO7pkb.js → remote-extensions-BJamgsCe.js} +1 -1
- package/dist/{remote-extensions-CsI_BJ_f.js → remote-extensions-CeywZL-Z.js} +4 -4
- package/dist/{remote-language-server-Dx13Mg2m.js → remote-language-server-CH8sG_CU.js} +6 -6
- package/dist/{remote-uninstall-Dp2o3qz0.js → remote-uninstall-Cq-lkBY7.js} +7 -7
- package/dist/{request-origin-CNSLVn7x.js → request-origin-CargJFvH.js} +14 -3
- package/dist/{review-app-launcher-BZAnR8Vp.js → review-app-launcher-D8ApNDUV.js} +3 -3
- package/dist/{review-home-paths-CG-sb9Cw.js → review-home-paths-DcB4xLD-.js} +2 -2
- package/dist/{review-telemetry-BM-_KdLi.js → review-telemetry-BW7gJrSo.js} +7 -7
- package/dist/{runtime-C6_1K-3Z.js → runtime-DY6Ctyog.js} +1 -1
- package/dist/runtime.js +1 -1
- package/dist/{s3-CsG2EWED.js → s3-BIWv4h1J.js} +2 -2
- package/dist/{s3-config-zz4GWh2T.js → s3-config-Du3RBIvv.js} +1 -1
- package/dist/server/desktop-host.js +51 -17
- package/dist/{server-discovery-DKLIOgg3.js → server-discovery-aHK_1r3U.js} +5 -5
- package/dist/sharing/index.d.ts +209 -98
- package/dist/sharing/index.js +1 -1
- package/dist/{src-DYHVzX3b.js → src-DEOc8bvC.js} +63 -2
- package/dist/{src-Z1x4-Qmb.js → src-DjRaCctk.js} +6 -6
- package/dist/{stored-document-migration-DEt8iwem.js → stored-document-migration-CwmTwTb8.js} +139 -13
- package/dist/{thread-state-CO97FUqK.js → thread-state-CbLB0jUA.js} +1 -1
- package/dist/{tool-failure-C1L1vJyz.js → tool-failure-DZaQzi4f.js} +2 -2
- package/instructions/animation.md +345 -0
- package/instructions/authoring.md +2 -2
- package/package.json +6 -6
- package/src/fixtures/blocks/animation.json +19 -0
- package/src/review-api/authoring-tools.ts +17 -6
- package/src/review-api/blocks/animation.ts +98 -0
- package/src/review-api/blocks/index.ts +3 -0
- package/src/review-api/blocks/sequence.ts +3 -1
- package/src/review-api/comparison-coverage.ts +120 -89
- package/src/review-api/component-reference.ts +16 -0
- package/src/review-api/document-text.ts +11 -0
- package/src/review-api/document.ts +26 -5
- package/src/review-api/http.ts +31 -2
- package/src/review-api/instructions.ts +11 -2
- package/src/review-api/local-data.ts +21 -4
- package/src/review-api/mcp.ts +22 -14
- package/src/review-api/onboarding.ts +100 -26
- package/src/review-preferences.ts +43 -0
- package/src/server/desktop-server.ts +24 -0
- package/src/server/json-review-reporting.ts +1 -1
- package/src/server/review-server-core.ts +2 -0
- package/src/server/structural-comparisons.ts +5 -11
- package/src/server/structural-diff.ts +8 -2
- package/src/sharing/cli.ts +1 -1
- package/src/sharing/client.ts +54 -222
- package/src/sharing/export.ts +172 -114
- package/src/sharing/host.ts +32 -36
- package/src/sharing/import.ts +93 -147
- package/src/sharing/index.ts +1 -6
- package/src/sharing/routes.ts +18 -17
- package/src/ui-telemetry-events.ts +11 -0
- package/dist/agent-cli-Cn4-16XA.js +0 -2
- package/dist/profile-BopGMZUh.js +0 -2
- package/dist/{s3-config-CjcLyFV5.js → s3-config-D7Yt0VhS.js} +1 -1
- package/dist/{src-X9ZebeOD.js → src-DXnn7RrI.js} +1 -1
package/src/sharing/export.ts
CHANGED
|
@@ -1,18 +1,19 @@
|
|
|
1
|
-
import { createHash } from "node:crypto";
|
|
2
|
-
|
|
3
1
|
import {
|
|
4
2
|
MAX_SHARE_BYTES,
|
|
5
|
-
MAX_SHARE_MANIFEST_BYTES,
|
|
6
|
-
MAX_SHARE_OBJECT_BYTES,
|
|
7
3
|
SHARE_FORMAT,
|
|
8
|
-
type
|
|
9
|
-
|
|
4
|
+
type ShareBundle,
|
|
5
|
+
type ShareSource,
|
|
10
6
|
} from "@dev.fast/review-share-protocol";
|
|
11
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
type LensSource,
|
|
9
|
+
selectionKey,
|
|
10
|
+
sourceAnchors,
|
|
11
|
+
} from "@review/lens-selection.js";
|
|
12
12
|
import { markdownNodes, parseMarkdown } from "@review/markdown.js";
|
|
13
13
|
import { lensSelections } from "@review/review-api/diff-lenses.js";
|
|
14
|
-
import { ReviewInputError } from "@review/review-api/document.js";
|
|
15
14
|
import {
|
|
15
|
+
type Pins,
|
|
16
|
+
ReviewInputError,
|
|
16
17
|
anchorPins,
|
|
17
18
|
boardRepositoryId,
|
|
18
19
|
checkReferences,
|
|
@@ -21,28 +22,13 @@ import {
|
|
|
21
22
|
readsAtBoardPins,
|
|
22
23
|
resourceReference,
|
|
23
24
|
resourceReferences,
|
|
25
|
+
selectionReferences,
|
|
24
26
|
sourceReferences,
|
|
25
27
|
} from "@review/review-api/document.js";
|
|
26
28
|
import type { LocalReviewData } from "@review/review-api/local-data.js";
|
|
29
|
+
import { reviewProgress } from "@review/review-api/review-progress.js";
|
|
27
30
|
import type { ReviewStore, Snapshot } from "@review/review-api/store.js";
|
|
28
|
-
import {
|
|
29
|
-
|
|
30
|
-
export interface ShareBundle {
|
|
31
|
-
manifest: ShareManifest;
|
|
32
|
-
objects: Map<string, Uint8Array>;
|
|
33
|
-
attribution?: { login: string; sharedAt: number };
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export type ShareExportStore = Pick<ReviewStore, "read" | "resource">;
|
|
37
|
-
|
|
38
|
-
export type ShareExportData = Pick<
|
|
39
|
-
LocalReviewData,
|
|
40
|
-
"map" | "validateResource" | "validateSource"
|
|
41
|
-
>;
|
|
42
|
-
|
|
43
|
-
export function digestBytes(bytes: Uint8Array): string {
|
|
44
|
-
return createHash("sha256").update(bytes).digest("hex");
|
|
45
|
-
}
|
|
31
|
+
import type { FileLineRange } from "@review/source.js";
|
|
46
32
|
|
|
47
33
|
export function validateShareSources(
|
|
48
34
|
snapshot: Pick<Snapshot, "pins" | "document" | "lenses">,
|
|
@@ -55,8 +41,8 @@ export function validateShareSources(
|
|
|
55
41
|
}
|
|
56
42
|
|
|
57
43
|
/** The commits a board's references read at besides its base and head. A
|
|
58
|
-
* recipient fetches them too. A shared board names no repository of
|
|
59
|
-
* own, so one stands in for its commit links. */
|
|
44
|
+
* Desktop recipient fetches them too. A shared board names no repository of
|
|
45
|
+
* its own, so one stands in for its commit links. */
|
|
60
46
|
export function referenceCommits(
|
|
61
47
|
snapshot: Pick<Snapshot, "pins" | "document" | "target">,
|
|
62
48
|
repositoryId = boardRepositoryId(snapshot) ?? "shared",
|
|
@@ -74,13 +60,83 @@ export function referenceCommits(
|
|
|
74
60
|
return [...commits].sort();
|
|
75
61
|
}
|
|
76
62
|
|
|
63
|
+
interface ExcerptRange {
|
|
64
|
+
pins: Pins;
|
|
65
|
+
side: "base" | "head";
|
|
66
|
+
file: string;
|
|
67
|
+
fromLine: number;
|
|
68
|
+
toLine: number;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** One excerpt per run of overlapping or adjacent lines in a file at a commit. */
|
|
72
|
+
async function cutExcerpts(
|
|
73
|
+
data: {
|
|
74
|
+
quote(
|
|
75
|
+
pins: Pins,
|
|
76
|
+
range: Omit<ExcerptRange, "pins">,
|
|
77
|
+
): Promise<{ commit: string; text: string }>;
|
|
78
|
+
},
|
|
79
|
+
ranges: ExcerptRange[],
|
|
80
|
+
): Promise<ShareSource[]> {
|
|
81
|
+
const groups = new Map<string, ExcerptRange[]>();
|
|
82
|
+
|
|
83
|
+
for (const range of ranges) {
|
|
84
|
+
const key = JSON.stringify([range.pins[range.side], range.file]);
|
|
85
|
+
groups.set(key, [...(groups.get(key) ?? []), range]);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const merged = [...groups.values()].flatMap((group) =>
|
|
89
|
+
group
|
|
90
|
+
.toSorted((a, b) => a.fromLine - b.fromLine)
|
|
91
|
+
.reduce<ExcerptRange[]>((runs, range) => {
|
|
92
|
+
const last = runs.at(-1);
|
|
93
|
+
|
|
94
|
+
if (last && range.fromLine <= last.toLine + 1)
|
|
95
|
+
last.toLine = Math.max(last.toLine, range.toLine);
|
|
96
|
+
else runs.push({ ...range });
|
|
97
|
+
|
|
98
|
+
return runs;
|
|
99
|
+
}, []),
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
return Promise.all(
|
|
103
|
+
merged.map(async ({ pins, ...range }) => {
|
|
104
|
+
const quote = await data.quote(pins, range);
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
kind: "excerpt" as const,
|
|
108
|
+
file: range.file,
|
|
109
|
+
commit: quote.commit,
|
|
110
|
+
fromLine: range.fromLine,
|
|
111
|
+
lines: excerptLines(quote.text),
|
|
112
|
+
};
|
|
113
|
+
}),
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// jsonb cannot store NUL.
|
|
118
|
+
const excerptLines = (text: string) =>
|
|
119
|
+
text.split(/\r?\n/).map((line) => line.replaceAll("\0", "\uFFFD"));
|
|
120
|
+
|
|
121
|
+
/** The create request body, within the upload limit. */
|
|
122
|
+
export function shareRequestBody(bundle: ShareBundle, requestId: string) {
|
|
123
|
+
const body = JSON.stringify({ requestId, bundle });
|
|
124
|
+
|
|
125
|
+
if (Buffer.byteLength(body) > MAX_SHARE_BYTES)
|
|
126
|
+
throw new ReviewInputError(
|
|
127
|
+
`Review exceeds the ${MAX_SHARE_BYTES / 1024 / 1024} MB sharing limit. Remove large traces, then retry.`,
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
return body;
|
|
131
|
+
}
|
|
132
|
+
|
|
77
133
|
/** Freeze one stored version before any asynchronous source reads. */
|
|
78
134
|
export async function exportShare(input: {
|
|
79
|
-
store:
|
|
80
|
-
data:
|
|
135
|
+
store: ReviewStore;
|
|
136
|
+
data: LocalReviewData;
|
|
81
137
|
reviewId: string;
|
|
82
138
|
version?: number;
|
|
83
|
-
repository?:
|
|
139
|
+
repository?: ShareBundle["repository"];
|
|
84
140
|
}): Promise<ShareBundle> {
|
|
85
141
|
const stored = structuredClone(
|
|
86
142
|
input.store.read(input.reviewId, input.version),
|
|
@@ -106,48 +162,8 @@ export async function exportShare(input: {
|
|
|
106
162
|
|
|
107
163
|
documentSchema.parse(snapshot.document);
|
|
108
164
|
checkReferences(snapshot.document);
|
|
109
|
-
const objects = new Map<string, Uint8Array>();
|
|
110
|
-
let totalBytes = 0;
|
|
111
|
-
|
|
112
|
-
const add = (bytes: Uint8Array) => {
|
|
113
|
-
if (bytes.byteLength > MAX_SHARE_OBJECT_BYTES)
|
|
114
|
-
throw new ReviewInputError("A shared object exceeds the upload limit.");
|
|
115
|
-
const id = digestBytes(bytes);
|
|
116
|
-
|
|
117
|
-
if (!objects.has(id)) {
|
|
118
|
-
totalBytes += bytes.byteLength;
|
|
119
|
-
|
|
120
|
-
if (totalBytes > MAX_SHARE_BYTES)
|
|
121
|
-
throw new ReviewInputError("Review exceeds the sharing limit.");
|
|
122
|
-
objects.set(id, Uint8Array.from(bytes));
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
return id;
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
const json = <Value>(value: Value) => add(Buffer.from(JSON.stringify(value)));
|
|
129
|
-
const resources: ShareManifest["resources"] = [];
|
|
130
|
-
const maps: Record<string, Awaited<ReturnType<LocalReviewData["map"]>>> = {};
|
|
131
|
-
|
|
132
|
-
// Lens ranges travel with the snapshot and must resolve at its pins too.
|
|
133
|
-
const sources = [
|
|
134
|
-
...sourceReferences(snapshot.document, { repositoryId }),
|
|
135
|
-
...lensSelections(snapshot.lenses ?? []).flatMap((selection) =>
|
|
136
|
-
sourceAnchors(selection.source).map((source) => ({
|
|
137
|
-
...selection,
|
|
138
|
-
source,
|
|
139
|
-
peek: false,
|
|
140
|
-
})),
|
|
141
|
-
),
|
|
142
|
-
];
|
|
143
|
-
|
|
144
|
-
const pins = snapshot.pins;
|
|
145
|
-
|
|
146
165
|
validateShareSources(snapshot);
|
|
147
166
|
|
|
148
|
-
if ((pins || referenceCommits(stored).length) && !input.repository)
|
|
149
|
-
throw new ReviewInputError("Shared source pins require a repository.", 409);
|
|
150
|
-
|
|
151
167
|
for (const block of elements(snapshot.document)) {
|
|
152
168
|
if (block.type !== "markdown") continue;
|
|
153
169
|
|
|
@@ -158,68 +174,110 @@ export async function exportShare(input: {
|
|
|
158
174
|
);
|
|
159
175
|
}
|
|
160
176
|
|
|
177
|
+
const pins = snapshot.pins;
|
|
178
|
+
|
|
179
|
+
// Lens ranges travel with the snapshot and must resolve at its pins too.
|
|
180
|
+
const references: {
|
|
181
|
+
source: FileLineRange;
|
|
182
|
+
peek?: boolean;
|
|
183
|
+
}[] = [
|
|
184
|
+
...sourceReferences(snapshot.document, { repositoryId }),
|
|
185
|
+
...lensSelections(snapshot.lenses ?? []).flatMap((selection) =>
|
|
186
|
+
sourceAnchors(selection.source).map((source) => ({ source })),
|
|
187
|
+
),
|
|
188
|
+
];
|
|
189
|
+
|
|
190
|
+
const traces = new Map<string, unknown>();
|
|
191
|
+
const maps = new Map<string, Awaited<ReturnType<LocalReviewData["map"]>>>();
|
|
192
|
+
|
|
161
193
|
for (const block of resourceReferences(snapshot.document)) {
|
|
162
194
|
await input.data.validateResource(pins, block);
|
|
195
|
+
const id = resourceReference(block)?.id;
|
|
163
196
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
if (
|
|
167
|
-
!reference ||
|
|
168
|
-
resources.some((resource) => resource.id === reference.id)
|
|
169
|
-
)
|
|
197
|
+
if (!id || block.type === "image" || traces.has(id) || maps.has(id))
|
|
170
198
|
continue;
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
.parse(resource.mimeType),
|
|
179
|
-
object: add(resource.data),
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
if (block.type === "software_map") {
|
|
199
|
+
|
|
200
|
+
if (block.type === "trace_quote")
|
|
201
|
+
traces.set(
|
|
202
|
+
id,
|
|
203
|
+
JSON.parse(Buffer.from(input.store.resource(id).data).toString()),
|
|
204
|
+
);
|
|
205
|
+
else if (block.type === "software_map") {
|
|
183
206
|
const map = await input.data.map(pins, id);
|
|
184
|
-
|
|
207
|
+
maps.set(id, map);
|
|
185
208
|
|
|
186
209
|
for (const element of map.elements)
|
|
187
210
|
for (const range of element.sourceRanges ?? [])
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
source: { ...range, side: map.side },
|
|
191
|
-
});
|
|
192
|
-
}
|
|
211
|
+
references.push({ source: { ...range, side: map.side } });
|
|
212
|
+
} else throw new ReviewInputError("A shared resource has the wrong type.");
|
|
193
213
|
}
|
|
194
214
|
|
|
195
|
-
for (const reference of
|
|
215
|
+
for (const reference of references)
|
|
196
216
|
await input.data.validateSource(
|
|
197
217
|
anchorPins(reference.source, pins),
|
|
198
218
|
reference.source,
|
|
199
219
|
{ peek: reference.peek ?? false },
|
|
200
220
|
);
|
|
201
221
|
|
|
202
|
-
const
|
|
203
|
-
|
|
222
|
+
const { resolvedSelections } = await reviewProgress(
|
|
223
|
+
input.store,
|
|
224
|
+
input.data,
|
|
225
|
+
stored,
|
|
226
|
+
);
|
|
204
227
|
|
|
205
|
-
const
|
|
228
|
+
const selected = new Map<string, LensSource>(
|
|
229
|
+
[
|
|
230
|
+
...lensSelections(snapshot.lenses ?? []),
|
|
231
|
+
...selectionReferences(snapshot.document, {
|
|
232
|
+
tolerant: true,
|
|
233
|
+
repositoryId,
|
|
234
|
+
}),
|
|
235
|
+
].map(({ source }) => [selectionKey(source), source]),
|
|
236
|
+
);
|
|
237
|
+
|
|
238
|
+
const selections = Object.fromEntries(
|
|
239
|
+
Object.entries(resolvedSelections).map(([key, ranges]) => {
|
|
240
|
+
const own = selected.get(key)?.pins;
|
|
241
|
+
|
|
242
|
+
return [
|
|
243
|
+
key,
|
|
244
|
+
ranges.map((range) => ({ ...range, ...(own && { pins: own }) })),
|
|
245
|
+
];
|
|
246
|
+
}),
|
|
247
|
+
);
|
|
248
|
+
|
|
249
|
+
const excerpts: ExcerptRange[] = [
|
|
250
|
+
...Object.values(selections).flat(),
|
|
251
|
+
...[...maps.values()].flatMap((map) =>
|
|
252
|
+
map.elements.flatMap((element) =>
|
|
253
|
+
(element.sourceRanges ?? []).map((range) => ({
|
|
254
|
+
...range,
|
|
255
|
+
side: map.side,
|
|
256
|
+
})),
|
|
257
|
+
),
|
|
258
|
+
),
|
|
259
|
+
].map(({ side = "head", file, fromLine, toLine, ...range }) => ({
|
|
260
|
+
pins: anchorPins(range, pins),
|
|
261
|
+
side,
|
|
262
|
+
file,
|
|
263
|
+
fromLine,
|
|
264
|
+
toLine,
|
|
265
|
+
}));
|
|
266
|
+
|
|
267
|
+
return {
|
|
206
268
|
format: SHARE_FORMAT,
|
|
207
269
|
reviewId: snapshot.reviewId,
|
|
208
270
|
version: snapshot.version,
|
|
209
271
|
title: snapshot.title,
|
|
210
|
-
snapshot
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
if (Buffer.byteLength(JSON.stringify(manifest)) > MAX_SHARE_MANIFEST_BYTES)
|
|
222
|
-
throw new ReviewInputError("The share manifest exceeds the upload limit.");
|
|
223
|
-
|
|
224
|
-
return { manifest, objects };
|
|
272
|
+
snapshot,
|
|
273
|
+
images: {},
|
|
274
|
+
traces: Object.fromEntries(traces),
|
|
275
|
+
maps: Object.fromEntries(maps),
|
|
276
|
+
commits: pins
|
|
277
|
+
? await input.data.commits((await input.data.resolveSource(stored)).pins)
|
|
278
|
+
: [],
|
|
279
|
+
selections,
|
|
280
|
+
sources: await cutExcerpts(input.data, excerpts),
|
|
281
|
+
...(input.repository && { repository: input.repository }),
|
|
282
|
+
};
|
|
225
283
|
}
|
package/src/sharing/host.ts
CHANGED
|
@@ -17,7 +17,7 @@ import type { Hono } from "hono";
|
|
|
17
17
|
import { z } from "zod";
|
|
18
18
|
|
|
19
19
|
import { readSharingAuth } from "./auth.js";
|
|
20
|
-
import { ShareAuthError, ShareClient
|
|
20
|
+
import { ShareAuthError, ShareClient } from "./client.js";
|
|
21
21
|
import {
|
|
22
22
|
exportShare,
|
|
23
23
|
referenceCommits,
|
|
@@ -147,7 +147,7 @@ export function mountSharingHost(
|
|
|
147
147
|
"This share uses an untrusted service. Sign in to that service before opening its links.",
|
|
148
148
|
400,
|
|
149
149
|
);
|
|
150
|
-
const id = sharedReviewId(parsed.origin, parsed.
|
|
150
|
+
const id = sharedReviewId(parsed.origin, parsed.capability);
|
|
151
151
|
|
|
152
152
|
if (!imports.has(id)) {
|
|
153
153
|
shared.setStatus(id, "downloading");
|
|
@@ -160,12 +160,16 @@ export function mountSharingHost(
|
|
|
160
160
|
return;
|
|
161
161
|
}
|
|
162
162
|
|
|
163
|
-
const bundle = await new ShareClient(
|
|
164
|
-
parsed.
|
|
163
|
+
const { bundle, attribution } = await new ShareClient(
|
|
164
|
+
parsed.origin,
|
|
165
|
+
).download(parsed.capability);
|
|
166
|
+
|
|
167
|
+
await shared.import(
|
|
168
|
+
parsed.origin,
|
|
165
169
|
parsed.capability,
|
|
170
|
+
bundle,
|
|
171
|
+
attribution,
|
|
166
172
|
);
|
|
167
|
-
|
|
168
|
-
await shared.import(parsed.origin, parsed.shareId, bundle);
|
|
169
173
|
})()
|
|
170
174
|
.catch((error) => {
|
|
171
175
|
shared.setStatus(
|
|
@@ -234,49 +238,42 @@ export function mountSharingPublisher(
|
|
|
234
238
|
|
|
235
239
|
const readRepository = options.readRepository ?? readShareRepository;
|
|
236
240
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
241
|
+
const commits = referenceCommits(snapshot);
|
|
242
|
+
|
|
243
|
+
// Desktop imports need the commits on GitHub; without them the share is
|
|
244
|
+
// for the browser only.
|
|
245
|
+
const repository = (async () => {
|
|
246
|
+
if (!root) return undefined;
|
|
247
|
+
|
|
248
|
+
try {
|
|
249
|
+
const repository = await readRepository(root);
|
|
250
|
+
|
|
251
|
+
if (snapshot.pins || commits.length)
|
|
252
|
+
await verifyRepository(root, snapshot.pins, commits);
|
|
253
|
+
|
|
254
|
+
return repository;
|
|
255
|
+
} catch {
|
|
256
|
+
return undefined;
|
|
257
|
+
}
|
|
258
|
+
})();
|
|
250
259
|
|
|
251
260
|
const bundle = await exportShare({
|
|
252
261
|
store,
|
|
253
262
|
data,
|
|
254
263
|
reviewId: input.reviewId,
|
|
255
264
|
version: snapshot.version,
|
|
256
|
-
repository,
|
|
257
265
|
});
|
|
258
266
|
|
|
259
|
-
const
|
|
267
|
+
const verified = await repository;
|
|
260
268
|
|
|
261
|
-
|
|
262
|
-
const verification =
|
|
263
|
-
root && (snapshot.pins || commits.length) && repository
|
|
264
|
-
? verifyRepository(root, snapshot.pins, commits).then(
|
|
265
|
-
() => ({ ok: true as const }),
|
|
266
|
-
(error: Error) => ({ ok: false as const, error }),
|
|
267
|
-
)
|
|
268
|
-
: Promise.resolve({ ok: true as const });
|
|
269
|
+
if (verified) bundle.repository = verified;
|
|
269
270
|
|
|
270
271
|
try {
|
|
271
272
|
const result = await new ShareClient(
|
|
272
273
|
account.origin,
|
|
273
274
|
account.token,
|
|
274
275
|
options.fetch,
|
|
275
|
-
).create(bundle, input.requestId ?? randomUUID()
|
|
276
|
-
const result = await verification;
|
|
277
|
-
|
|
278
|
-
if (!result.ok) throw result.error;
|
|
279
|
-
});
|
|
276
|
+
).create(bundle, input.requestId ?? randomUUID());
|
|
280
277
|
|
|
281
278
|
const url = new URL(result.url);
|
|
282
279
|
|
|
@@ -297,8 +294,7 @@ export function mountSharingPublisher(
|
|
|
297
294
|
version: snapshot.version,
|
|
298
295
|
});
|
|
299
296
|
} catch (error) {
|
|
300
|
-
if (error instanceof
|
|
301
|
-
return context.json({ error: error.message }, 422);
|
|
297
|
+
if (error instanceof ReviewInputError) throw error;
|
|
302
298
|
|
|
303
299
|
if (error instanceof ShareAuthError) {
|
|
304
300
|
// A CI token lives in the environment; only the saved login can go stale.
|