rivet-design 0.11.8 → 0.11.9
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/config/flags.d.ts +3 -0
- package/dist/config/flags.d.ts.map +1 -1
- package/dist/config/flags.js +1 -0
- package/dist/config/flags.js.map +1 -1
- package/dist/index.d.ts +1 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +96 -590
- package/dist/index.js.map +1 -1
- package/dist/install/harnesses.d.ts +82 -0
- package/dist/install/harnesses.d.ts.map +1 -0
- package/dist/install/harnesses.js +537 -0
- package/dist/install/harnesses.js.map +1 -0
- package/dist/mcp/agent-variants/WorktreeOrchestrator.d.ts +25 -0
- package/dist/mcp/agent-variants/WorktreeOrchestrator.d.ts.map +1 -1
- package/dist/mcp/agent-variants/WorktreeOrchestrator.js +121 -0
- package/dist/mcp/agent-variants/WorktreeOrchestrator.js.map +1 -1
- package/dist/mcp/auth/tools.d.ts.map +1 -1
- package/dist/mcp/auth/tools.js +63 -12
- package/dist/mcp/auth/tools.js.map +1 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +2 -7
- package/dist/mcp/server.js.map +1 -1
- package/dist/onboarding/signin.d.ts +31 -0
- package/dist/onboarding/signin.d.ts.map +1 -0
- package/dist/onboarding/signin.js +235 -0
- package/dist/onboarding/signin.js.map +1 -0
- package/dist/routes/agentVariants.d.ts.map +1 -1
- package/dist/routes/agentVariants.js +450 -33
- package/dist/routes/agentVariants.js.map +1 -1
- package/dist/services/PrototypeDeployService.d.ts +2 -0
- package/dist/services/PrototypeDeployService.d.ts.map +1 -1
- package/dist/services/PrototypeDeployService.js +6 -0
- package/dist/services/PrototypeDeployService.js.map +1 -1
- package/dist/services/TelemetryService.d.ts +70 -0
- package/dist/services/TelemetryService.d.ts.map +1 -1
- package/dist/services/TelemetryService.js +85 -0
- package/dist/services/TelemetryService.js.map +1 -1
- package/dist/services/VariantHistoryService.d.ts +18 -0
- package/dist/services/VariantHistoryService.d.ts.map +1 -1
- package/dist/services/VariantHistoryService.js +27 -3
- package/dist/services/VariantHistoryService.js.map +1 -1
- package/package.json +2 -1
- package/src/ui/dist/assets/main-BtohezP4.js +641 -0
- package/src/ui/dist/assets/{main-CtRINpOq.css → main-IY99De7M.css} +1 -1
- package/src/ui/dist/index.html +2 -2
- package/src/ui/dist/assets/main-CF-wHnwl.js +0 -641
|
@@ -78,6 +78,10 @@ const deployBody = v3_1.z.object({
|
|
|
78
78
|
variantId: v3_1.z.string().min(1),
|
|
79
79
|
projectPath: v3_1.z.string().optional(),
|
|
80
80
|
});
|
|
81
|
+
const canvasDeployBody = v3_1.z.object({
|
|
82
|
+
variantIds: v3_1.z.array(v3_1.z.string().min(1)).min(1),
|
|
83
|
+
projectPath: v3_1.z.string().optional(),
|
|
84
|
+
});
|
|
81
85
|
const elementRefSchema = v3_1.z.object({
|
|
82
86
|
xpath: v3_1.z.string(),
|
|
83
87
|
tagName: v3_1.z.string(),
|
|
@@ -119,7 +123,11 @@ const inlineVariantBody = v3_1.z.object({
|
|
|
119
123
|
images: v3_1.z.array(v3_1.z.string()).optional(),
|
|
120
124
|
references: v3_1.z.array(commentReferenceItemSchema).max(8).optional(),
|
|
121
125
|
variantRequest: v3_1.z.object({
|
|
122
|
-
count: v3_1.z
|
|
126
|
+
count: v3_1.z
|
|
127
|
+
.number()
|
|
128
|
+
.int()
|
|
129
|
+
.min(contracts_1.VARIANT_COUNT_MIN)
|
|
130
|
+
.max(contracts_1.VARIANT_COUNT_MAX),
|
|
123
131
|
}),
|
|
124
132
|
})
|
|
125
133
|
.passthrough())
|
|
@@ -161,6 +169,169 @@ const HISTORY_STATUSES = new Set([
|
|
|
161
169
|
'rejected',
|
|
162
170
|
'cancelled',
|
|
163
171
|
]);
|
|
172
|
+
const escapeHtml = (value) => value.replace(/[&<>"']/g, (char) => {
|
|
173
|
+
switch (char) {
|
|
174
|
+
case '&':
|
|
175
|
+
return '&';
|
|
176
|
+
case '<':
|
|
177
|
+
return '<';
|
|
178
|
+
case '>':
|
|
179
|
+
return '>';
|
|
180
|
+
case '"':
|
|
181
|
+
return '"';
|
|
182
|
+
case "'":
|
|
183
|
+
return ''';
|
|
184
|
+
default:
|
|
185
|
+
return char;
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
const CANVAS_IFRAME_SANDBOX = 'allow-scripts allow-forms allow-popups';
|
|
189
|
+
/**
|
|
190
|
+
* Render a public canvas page that puts every deployed direction in one shareable view.
|
|
191
|
+
*/
|
|
192
|
+
const buildCanvasShareHtml = (args) => {
|
|
193
|
+
const prompt = args.sessionPrompt.trim();
|
|
194
|
+
const variantCards = args.variants
|
|
195
|
+
.map((variant, index) => {
|
|
196
|
+
const label = variant.label.trim() || `Direction ${index + 1}`;
|
|
197
|
+
const brief = variant.brief.trim();
|
|
198
|
+
return `<article class="variant-card">
|
|
199
|
+
<header>
|
|
200
|
+
<div>
|
|
201
|
+
<p class="eyebrow">Direction ${index + 1}</p>
|
|
202
|
+
<h2>${escapeHtml(label)}</h2>
|
|
203
|
+
</div>
|
|
204
|
+
<a href="${escapeHtml(variant.shareUrl)}" target="_blank" rel="noreferrer">Open</a>
|
|
205
|
+
</header>
|
|
206
|
+
${brief ? `<p class="brief">${escapeHtml(brief)}</p>` : ''}
|
|
207
|
+
<iframe title="${escapeHtml(label)}" src="${escapeHtml(variant.shareUrl)}" loading="lazy" sandbox="${CANVAS_IFRAME_SANDBOX}"></iframe>
|
|
208
|
+
</article>`;
|
|
209
|
+
})
|
|
210
|
+
.join('\n');
|
|
211
|
+
return `<!doctype html>
|
|
212
|
+
<html lang="en">
|
|
213
|
+
<head>
|
|
214
|
+
<meta charset="utf-8">
|
|
215
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
216
|
+
<title>Rivet Canvas</title>
|
|
217
|
+
<style>
|
|
218
|
+
:root {
|
|
219
|
+
color-scheme: light;
|
|
220
|
+
--bg: #f6f4ef;
|
|
221
|
+
--card: #ffffff;
|
|
222
|
+
--ink: #161615;
|
|
223
|
+
--muted: #6f6a60;
|
|
224
|
+
--border: #ded8cc;
|
|
225
|
+
--accent: #ff5a1f;
|
|
226
|
+
}
|
|
227
|
+
* { box-sizing: border-box; }
|
|
228
|
+
body {
|
|
229
|
+
margin: 0;
|
|
230
|
+
background: var(--bg);
|
|
231
|
+
color: var(--ink);
|
|
232
|
+
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
233
|
+
}
|
|
234
|
+
main {
|
|
235
|
+
width: min(1440px, calc(100vw - 32px));
|
|
236
|
+
margin: 0 auto;
|
|
237
|
+
padding: 32px 0 48px;
|
|
238
|
+
}
|
|
239
|
+
.hero {
|
|
240
|
+
display: flex;
|
|
241
|
+
align-items: flex-end;
|
|
242
|
+
justify-content: space-between;
|
|
243
|
+
gap: 24px;
|
|
244
|
+
margin-bottom: 24px;
|
|
245
|
+
}
|
|
246
|
+
h1 {
|
|
247
|
+
margin: 0;
|
|
248
|
+
font-size: clamp(32px, 5vw, 64px);
|
|
249
|
+
letter-spacing: -0.06em;
|
|
250
|
+
line-height: 0.95;
|
|
251
|
+
}
|
|
252
|
+
.prompt {
|
|
253
|
+
max-width: 720px;
|
|
254
|
+
color: var(--muted);
|
|
255
|
+
font-size: 15px;
|
|
256
|
+
line-height: 1.5;
|
|
257
|
+
}
|
|
258
|
+
.grid {
|
|
259
|
+
display: grid;
|
|
260
|
+
grid-template-columns: repeat(auto-fit, minmax(420px, 1fr));
|
|
261
|
+
gap: 18px;
|
|
262
|
+
}
|
|
263
|
+
.variant-card {
|
|
264
|
+
overflow: hidden;
|
|
265
|
+
border: 1px solid var(--border);
|
|
266
|
+
border-radius: 18px;
|
|
267
|
+
background: var(--card);
|
|
268
|
+
box-shadow: 0 20px 60px rgba(22, 22, 21, 0.08);
|
|
269
|
+
}
|
|
270
|
+
.variant-card header {
|
|
271
|
+
display: flex;
|
|
272
|
+
align-items: flex-start;
|
|
273
|
+
justify-content: space-between;
|
|
274
|
+
gap: 16px;
|
|
275
|
+
padding: 18px 18px 0;
|
|
276
|
+
}
|
|
277
|
+
.eyebrow {
|
|
278
|
+
margin: 0 0 6px;
|
|
279
|
+
color: var(--accent);
|
|
280
|
+
font-size: 11px;
|
|
281
|
+
font-weight: 700;
|
|
282
|
+
letter-spacing: 0.12em;
|
|
283
|
+
text-transform: uppercase;
|
|
284
|
+
}
|
|
285
|
+
h2 {
|
|
286
|
+
margin: 0;
|
|
287
|
+
font-size: 20px;
|
|
288
|
+
letter-spacing: -0.03em;
|
|
289
|
+
}
|
|
290
|
+
a {
|
|
291
|
+
color: var(--ink);
|
|
292
|
+
font-size: 13px;
|
|
293
|
+
font-weight: 700;
|
|
294
|
+
text-decoration: none;
|
|
295
|
+
border-bottom: 1px solid currentColor;
|
|
296
|
+
}
|
|
297
|
+
.brief {
|
|
298
|
+
margin: 10px 18px 16px;
|
|
299
|
+
color: var(--muted);
|
|
300
|
+
font-size: 13px;
|
|
301
|
+
line-height: 1.45;
|
|
302
|
+
}
|
|
303
|
+
iframe {
|
|
304
|
+
display: block;
|
|
305
|
+
width: 100%;
|
|
306
|
+
height: 720px;
|
|
307
|
+
border: 0;
|
|
308
|
+
border-top: 1px solid var(--border);
|
|
309
|
+
background: white;
|
|
310
|
+
}
|
|
311
|
+
@media (max-width: 640px) {
|
|
312
|
+
main { width: min(100vw - 20px, 1440px); padding-top: 20px; }
|
|
313
|
+
.hero { display: block; }
|
|
314
|
+
.grid { grid-template-columns: 1fr; }
|
|
315
|
+
iframe { height: 560px; }
|
|
316
|
+
}
|
|
317
|
+
</style>
|
|
318
|
+
</head>
|
|
319
|
+
<body>
|
|
320
|
+
<main>
|
|
321
|
+
<section class="hero">
|
|
322
|
+
<div>
|
|
323
|
+
<p class="eyebrow">Rivet Canvas</p>
|
|
324
|
+
<h1>${args.variants.length} directions</h1>
|
|
325
|
+
</div>
|
|
326
|
+
${prompt ? `<p class="prompt">${escapeHtml(prompt)}</p>` : ''}
|
|
327
|
+
</section>
|
|
328
|
+
<section class="grid">
|
|
329
|
+
${variantCards}
|
|
330
|
+
</section>
|
|
331
|
+
</main>
|
|
332
|
+
</body>
|
|
333
|
+
</html>`;
|
|
334
|
+
};
|
|
164
335
|
/** Resolve a Rivet-generated subproject path to the workspace that owns variant history. */
|
|
165
336
|
const generatedProjectWorkspaceRoot = (projectPath) => {
|
|
166
337
|
const normalized = path_1.default.normalize(projectPath);
|
|
@@ -230,6 +401,67 @@ const createAgentVariantsRouter = (orchestrator, bridge, telemetry, variantHisto
|
|
|
230
401
|
}
|
|
231
402
|
return null;
|
|
232
403
|
};
|
|
404
|
+
/**
|
|
405
|
+
* Deploy one persisted variant and append the returned public URL to history.
|
|
406
|
+
*/
|
|
407
|
+
const deployPersistedVariant = async (args) => {
|
|
408
|
+
const existingDeployments = args.variant.manifest.deployments ?? [];
|
|
409
|
+
const latestDeployment = existingDeployments.length > 0
|
|
410
|
+
? existingDeployments[existingDeployments.length - 1]
|
|
411
|
+
: undefined;
|
|
412
|
+
if (args.reuseExisting && latestDeployment) {
|
|
413
|
+
return {
|
|
414
|
+
ok: true,
|
|
415
|
+
variant: {
|
|
416
|
+
variantId: args.variant.manifest.variantId,
|
|
417
|
+
label: args.variant.manifest.label,
|
|
418
|
+
brief: args.variant.manifest.brief,
|
|
419
|
+
...latestDeployment,
|
|
420
|
+
},
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
const filesDir = path_1.default.join(args.variant.variantDir, 'files');
|
|
424
|
+
const preview = args.variant.manifest.preview;
|
|
425
|
+
let result;
|
|
426
|
+
if (preview.kind === 'static') {
|
|
427
|
+
// Already a built static bundle (e.g. a history static-preview variant)
|
|
428
|
+
// — upload files/ directly, no build needed.
|
|
429
|
+
result = await deployService.deployStatic(filesDir);
|
|
430
|
+
}
|
|
431
|
+
else if (args.variant.manifest.projectKind === 'fresh' &&
|
|
432
|
+
args.variant.manifest.artifact.kind === 'project') {
|
|
433
|
+
// Buildable fresh Vite app — compile then upload.
|
|
434
|
+
result = await deployService.deploy(filesDir);
|
|
435
|
+
}
|
|
436
|
+
else {
|
|
437
|
+
return {
|
|
438
|
+
ok: false,
|
|
439
|
+
status: 422,
|
|
440
|
+
errorCode: 'RUNTIME_VALIDATION_FAILED',
|
|
441
|
+
message: "This prototype can't be deployed — only fresh app directions or static previews are supported.",
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
const deployment = {
|
|
445
|
+
deployId: result.deployId,
|
|
446
|
+
shareUrl: result.shareUrl,
|
|
447
|
+
deployedAt: new Date().toISOString(),
|
|
448
|
+
};
|
|
449
|
+
await variantHistory.recordDeployment({
|
|
450
|
+
projectPath: args.projectPath,
|
|
451
|
+
sessionId: args.sessionId,
|
|
452
|
+
variantId: args.variant.manifest.variantId,
|
|
453
|
+
deployment,
|
|
454
|
+
});
|
|
455
|
+
return {
|
|
456
|
+
ok: true,
|
|
457
|
+
variant: {
|
|
458
|
+
variantId: args.variant.manifest.variantId,
|
|
459
|
+
label: args.variant.manifest.label,
|
|
460
|
+
brief: args.variant.manifest.brief,
|
|
461
|
+
...deployment,
|
|
462
|
+
},
|
|
463
|
+
};
|
|
464
|
+
};
|
|
233
465
|
router.post('/variants/inline', async (req, res) => {
|
|
234
466
|
if (rejectNonLocalRequest(req, res))
|
|
235
467
|
return;
|
|
@@ -368,7 +600,8 @@ const createAgentVariantsRouter = (orchestrator, bridge, telemetry, variantHisto
|
|
|
368
600
|
return;
|
|
369
601
|
}
|
|
370
602
|
const statusParam = typeof req.query.status === 'string' ? req.query.status : undefined;
|
|
371
|
-
if (statusParam &&
|
|
603
|
+
if (statusParam &&
|
|
604
|
+
!HISTORY_STATUSES.has(statusParam)) {
|
|
372
605
|
res.status(400).json({
|
|
373
606
|
errorCode: 'SCHEMA_VALIDATION_FAILED',
|
|
374
607
|
message: `Invalid status '${statusParam}' — expected one of ${[...HISTORY_STATUSES].join(', ')}.`,
|
|
@@ -508,14 +741,71 @@ const createAgentVariantsRouter = (orchestrator, bridge, telemetry, variantHisto
|
|
|
508
741
|
return;
|
|
509
742
|
}
|
|
510
743
|
try {
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
744
|
+
if (!parsed.data.variantId) {
|
|
745
|
+
telemetry?.trackAgentVariantsPreviewSwitchStarted({
|
|
746
|
+
sessionId,
|
|
747
|
+
variantId: null,
|
|
748
|
+
target: 'original',
|
|
749
|
+
previewKind: 'original',
|
|
750
|
+
hasPort: false,
|
|
751
|
+
});
|
|
752
|
+
bridge.switchPreviewPort(null);
|
|
753
|
+
telemetry?.trackAgentVariantsPreviewSwitchCompleted({
|
|
754
|
+
sessionId,
|
|
755
|
+
variantId: null,
|
|
756
|
+
target: 'original',
|
|
757
|
+
previewKind: 'original',
|
|
758
|
+
hasPort: false,
|
|
759
|
+
port: null,
|
|
760
|
+
});
|
|
761
|
+
res.json({ port: null });
|
|
762
|
+
return;
|
|
763
|
+
}
|
|
764
|
+
const port = orchestrator.getDevServerPort(sessionId, parsed.data.variantId);
|
|
765
|
+
telemetry?.trackAgentVariantsPreviewSwitchStarted({
|
|
766
|
+
sessionId,
|
|
767
|
+
variantId: parsed.data.variantId,
|
|
768
|
+
target: 'variant',
|
|
769
|
+
previewKind: 'dev_server',
|
|
770
|
+
hasPort: typeof port === 'number',
|
|
771
|
+
});
|
|
772
|
+
if (typeof port !== 'number') {
|
|
773
|
+
telemetry?.trackAgentVariantsPreviewSwitchFailed({
|
|
774
|
+
sessionId,
|
|
775
|
+
variantId: parsed.data.variantId,
|
|
776
|
+
target: 'variant',
|
|
777
|
+
previewKind: 'dev_server',
|
|
778
|
+
errorCode: 'PREVIEW_NOT_READY',
|
|
779
|
+
reason: 'missing_variant_port',
|
|
780
|
+
hasPort: false,
|
|
781
|
+
});
|
|
782
|
+
res.status(409).json({
|
|
783
|
+
errorCode: 'PREVIEW_NOT_READY',
|
|
784
|
+
message: 'Variant preview is not ready yet',
|
|
785
|
+
});
|
|
786
|
+
return;
|
|
787
|
+
}
|
|
515
788
|
bridge.switchPreviewPort(port);
|
|
789
|
+
telemetry?.trackAgentVariantsPreviewSwitchCompleted({
|
|
790
|
+
sessionId,
|
|
791
|
+
variantId: parsed.data.variantId,
|
|
792
|
+
target: 'variant',
|
|
793
|
+
previewKind: 'dev_server',
|
|
794
|
+
hasPort: true,
|
|
795
|
+
port,
|
|
796
|
+
});
|
|
516
797
|
res.json({ port });
|
|
517
798
|
}
|
|
518
799
|
catch (err) {
|
|
800
|
+
telemetry?.trackAgentVariantsPreviewSwitchFailed({
|
|
801
|
+
sessionId,
|
|
802
|
+
variantId: parsed.data.variantId,
|
|
803
|
+
target: parsed.data.variantId ? 'variant' : 'original',
|
|
804
|
+
previewKind: parsed.data.variantId ? 'dev_server' : 'original',
|
|
805
|
+
errorCode: 'PREVIEW_SWITCH_FAILED',
|
|
806
|
+
reason: err instanceof Error ? err.message : String(err),
|
|
807
|
+
hasPort: false,
|
|
808
|
+
});
|
|
519
809
|
sendError(res, err);
|
|
520
810
|
}
|
|
521
811
|
});
|
|
@@ -539,7 +829,9 @@ const createAgentVariantsRouter = (orchestrator, bridge, telemetry, variantHisto
|
|
|
539
829
|
// sub-path. The hasAssets check also avoids the trailing-slash
|
|
540
830
|
// fragment-link quirk for the common no-assets case.
|
|
541
831
|
const hasAssets = orchestrator.hasStaticPreviewAssets(sessionId, variantId);
|
|
542
|
-
if (hasAssets &&
|
|
832
|
+
if (hasAssets &&
|
|
833
|
+
!req.originalUrl.includes('?') &&
|
|
834
|
+
!req.originalUrl.endsWith('/')) {
|
|
543
835
|
// Document URL must end in `/` so the browser's `document.URL` and
|
|
544
836
|
// the `<base href>` agree. Otherwise fragment-only links
|
|
545
837
|
// (`<a href="#section">`) resolve to the base-href URL — different
|
|
@@ -789,40 +1081,165 @@ const createAgentVariantsRouter = (orchestrator, bridge, telemetry, variantHisto
|
|
|
789
1081
|
});
|
|
790
1082
|
return;
|
|
791
1083
|
}
|
|
792
|
-
const
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
1084
|
+
const deployed = await deployPersistedVariant({
|
|
1085
|
+
projectPath: found.projectPath,
|
|
1086
|
+
sessionId,
|
|
1087
|
+
variant: found.detail,
|
|
1088
|
+
reuseExisting: false,
|
|
1089
|
+
});
|
|
1090
|
+
if (!deployed.ok) {
|
|
1091
|
+
res.status(deployed.status).json({
|
|
1092
|
+
errorCode: deployed.errorCode,
|
|
1093
|
+
message: deployed.message,
|
|
1094
|
+
});
|
|
1095
|
+
return;
|
|
801
1096
|
}
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
1097
|
+
const { deployId, shareUrl, deployedAt } = deployed.variant;
|
|
1098
|
+
res.json({ deployId, shareUrl, deployedAt });
|
|
1099
|
+
}
|
|
1100
|
+
catch (err) {
|
|
1101
|
+
log.error('Prototype deploy failed', err);
|
|
1102
|
+
const message = err instanceof Error ? err.message : 'Deploy failed.';
|
|
1103
|
+
const detail = err && typeof err === 'object' && 'detail' in err
|
|
1104
|
+
? String(err.detail ?? '')
|
|
1105
|
+
: '';
|
|
1106
|
+
res.status(500).json({
|
|
1107
|
+
errorCode: 'RUNTIME_VALIDATION_FAILED',
|
|
1108
|
+
message,
|
|
1109
|
+
...(detail ? { detail } : {}),
|
|
1110
|
+
});
|
|
1111
|
+
}
|
|
1112
|
+
});
|
|
1113
|
+
/**
|
|
1114
|
+
* Deploy a fresh-project canvas to the public Prototype Preview Host. The
|
|
1115
|
+
* canvas page embeds every requested direction so one public URL can be used
|
|
1116
|
+
* for review instead of sharing one URL per variant.
|
|
1117
|
+
*/
|
|
1118
|
+
router.post('/variants/:sessionId/canvas/deploy', async (req, res) => {
|
|
1119
|
+
if (rejectNonLocalRequest(req, res))
|
|
1120
|
+
return;
|
|
1121
|
+
const sessionId = req.params.sessionId;
|
|
1122
|
+
const flags = await (0, evaluateFlags_1.getFeatureFlags)({
|
|
1123
|
+
userKey: (0, featureFlagUserKey_1.getConfigFeatureFlagUserKey)({ headers: req.headers }),
|
|
1124
|
+
});
|
|
1125
|
+
if (!flags.deployPrototype.isEnabled) {
|
|
1126
|
+
res.status(403).json({
|
|
1127
|
+
errorCode: 'FEATURE_DISABLED',
|
|
1128
|
+
message: 'Prototype deploy is not enabled.',
|
|
1129
|
+
});
|
|
1130
|
+
return;
|
|
1131
|
+
}
|
|
1132
|
+
if (!flags.canvasShare.isEnabled) {
|
|
1133
|
+
res.status(403).json({
|
|
1134
|
+
errorCode: 'FEATURE_DISABLED',
|
|
1135
|
+
message: 'Canvas share is not enabled.',
|
|
1136
|
+
});
|
|
1137
|
+
return;
|
|
1138
|
+
}
|
|
1139
|
+
const parsed = canvasDeployBody.safeParse(req.body);
|
|
1140
|
+
if (!parsed.success) {
|
|
1141
|
+
res.status(400).json({
|
|
1142
|
+
errorCode: 'SCHEMA_VALIDATION_FAILED',
|
|
1143
|
+
message: parsed.error.message,
|
|
1144
|
+
});
|
|
1145
|
+
return;
|
|
1146
|
+
}
|
|
1147
|
+
const projectPath = resolveProjectPath(parsed.data.projectPath);
|
|
1148
|
+
if (!projectPath) {
|
|
1149
|
+
res.status(400).json({
|
|
1150
|
+
errorCode: 'RUNTIME_VALIDATION_FAILED',
|
|
1151
|
+
message: 'No projectPath available — pass projectPath or open a project in the visual editor.',
|
|
1152
|
+
});
|
|
1153
|
+
return;
|
|
1154
|
+
}
|
|
1155
|
+
try {
|
|
1156
|
+
const variantIds = [...new Set(parsed.data.variantIds)];
|
|
1157
|
+
const canvasVariants = [];
|
|
1158
|
+
const deployedVariants = [];
|
|
1159
|
+
let sessionPrompt = '';
|
|
1160
|
+
let canvasHistoryProjectPath = projectPath;
|
|
1161
|
+
for (const variantId of variantIds) {
|
|
1162
|
+
const found = await findHistoryVariant({
|
|
1163
|
+
projectPath,
|
|
1164
|
+
sessionId,
|
|
1165
|
+
variantId,
|
|
1166
|
+
});
|
|
1167
|
+
if (!found) {
|
|
1168
|
+
res.status(404).json({
|
|
1169
|
+
errorCode: 'WORK_ITEM_NOT_FOUND',
|
|
1170
|
+
message: `Variant '${variantId}' not found in history.`,
|
|
1171
|
+
});
|
|
1172
|
+
return;
|
|
1173
|
+
}
|
|
1174
|
+
canvasHistoryProjectPath = found.projectPath;
|
|
1175
|
+
const variant = found.detail;
|
|
1176
|
+
if (variant.manifest.preview.kind !== 'static') {
|
|
1177
|
+
res.status(422).json({
|
|
1178
|
+
errorCode: 'RUNTIME_VALIDATION_FAILED',
|
|
1179
|
+
message: 'Canvas sharing only supports static preview directions. Share buildable app directions individually.',
|
|
1180
|
+
});
|
|
1181
|
+
return;
|
|
1182
|
+
}
|
|
1183
|
+
if (!sessionPrompt) {
|
|
1184
|
+
sessionPrompt = variant.manifest.sessionPrompt;
|
|
1185
|
+
}
|
|
1186
|
+
canvasVariants.push(found);
|
|
806
1187
|
}
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
1188
|
+
for (const found of canvasVariants) {
|
|
1189
|
+
const variant = found.detail;
|
|
1190
|
+
const deployed = await deployPersistedVariant({
|
|
1191
|
+
projectPath: found.projectPath,
|
|
1192
|
+
sessionId,
|
|
1193
|
+
variant,
|
|
1194
|
+
reuseExisting: false,
|
|
811
1195
|
});
|
|
812
|
-
|
|
1196
|
+
if (!deployed.ok) {
|
|
1197
|
+
res.status(deployed.status).json({
|
|
1198
|
+
errorCode: deployed.errorCode,
|
|
1199
|
+
message: deployed.message,
|
|
1200
|
+
});
|
|
1201
|
+
return;
|
|
1202
|
+
}
|
|
1203
|
+
deployedVariants.push(deployed.variant);
|
|
813
1204
|
}
|
|
814
|
-
const
|
|
1205
|
+
const html = buildCanvasShareHtml({
|
|
1206
|
+
sessionPrompt,
|
|
1207
|
+
variants: deployedVariants,
|
|
1208
|
+
});
|
|
1209
|
+
const canvas = await deployService.deployHtml(html);
|
|
815
1210
|
const deployedAt = new Date().toISOString();
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
1211
|
+
const canvasDeployment = {
|
|
1212
|
+
deployId: canvas.deployId,
|
|
1213
|
+
shareUrl: canvas.shareUrl,
|
|
1214
|
+
deployedAt,
|
|
1215
|
+
variants: deployedVariants.map((variant) => ({
|
|
1216
|
+
variantId: variant.variantId,
|
|
1217
|
+
deployId: variant.deployId,
|
|
1218
|
+
shareUrl: variant.shareUrl,
|
|
1219
|
+
})),
|
|
1220
|
+
};
|
|
1221
|
+
let historyWarning;
|
|
1222
|
+
try {
|
|
1223
|
+
await variantHistory.recordCanvasDeployment({
|
|
1224
|
+
projectPath: canvasHistoryProjectPath,
|
|
1225
|
+
sessionId,
|
|
1226
|
+
deployment: canvasDeployment,
|
|
1227
|
+
});
|
|
1228
|
+
}
|
|
1229
|
+
catch (err) {
|
|
1230
|
+
historyWarning = 'Canvas history could not be recorded.';
|
|
1231
|
+
log.warn('Canvas deploy history record failed', err);
|
|
1232
|
+
}
|
|
1233
|
+
res.json({
|
|
1234
|
+
deployId: canvas.deployId,
|
|
1235
|
+
shareUrl: canvas.shareUrl,
|
|
1236
|
+
deployedAt,
|
|
1237
|
+
variants: deployedVariants,
|
|
1238
|
+
...(historyWarning ? { historyWarning } : {}),
|
|
821
1239
|
});
|
|
822
|
-
res.json({ deployId, shareUrl, deployedAt });
|
|
823
1240
|
}
|
|
824
1241
|
catch (err) {
|
|
825
|
-
log.error('
|
|
1242
|
+
log.error('Canvas deploy failed', err);
|
|
826
1243
|
const message = err instanceof Error ? err.message : 'Deploy failed.';
|
|
827
1244
|
const detail = err && typeof err === 'object' && 'detail' in err
|
|
828
1245
|
? String(err.detail ?? '')
|