@playdrop/playdrop-cli 0.9.5 → 0.10.0
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/config/client-meta.json +1 -2
- package/dist/apiClient.d.ts +2 -0
- package/dist/apiClient.js +27 -2
- package/dist/appUrls.d.ts +1 -0
- package/dist/appUrls.js +9 -0
- package/dist/apps/build.js +39 -28
- package/dist/apps/index.d.ts +1 -0
- package/dist/apps/index.js +2 -0
- package/dist/apps/launchCheck.d.ts +2 -0
- package/dist/apps/launchCheck.js +31 -6
- package/dist/apps/registration.d.ts +1 -0
- package/dist/apps/registration.js +1 -0
- package/dist/apps/upload.d.ts +1 -0
- package/dist/apps/upload.js +4 -17
- package/dist/captureRuntime.d.ts +1 -0
- package/dist/captureRuntime.js +308 -0
- package/dist/catalogue.d.ts +4 -2
- package/dist/catalogue.js +50 -7
- package/dist/commandContext.js +42 -3
- package/dist/commands/capture.d.ts +1 -0
- package/dist/commands/capture.js +30 -13
- package/dist/commands/create.d.ts +0 -1
- package/dist/commands/create.js +2 -151
- package/dist/commands/creations.d.ts +0 -13
- package/dist/commands/creations.js +0 -141
- package/dist/commands/dev.d.ts +2 -1
- package/dist/commands/dev.js +23 -6
- package/dist/commands/devServer.js +3 -1
- package/dist/commands/generation.d.ts +1 -0
- package/dist/commands/generation.js +274 -0
- package/dist/commands/upload.d.ts +27 -1
- package/dist/commands/upload.js +962 -21
- package/dist/commands/validate.js +5 -0
- package/dist/commands/worker/runtime.d.ts +69 -0
- package/dist/commands/worker/runtime.js +414 -0
- package/dist/commands/worker.d.ts +144 -0
- package/dist/commands/worker.js +2219 -0
- package/dist/config.d.ts +2 -0
- package/dist/config.js +23 -0
- package/dist/index.js +71 -30
- package/dist/shellProbe.d.ts +1 -1
- package/dist/shellProbe.js +3 -3
- package/dist/workspaceAuth.d.ts +1 -0
- package/dist/workspaceAuth.js +8 -0
- package/node_modules/@playdrop/api-client/dist/client.d.ts +31 -14
- package/node_modules/@playdrop/api-client/dist/client.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/client.js +2 -2
- package/node_modules/@playdrop/api-client/dist/domains/admin.d.ts +3 -1
- package/node_modules/@playdrop/api-client/dist/domains/admin.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/domains/admin.js +45 -0
- package/node_modules/@playdrop/api-client/dist/domains/agent-tasks.d.ts +72 -0
- package/node_modules/@playdrop/api-client/dist/domains/agent-tasks.d.ts.map +1 -0
- package/node_modules/@playdrop/api-client/dist/domains/agent-tasks.js +442 -0
- package/node_modules/@playdrop/api-client/dist/index.d.ts +31 -14
- package/node_modules/@playdrop/api-client/dist/index.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/index.js +134 -38
- package/node_modules/@playdrop/config/client-meta.json +1 -2
- package/node_modules/@playdrop/types/dist/api.d.ts +501 -74
- package/node_modules/@playdrop/types/dist/api.d.ts.map +1 -1
- package/node_modules/@playdrop/types/dist/api.js +90 -9
- package/node_modules/@playdrop/types/dist/app.d.ts +2 -0
- package/node_modules/@playdrop/types/dist/app.d.ts.map +1 -1
- package/node_modules/@playdrop/types/dist/app.js +3 -0
- package/node_modules/@playdrop/types/dist/version.d.ts +1 -0
- package/node_modules/@playdrop/types/dist/version.d.ts.map +1 -1
- package/package.json +2 -1
- package/node_modules/@playdrop/api-client/dist/domains/game-ideas.d.ts +0 -46
- package/node_modules/@playdrop/api-client/dist/domains/game-ideas.d.ts.map +0 -1
- package/node_modules/@playdrop/api-client/dist/domains/game-ideas.js +0 -177
package/dist/captureRuntime.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
exports.MAX_CAPTURE_TIMEOUT_SECONDS = exports.CAPTURE_LOG_LEVEL_VALUES = void 0;
|
|
4
7
|
exports.resolveCaptureLogLevel = resolveCaptureLogLevel;
|
|
@@ -6,6 +9,7 @@ exports.validateCaptureTimeout = validateCaptureTimeout;
|
|
|
6
9
|
exports.runCapture = runCapture;
|
|
7
10
|
const promises_1 = require("node:fs/promises");
|
|
8
11
|
const node_path_1 = require("node:path");
|
|
12
|
+
const sharp_1 = __importDefault(require("sharp"));
|
|
9
13
|
const playwright_1 = require("./playwright");
|
|
10
14
|
const sessionCookie_1 = require("./sessionCookie");
|
|
11
15
|
const FRAME_SELECTOR = 'iframe[title="Game"]';
|
|
@@ -18,6 +22,11 @@ const GOOGLE_TELEMETRY_HOSTS = [
|
|
|
18
22
|
'doubleclick.net',
|
|
19
23
|
];
|
|
20
24
|
const GOOGLE_COUNTRY_SECOND_LEVEL_DOMAINS = new Set(['ac', 'co', 'com', 'edu', 'gov', 'net', 'org']);
|
|
25
|
+
const CANVAS_VISUAL_SAMPLE_SIZE = 64;
|
|
26
|
+
const MIN_VISIBLE_CANVAS_EDGE_PX = 48;
|
|
27
|
+
const MIN_CANVAS_FOREGROUND_RATIO = 0.018;
|
|
28
|
+
const MIN_CANVAS_COLOR_RANGE = 34;
|
|
29
|
+
const CANVAS_BACKGROUND_DISTANCE_THRESHOLD = 28;
|
|
21
30
|
function isHostOrSubdomain(hostname, domain) {
|
|
22
31
|
return hostname === domain || hostname.endsWith(`.${domain}`);
|
|
23
32
|
}
|
|
@@ -232,6 +241,298 @@ function shouldSettleHostedLaunchWaiter(state, expectedState) {
|
|
|
232
241
|
}
|
|
233
242
|
return state.state === expectedState;
|
|
234
243
|
}
|
|
244
|
+
function shouldAssertHostedVisualReadiness(input) {
|
|
245
|
+
return Boolean(input.requireHostedLaunchReady) && (input.expectedHostedLaunchState ?? 'ready') === 'ready';
|
|
246
|
+
}
|
|
247
|
+
function summarizeRgbaPixels(pixels, pixelCount, channels) {
|
|
248
|
+
let bgR = 0;
|
|
249
|
+
let bgG = 0;
|
|
250
|
+
let bgB = 0;
|
|
251
|
+
const edge = Math.max(1, Math.round(Math.sqrt(pixelCount)));
|
|
252
|
+
const cornerIndexes = [
|
|
253
|
+
0,
|
|
254
|
+
edge - 1,
|
|
255
|
+
edge * (edge - 1),
|
|
256
|
+
(edge * edge) - 1,
|
|
257
|
+
].filter(index => index >= 0 && index < pixelCount);
|
|
258
|
+
for (const index of cornerIndexes) {
|
|
259
|
+
const offset = index * channels;
|
|
260
|
+
bgR += pixels[offset] ?? 0;
|
|
261
|
+
bgG += pixels[offset + 1] ?? 0;
|
|
262
|
+
bgB += pixels[offset + 2] ?? 0;
|
|
263
|
+
}
|
|
264
|
+
bgR /= cornerIndexes.length || 1;
|
|
265
|
+
bgG /= cornerIndexes.length || 1;
|
|
266
|
+
bgB /= cornerIndexes.length || 1;
|
|
267
|
+
let minLuma = 255;
|
|
268
|
+
let maxLuma = 0;
|
|
269
|
+
let foreground = 0;
|
|
270
|
+
let opaque = 0;
|
|
271
|
+
for (let index = 0; index < pixelCount; index += 1) {
|
|
272
|
+
const offset = index * channels;
|
|
273
|
+
const r = pixels[offset] ?? 0;
|
|
274
|
+
const g = pixels[offset + 1] ?? 0;
|
|
275
|
+
const b = pixels[offset + 2] ?? 0;
|
|
276
|
+
const alpha = channels >= 4 ? pixels[offset + 3] ?? 255 : 255;
|
|
277
|
+
if (alpha > 8) {
|
|
278
|
+
opaque += 1;
|
|
279
|
+
}
|
|
280
|
+
const luma = (0.2126 * r) + (0.7152 * g) + (0.0722 * b);
|
|
281
|
+
minLuma = Math.min(minLuma, luma);
|
|
282
|
+
maxLuma = Math.max(maxLuma, luma);
|
|
283
|
+
const distance = Math.hypot(r - bgR, g - bgG, b - bgB);
|
|
284
|
+
if (distance >= CANVAS_BACKGROUND_DISTANCE_THRESHOLD && alpha > 8) {
|
|
285
|
+
foreground += 1;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
const foregroundRatio = foreground / pixelCount;
|
|
289
|
+
const opaqueRatio = opaque / pixelCount;
|
|
290
|
+
const colorRange = maxLuma - minLuma;
|
|
291
|
+
return {
|
|
292
|
+
foregroundRatio,
|
|
293
|
+
colorRange,
|
|
294
|
+
opaqueRatio,
|
|
295
|
+
pass: opaqueRatio > 0.05
|
|
296
|
+
&& (foregroundRatio >= MIN_CANVAS_FOREGROUND_RATIO || colorRange >= MIN_CANVAS_COLOR_RANGE),
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
async function summarizeVisibleCanvases(frame) {
|
|
300
|
+
return await frame.evaluate((config) => {
|
|
301
|
+
const canvases = Array.from(document.querySelectorAll('canvas'));
|
|
302
|
+
const visibleCanvases = canvases.filter((canvas) => {
|
|
303
|
+
const rect = canvas.getBoundingClientRect();
|
|
304
|
+
const style = window.getComputedStyle(canvas);
|
|
305
|
+
const opacity = Number.parseFloat(style.opacity || '1');
|
|
306
|
+
return rect.width >= config.minEdge
|
|
307
|
+
&& rect.height >= config.minEdge
|
|
308
|
+
&& style.display !== 'none'
|
|
309
|
+
&& style.visibility !== 'hidden'
|
|
310
|
+
&& opacity > 0.05;
|
|
311
|
+
});
|
|
312
|
+
const summaries = visibleCanvases.map((canvas) => {
|
|
313
|
+
const rect = canvas.getBoundingClientRect();
|
|
314
|
+
const sample = document.createElement('canvas');
|
|
315
|
+
sample.width = config.sampleSize;
|
|
316
|
+
sample.height = config.sampleSize;
|
|
317
|
+
const context = sample.getContext('2d', { willReadFrequently: true });
|
|
318
|
+
if (!context) {
|
|
319
|
+
return {
|
|
320
|
+
x: Math.round(rect.left),
|
|
321
|
+
y: Math.round(rect.top),
|
|
322
|
+
width: Math.round(rect.width),
|
|
323
|
+
height: Math.round(rect.height),
|
|
324
|
+
foregroundRatio: 0,
|
|
325
|
+
colorRange: 0,
|
|
326
|
+
opaqueRatio: 0,
|
|
327
|
+
readable: false,
|
|
328
|
+
pass: false,
|
|
329
|
+
error: 'canvas_2d_context_unavailable',
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
try {
|
|
333
|
+
context.drawImage(canvas, 0, 0, config.sampleSize, config.sampleSize);
|
|
334
|
+
const pixels = context.getImageData(0, 0, config.sampleSize, config.sampleSize).data;
|
|
335
|
+
const cornerIndexes = [
|
|
336
|
+
0,
|
|
337
|
+
config.sampleSize - 1,
|
|
338
|
+
config.sampleSize * (config.sampleSize - 1),
|
|
339
|
+
(config.sampleSize * config.sampleSize) - 1,
|
|
340
|
+
];
|
|
341
|
+
let bgR = 0;
|
|
342
|
+
let bgG = 0;
|
|
343
|
+
let bgB = 0;
|
|
344
|
+
for (const index of cornerIndexes) {
|
|
345
|
+
const offset = index * 4;
|
|
346
|
+
bgR += pixels[offset] ?? 0;
|
|
347
|
+
bgG += pixels[offset + 1] ?? 0;
|
|
348
|
+
bgB += pixels[offset + 2] ?? 0;
|
|
349
|
+
}
|
|
350
|
+
bgR /= cornerIndexes.length;
|
|
351
|
+
bgG /= cornerIndexes.length;
|
|
352
|
+
bgB /= cornerIndexes.length;
|
|
353
|
+
let minLuma = 255;
|
|
354
|
+
let maxLuma = 0;
|
|
355
|
+
let foreground = 0;
|
|
356
|
+
let opaque = 0;
|
|
357
|
+
const total = config.sampleSize * config.sampleSize;
|
|
358
|
+
for (let offset = 0; offset < pixels.length; offset += 4) {
|
|
359
|
+
const r = pixels[offset] ?? 0;
|
|
360
|
+
const g = pixels[offset + 1] ?? 0;
|
|
361
|
+
const b = pixels[offset + 2] ?? 0;
|
|
362
|
+
const alpha = pixels[offset + 3] ?? 0;
|
|
363
|
+
if (alpha > 8) {
|
|
364
|
+
opaque += 1;
|
|
365
|
+
}
|
|
366
|
+
const luma = (0.2126 * r) + (0.7152 * g) + (0.0722 * b);
|
|
367
|
+
minLuma = Math.min(minLuma, luma);
|
|
368
|
+
maxLuma = Math.max(maxLuma, luma);
|
|
369
|
+
const distance = Math.hypot(r - bgR, g - bgG, b - bgB);
|
|
370
|
+
if (distance >= config.backgroundDistanceThreshold && alpha > 8) {
|
|
371
|
+
foreground += 1;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
const foregroundRatio = foreground / total;
|
|
375
|
+
const opaqueRatio = opaque / total;
|
|
376
|
+
const colorRange = maxLuma - minLuma;
|
|
377
|
+
return {
|
|
378
|
+
x: Math.round(rect.left),
|
|
379
|
+
y: Math.round(rect.top),
|
|
380
|
+
width: Math.round(rect.width),
|
|
381
|
+
height: Math.round(rect.height),
|
|
382
|
+
foregroundRatio,
|
|
383
|
+
colorRange,
|
|
384
|
+
opaqueRatio,
|
|
385
|
+
readable: true,
|
|
386
|
+
pass: opaqueRatio > 0.05
|
|
387
|
+
&& (foregroundRatio >= config.minForegroundRatio || colorRange >= config.minColorRange),
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
catch (error) {
|
|
391
|
+
return {
|
|
392
|
+
x: Math.round(rect.left),
|
|
393
|
+
y: Math.round(rect.top),
|
|
394
|
+
width: Math.round(rect.width),
|
|
395
|
+
height: Math.round(rect.height),
|
|
396
|
+
foregroundRatio: 0,
|
|
397
|
+
colorRange: 0,
|
|
398
|
+
opaqueRatio: 0,
|
|
399
|
+
readable: false,
|
|
400
|
+
pass: false,
|
|
401
|
+
error: error instanceof Error ? error.message : String(error),
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
});
|
|
405
|
+
return {
|
|
406
|
+
canvasCount: canvases.length,
|
|
407
|
+
visibleCanvasCount: visibleCanvases.length,
|
|
408
|
+
readableCanvasCount: summaries.filter((summary) => summary.readable).length,
|
|
409
|
+
passingCanvasCount: summaries.filter((summary) => summary.pass).length,
|
|
410
|
+
canvases: summaries,
|
|
411
|
+
};
|
|
412
|
+
}, {
|
|
413
|
+
sampleSize: CANVAS_VISUAL_SAMPLE_SIZE,
|
|
414
|
+
minEdge: MIN_VISIBLE_CANVAS_EDGE_PX,
|
|
415
|
+
minForegroundRatio: MIN_CANVAS_FOREGROUND_RATIO,
|
|
416
|
+
minColorRange: MIN_CANVAS_COLOR_RANGE,
|
|
417
|
+
backgroundDistanceThreshold: CANVAS_BACKGROUND_DISTANCE_THRESHOLD,
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
function formatCanvasVisualSummary(summary) {
|
|
421
|
+
const readable = summary.canvases.filter(canvas => canvas.readable);
|
|
422
|
+
const first = readable[0];
|
|
423
|
+
if (!first) {
|
|
424
|
+
return `${summary.visibleCanvasCount} visible canvas(es), none readable`;
|
|
425
|
+
}
|
|
426
|
+
return `${summary.visibleCanvasCount} visible canvas(es), foregroundRatio=${first.foregroundRatio.toFixed(3)}, colorRange=${first.colorRange.toFixed(1)}, opaqueRatio=${first.opaqueRatio.toFixed(3)}`;
|
|
427
|
+
}
|
|
428
|
+
async function getFramePageOffset(frame) {
|
|
429
|
+
try {
|
|
430
|
+
const element = await frame.frameElement();
|
|
431
|
+
try {
|
|
432
|
+
const box = await element.boundingBox();
|
|
433
|
+
return box ? { x: box.x, y: box.y } : null;
|
|
434
|
+
}
|
|
435
|
+
finally {
|
|
436
|
+
await element.dispose().catch(() => { });
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
catch {
|
|
440
|
+
return null;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
function buildCanvasScreenshotClip(frameOffset, canvas, viewport) {
|
|
444
|
+
if (!Number.isFinite(canvas.x) || !Number.isFinite(canvas.y)) {
|
|
445
|
+
return null;
|
|
446
|
+
}
|
|
447
|
+
const x = Math.max(0, frameOffset.x + Number(canvas.x));
|
|
448
|
+
const y = Math.max(0, frameOffset.y + Number(canvas.y));
|
|
449
|
+
const viewportWidth = viewport?.width ?? Number.POSITIVE_INFINITY;
|
|
450
|
+
const viewportHeight = viewport?.height ?? Number.POSITIVE_INFINITY;
|
|
451
|
+
const width = Math.min(canvas.width, viewportWidth - x);
|
|
452
|
+
const height = Math.min(canvas.height, viewportHeight - y);
|
|
453
|
+
if (width < MIN_VISIBLE_CANVAS_EDGE_PX || height < MIN_VISIBLE_CANVAS_EDGE_PX) {
|
|
454
|
+
return null;
|
|
455
|
+
}
|
|
456
|
+
return { x, y, width, height };
|
|
457
|
+
}
|
|
458
|
+
async function summarizeScreenshotClip(page, clip) {
|
|
459
|
+
try {
|
|
460
|
+
const png = await page.screenshot({ type: 'png', clip });
|
|
461
|
+
const { data, info } = await (0, sharp_1.default)(png)
|
|
462
|
+
.ensureAlpha()
|
|
463
|
+
.resize(CANVAS_VISUAL_SAMPLE_SIZE, CANVAS_VISUAL_SAMPLE_SIZE, { fit: 'fill' })
|
|
464
|
+
.raw()
|
|
465
|
+
.toBuffer({ resolveWithObject: true });
|
|
466
|
+
const pixelSummary = summarizeRgbaPixels(data, info.width * info.height, info.channels);
|
|
467
|
+
return {
|
|
468
|
+
x: Math.round(clip.x),
|
|
469
|
+
y: Math.round(clip.y),
|
|
470
|
+
width: Math.round(clip.width),
|
|
471
|
+
height: Math.round(clip.height),
|
|
472
|
+
foregroundRatio: pixelSummary.foregroundRatio,
|
|
473
|
+
colorRange: pixelSummary.colorRange,
|
|
474
|
+
opaqueRatio: pixelSummary.opaqueRatio,
|
|
475
|
+
readable: true,
|
|
476
|
+
pass: pixelSummary.pass,
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
catch {
|
|
480
|
+
return null;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
async function summarizeCanvasScreenshots(page, frames, summaries) {
|
|
484
|
+
const viewport = typeof page.viewportSize === 'function' ? page.viewportSize() : null;
|
|
485
|
+
const screenshotSummaries = [];
|
|
486
|
+
for (let index = 0; index < frames.length; index += 1) {
|
|
487
|
+
const frame = frames[index];
|
|
488
|
+
const summary = summaries[index];
|
|
489
|
+
if (!frame || !summary || summary.visibleCanvasCount === 0 || summary.passingCanvasCount > 0) {
|
|
490
|
+
continue;
|
|
491
|
+
}
|
|
492
|
+
const frameOffset = await getFramePageOffset(frame);
|
|
493
|
+
if (!frameOffset) {
|
|
494
|
+
continue;
|
|
495
|
+
}
|
|
496
|
+
for (const canvas of summary.canvases) {
|
|
497
|
+
const clip = buildCanvasScreenshotClip(frameOffset, canvas, viewport);
|
|
498
|
+
if (!clip) {
|
|
499
|
+
continue;
|
|
500
|
+
}
|
|
501
|
+
const screenshotSummary = await summarizeScreenshotClip(page, clip);
|
|
502
|
+
if (screenshotSummary) {
|
|
503
|
+
screenshotSummaries.push(screenshotSummary);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
return screenshotSummaries;
|
|
508
|
+
}
|
|
509
|
+
async function assertHostedFrameVisualReadiness(page) {
|
|
510
|
+
const childFrames = page.frames().filter(frame => frame.parentFrame() !== null);
|
|
511
|
+
if (childFrames.length === 0) {
|
|
512
|
+
throw new Error('hosted_app_visual_blank: The hosted app reported ready but no game frame was available for visual validation.');
|
|
513
|
+
}
|
|
514
|
+
const summaries = await Promise.all(childFrames.map(frame => summarizeVisibleCanvases(frame)));
|
|
515
|
+
const framesWithCanvases = summaries.filter(summary => summary.visibleCanvasCount > 0);
|
|
516
|
+
if (framesWithCanvases.length === 0) {
|
|
517
|
+
return;
|
|
518
|
+
}
|
|
519
|
+
if (framesWithCanvases.some(summary => summary.passingCanvasCount > 0)) {
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
const screenshotSummaries = await summarizeCanvasScreenshots(page, childFrames, summaries);
|
|
523
|
+
if (screenshotSummaries.some(summary => summary.pass)) {
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
const readableFrames = framesWithCanvases.filter(summary => summary.readableCanvasCount > 0);
|
|
527
|
+
if (readableFrames.length === 0) {
|
|
528
|
+
return;
|
|
529
|
+
}
|
|
530
|
+
const screenshotDetail = screenshotSummaries.length > 0
|
|
531
|
+
? `; screenshot foregroundRatio=${screenshotSummaries[0].foregroundRatio.toFixed(3)}, colorRange=${screenshotSummaries[0].colorRange.toFixed(1)}, opaqueRatio=${screenshotSummaries[0].opaqueRatio.toFixed(3)}`
|
|
532
|
+
: '';
|
|
533
|
+
const detail = `${readableFrames.map(formatCanvasVisualSummary).join('; ')}${screenshotDetail}`;
|
|
534
|
+
throw new Error(`hosted_app_visual_blank: The hosted app reported ready, but its visible canvas looked empty or background-only after startup. ${detail}. Make the playable objects visible in the initial ready state.`);
|
|
535
|
+
}
|
|
235
536
|
async function writeLogFile(logPath, lines) {
|
|
236
537
|
if (!logPath || logPath.trim().length === 0) {
|
|
237
538
|
return;
|
|
@@ -623,6 +924,12 @@ async function runCapture(options) {
|
|
|
623
924
|
finalUrl = await assertPageState();
|
|
624
925
|
await page.waitForTimeout(options.settleAfterReadyMs ?? options.timeoutMs);
|
|
625
926
|
finalUrl = await assertPageState();
|
|
927
|
+
if (shouldAssertHostedVisualReadiness({
|
|
928
|
+
requireHostedLaunchReady: options.requireHostedLaunchReady,
|
|
929
|
+
expectedHostedLaunchState,
|
|
930
|
+
})) {
|
|
931
|
+
await assertHostedFrameVisualReadiness(page);
|
|
932
|
+
}
|
|
626
933
|
if (options.screenshotPath) {
|
|
627
934
|
const targetDir = (0, node_path_1.dirname)(options.screenshotPath);
|
|
628
935
|
if (targetDir && targetDir !== '.' && targetDir !== '/') {
|
|
@@ -641,6 +948,7 @@ async function runCapture(options) {
|
|
|
641
948
|
}
|
|
642
949
|
return {
|
|
643
950
|
errorCount: errors.length,
|
|
951
|
+
errors: errors.slice(0, 10),
|
|
644
952
|
warningCount: warnings.length,
|
|
645
953
|
warnings,
|
|
646
954
|
finalUrl,
|
package/dist/catalogue.d.ts
CHANGED
|
@@ -57,8 +57,8 @@ export type AppCatalogueEntry = {
|
|
|
57
57
|
ownedAssets?: OwnedAssetCatalogueEntry[];
|
|
58
58
|
assetSpecSupport?: AppMetadataAssetSpecSupport[];
|
|
59
59
|
uses?: {
|
|
60
|
-
assets?:
|
|
61
|
-
packs?:
|
|
60
|
+
assets?: unknown[];
|
|
61
|
+
packs?: unknown[];
|
|
62
62
|
};
|
|
63
63
|
tags?: string[];
|
|
64
64
|
relations?: Array<{
|
|
@@ -183,6 +183,8 @@ export type AppTask = {
|
|
|
183
183
|
version?: string;
|
|
184
184
|
releaseNotes?: string;
|
|
185
185
|
versionVisibility?: AppVersionVisibility;
|
|
186
|
+
sourceArchiveExcludeRelativeFiles?: string[];
|
|
187
|
+
bundleArchiveExcludeRelativeFiles?: string[];
|
|
186
188
|
license: ContentLicense;
|
|
187
189
|
tags: string[];
|
|
188
190
|
hostingMode?: AppHostingMode;
|
package/dist/catalogue.js
CHANGED
|
@@ -270,6 +270,37 @@ function normalizeCatalogueAppDependencyAssets(value, errors, context) {
|
|
|
270
270
|
}
|
|
271
271
|
return rows;
|
|
272
272
|
}
|
|
273
|
+
function normalizeCatalogueAppDependencyPacks(value, errors, context) {
|
|
274
|
+
if (value === undefined || value === null) {
|
|
275
|
+
return [];
|
|
276
|
+
}
|
|
277
|
+
if (!Array.isArray(value)) {
|
|
278
|
+
errors.push(`${context} uses.packs must be an array of canonical pack version ref strings.`);
|
|
279
|
+
return [];
|
|
280
|
+
}
|
|
281
|
+
const rows = [];
|
|
282
|
+
const seenRefs = new Set();
|
|
283
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
284
|
+
const entry = value[index];
|
|
285
|
+
if (typeof entry !== 'string' || entry.trim().length === 0) {
|
|
286
|
+
errors.push(`${context} uses.packs[${index}] must be a non-empty pack version ref string. Use "pack:playdrop/name@version", not { ref, runtimeKey }.`);
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
const parsed = (0, types_1.parseContentVersionRef)(entry.trim());
|
|
290
|
+
if (!parsed || parsed.kind !== 'pack') {
|
|
291
|
+
errors.push(`${context} uses.packs[${index}] must be a canonical pack version ref.`);
|
|
292
|
+
continue;
|
|
293
|
+
}
|
|
294
|
+
const normalizedRef = (0, types_1.formatContentVersionRef)(parsed);
|
|
295
|
+
if (seenRefs.has(normalizedRef)) {
|
|
296
|
+
errors.push(`${context} uses.packs contains duplicate ref "${normalizedRef}".`);
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
seenRefs.add(normalizedRef);
|
|
300
|
+
rows.push(normalizedRef);
|
|
301
|
+
}
|
|
302
|
+
return rows;
|
|
303
|
+
}
|
|
273
304
|
function rejectLegacyShopPriceCoinsField(value, errors, context) {
|
|
274
305
|
if (Object.prototype.hasOwnProperty.call(value, 'shopPriceCoins')) {
|
|
275
306
|
errors.push(`${context} uses legacy shopPriceCoins. Rename it to shopPriceCredits.`);
|
|
@@ -1130,8 +1161,13 @@ function buildAppTasks(rootDir, catalogues, options) {
|
|
|
1130
1161
|
}
|
|
1131
1162
|
}
|
|
1132
1163
|
// Hero images
|
|
1133
|
-
|
|
1134
|
-
|
|
1164
|
+
const rawHeroPortrait = typeof rawListing.heroPortrait === 'string' && rawListing.heroPortrait.trim()
|
|
1165
|
+
? rawListing.heroPortrait.trim()
|
|
1166
|
+
: typeof rawListing.heroPortraitPath === 'string' && rawListing.heroPortraitPath.trim()
|
|
1167
|
+
? rawListing.heroPortraitPath.trim()
|
|
1168
|
+
: '';
|
|
1169
|
+
if (!listingValidationFailed && rawHeroPortrait) {
|
|
1170
|
+
const heroPortraitPath = rawHeroPortrait;
|
|
1135
1171
|
const extensionError = validateListingAssetExtension(heroPortraitPath, 'listing.heroPortrait', 'image');
|
|
1136
1172
|
if (extensionError) {
|
|
1137
1173
|
errors.push(`[${label}] Skipping ${rawName}: ${extensionError}`);
|
|
@@ -1148,8 +1184,13 @@ function buildAppTasks(rootDir, catalogues, options) {
|
|
|
1148
1184
|
}
|
|
1149
1185
|
}
|
|
1150
1186
|
}
|
|
1151
|
-
|
|
1152
|
-
|
|
1187
|
+
const rawHeroLandscape = typeof rawListing.heroLandscape === 'string' && rawListing.heroLandscape.trim()
|
|
1188
|
+
? rawListing.heroLandscape.trim()
|
|
1189
|
+
: typeof rawListing.heroLandscapePath === 'string' && rawListing.heroLandscapePath.trim()
|
|
1190
|
+
? rawListing.heroLandscapePath.trim()
|
|
1191
|
+
: '';
|
|
1192
|
+
if (!listingValidationFailed && rawHeroLandscape) {
|
|
1193
|
+
const heroLandscapePath = rawHeroLandscape;
|
|
1153
1194
|
const extensionError = validateListingAssetExtension(heroLandscapePath, 'listing.heroLandscape', 'image');
|
|
1154
1195
|
if (extensionError) {
|
|
1155
1196
|
errors.push(`[${label}] Skipping ${rawName}: ${extensionError}`);
|
|
@@ -1253,10 +1294,12 @@ function buildAppTasks(rootDir, catalogues, options) {
|
|
|
1253
1294
|
}
|
|
1254
1295
|
listing = resolvedListing;
|
|
1255
1296
|
}
|
|
1297
|
+
const errorCountBeforeDependencies = errors.length;
|
|
1256
1298
|
const usesAssets = normalizeCatalogueAppDependencyAssets(entry.uses?.assets, errors, `[${label}] App "${rawName}"`);
|
|
1257
|
-
const usesPacks =
|
|
1258
|
-
|
|
1259
|
-
|
|
1299
|
+
const usesPacks = normalizeCatalogueAppDependencyPacks(entry.uses?.packs, errors, `[${label}] App "${rawName}"`);
|
|
1300
|
+
if (errors.length > errorCountBeforeDependencies) {
|
|
1301
|
+
continue;
|
|
1302
|
+
}
|
|
1260
1303
|
const errorCountBeforeTagMetadata = errors.length;
|
|
1261
1304
|
const remix = normalizeCatalogueRemixRef(entry.remix, 'app', errors, `[${label}] App "${rawName}"`);
|
|
1262
1305
|
const tags = normalizeCatalogueTags(entry.tags, errors, `[${label}] App "${rawName}"`);
|
package/dist/commandContext.js
CHANGED
|
@@ -38,8 +38,10 @@ function resolveConfiguredEnvironment(envName, command, options) {
|
|
|
38
38
|
}
|
|
39
39
|
function buildContext(cfg, envConfig, account, workspaceAuth = null) {
|
|
40
40
|
const token = account?.token ?? cfg.token ?? '';
|
|
41
|
-
const
|
|
42
|
-
const
|
|
41
|
+
const onBehalfCreatorUsername = workspaceAuth?.config.ownerUsername ?? null;
|
|
42
|
+
const agentTaskToken = workspaceAuth?.config.taskToken ?? null;
|
|
43
|
+
const client = (0, apiClient_1.createCliApiClient)({ baseUrl: envConfig.apiBase, token, onBehalfCreatorUsername, agentTaskToken });
|
|
44
|
+
const aiClient = (0, apiClient_1.createCliAiClient)({ baseUrl: envConfig.aiBase, token, onBehalfCreatorUsername, agentTaskToken });
|
|
43
45
|
return {
|
|
44
46
|
client,
|
|
45
47
|
aiClient,
|
|
@@ -124,8 +126,30 @@ function resolveWorkspaceSelectedAccount(cfg, currentAccount, workspaceAuth, req
|
|
|
124
126
|
const matchingSessions = (0, config_1.listAccountSessionsForUsername)(workspaceAuth.config.ownerUsername, cfg);
|
|
125
127
|
const preferredEnv = normalizeRequestedEnv(requestedEnv) ?? workspaceAuth.config.env;
|
|
126
128
|
if (preferredEnv) {
|
|
129
|
+
const ownerAccount = (0, config_1.findAccountSession)(workspaceAuth.config.ownerUsername, preferredEnv, cfg);
|
|
130
|
+
if (ownerAccount) {
|
|
131
|
+
return {
|
|
132
|
+
account: ownerAccount,
|
|
133
|
+
matchingSessions,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
if (workspaceAuth.config.taskToken && currentAccount) {
|
|
137
|
+
if (currentAccount.env === preferredEnv) {
|
|
138
|
+
return {
|
|
139
|
+
account: currentAccount,
|
|
140
|
+
matchingSessions,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
const currentAccountForEnv = (0, config_1.findAccountSession)(currentAccount.username, preferredEnv, cfg);
|
|
144
|
+
if (currentAccountForEnv) {
|
|
145
|
+
return {
|
|
146
|
+
account: currentAccountForEnv,
|
|
147
|
+
matchingSessions,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
}
|
|
127
151
|
return {
|
|
128
|
-
account:
|
|
152
|
+
account: null,
|
|
129
153
|
matchingSessions,
|
|
130
154
|
};
|
|
131
155
|
}
|
|
@@ -141,6 +165,21 @@ function resolveWorkspaceSelectedAccount(cfg, currentAccount, workspaceAuth, req
|
|
|
141
165
|
matchingSessions,
|
|
142
166
|
};
|
|
143
167
|
}
|
|
168
|
+
if (currentAccount) {
|
|
169
|
+
if (!preferredEnv || currentAccount.env === preferredEnv) {
|
|
170
|
+
return {
|
|
171
|
+
account: currentAccount,
|
|
172
|
+
matchingSessions,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
const currentAccountForEnv = (0, config_1.findAccountSession)(currentAccount.username, preferredEnv, cfg);
|
|
176
|
+
if (currentAccountForEnv) {
|
|
177
|
+
return {
|
|
178
|
+
account: currentAccountForEnv,
|
|
179
|
+
matchingSessions,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
}
|
|
144
183
|
return {
|
|
145
184
|
account: null,
|
|
146
185
|
matchingSessions,
|
package/dist/commands/capture.js
CHANGED
|
@@ -16,6 +16,7 @@ const captureRuntime_1 = require("../captureRuntime");
|
|
|
16
16
|
const devAuth_1 = require("../devAuth");
|
|
17
17
|
const devRuntimeAssets_1 = require("./devRuntimeAssets");
|
|
18
18
|
const dev_1 = require("./dev");
|
|
19
|
+
const dev_2 = require("./dev");
|
|
19
20
|
const MOBILE_USER_AGENT = 'Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Mobile/15E148 Safari/604.1';
|
|
20
21
|
const SURFACE_PRESETS = {
|
|
21
22
|
desktop: {
|
|
@@ -120,6 +121,7 @@ function resolveCaptureDevOptions(options) {
|
|
|
120
121
|
async function capture(targetArg, options = {}) {
|
|
121
122
|
const timeoutSeconds = (0, captureRuntime_1.validateCaptureTimeout)(options.timeoutSeconds);
|
|
122
123
|
const timeoutMs = Math.round(timeoutSeconds * 1000);
|
|
124
|
+
const devRouterPort = (0, dev_2.resolveDevRouterPort)(options.port ?? process.env.PLAYDROP_DEV_ROUTER_PORT);
|
|
123
125
|
let minimumLogLevel;
|
|
124
126
|
let loginOverride;
|
|
125
127
|
let devOptions;
|
|
@@ -242,11 +244,13 @@ async function capture(targetArg, options = {}) {
|
|
|
242
244
|
}
|
|
243
245
|
const projectInfo = (0, devShared_1.findProjectInfo)(filePath);
|
|
244
246
|
const devScriptAvailable = Boolean(projectInfo.projectDir && projectInfo.packageJson && typeof projectInfo.packageJson.scripts?.dev === 'string');
|
|
245
|
-
await (0, commandContext_1.withEnvironment)('project capture', 'Capturing app logs', async ({ client, env, envConfig }) => {
|
|
247
|
+
await (0, commandContext_1.withEnvironment)('project capture', 'Capturing app logs', async ({ client, env, envConfig, token, workspaceAuth }) => {
|
|
246
248
|
let currentUsername = '';
|
|
249
|
+
let currentUser = null;
|
|
247
250
|
try {
|
|
248
|
-
const
|
|
249
|
-
|
|
251
|
+
const fetchedCurrentUser = await (0, devShared_1.fetchDevUser)(client);
|
|
252
|
+
currentUser = fetchedCurrentUser;
|
|
253
|
+
currentUsername = fetchedCurrentUser.username.trim();
|
|
250
254
|
}
|
|
251
255
|
catch (error) {
|
|
252
256
|
if (error instanceof http_1.CLIUnsupportedClientError) {
|
|
@@ -280,8 +284,14 @@ async function capture(targetArg, options = {}) {
|
|
|
280
284
|
}
|
|
281
285
|
throw error;
|
|
282
286
|
}
|
|
283
|
-
|
|
284
|
-
|
|
287
|
+
const taskScoped = Boolean(workspaceAuth?.config.taskToken);
|
|
288
|
+
if (devOptions.selection.devAuth === 'viewer' && !loginOverride && !taskScoped) {
|
|
289
|
+
(0, messages_1.printErrorWithHelp)('Use --username and --password to establish a browser session before running --dev-auth viewer.', [], { command: 'project capture' });
|
|
290
|
+
process.exitCode = 1;
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
if (devOptions.selection.devAuth === 'player' && !loginOverride) {
|
|
294
|
+
(0, messages_1.printErrorWithHelp)('Use --username and --password to establish a browser session before running --dev-auth player.', [], { command: 'project capture' });
|
|
285
295
|
process.exitCode = 1;
|
|
286
296
|
return;
|
|
287
297
|
}
|
|
@@ -291,7 +301,7 @@ async function capture(targetArg, options = {}) {
|
|
|
291
301
|
creatorUsername: currentUsername,
|
|
292
302
|
appType: appTypeSlug,
|
|
293
303
|
appName,
|
|
294
|
-
port:
|
|
304
|
+
port: devRouterPort,
|
|
295
305
|
}, 750);
|
|
296
306
|
const devServerStartedByCapture = !serverAlreadyRunning;
|
|
297
307
|
let serverHandle = null;
|
|
@@ -328,7 +338,7 @@ async function capture(targetArg, options = {}) {
|
|
|
328
338
|
creatorUsername: currentUsername,
|
|
329
339
|
appType: appTypeSlug,
|
|
330
340
|
appName,
|
|
331
|
-
port:
|
|
341
|
+
port: devRouterPort,
|
|
332
342
|
})).toString(),
|
|
333
343
|
});
|
|
334
344
|
}
|
|
@@ -344,14 +354,14 @@ async function capture(targetArg, options = {}) {
|
|
|
344
354
|
creatorUsername: currentUsername,
|
|
345
355
|
appType: appTypeSlug,
|
|
346
356
|
appName,
|
|
347
|
-
port:
|
|
357
|
+
port: devRouterPort,
|
|
348
358
|
})}`);
|
|
349
359
|
try {
|
|
350
360
|
await (0, devServer_1.updateMountedDevRuntimeAssetManifest)({
|
|
351
361
|
creatorUsername: currentUsername,
|
|
352
362
|
appName,
|
|
353
363
|
runtimeAssetManifest,
|
|
354
|
-
port:
|
|
364
|
+
port: devRouterPort,
|
|
355
365
|
});
|
|
356
366
|
}
|
|
357
367
|
catch (error) {
|
|
@@ -369,7 +379,7 @@ async function capture(targetArg, options = {}) {
|
|
|
369
379
|
appType: appTypeSlug,
|
|
370
380
|
creatorUsername: currentUsername,
|
|
371
381
|
htmlPath: filePath,
|
|
372
|
-
port:
|
|
382
|
+
port: devRouterPort,
|
|
373
383
|
projectInfo,
|
|
374
384
|
runtimeAssetManifest,
|
|
375
385
|
});
|
|
@@ -389,7 +399,7 @@ async function capture(targetArg, options = {}) {
|
|
|
389
399
|
], { command: 'project capture' });
|
|
390
400
|
}
|
|
391
401
|
else {
|
|
392
|
-
(0, messages_1.printErrorWithHelp)(error?.message || `Failed to start the shared dev router on port ${
|
|
402
|
+
(0, messages_1.printErrorWithHelp)(error?.message || `Failed to start the shared dev router on port ${devRouterPort}.`, [
|
|
393
403
|
'Close the conflicting process or wait for the stale mount to exit.',
|
|
394
404
|
'Ensure the HTML file exists and is readable.',
|
|
395
405
|
], { command: 'project capture' });
|
|
@@ -412,7 +422,11 @@ async function capture(targetArg, options = {}) {
|
|
|
412
422
|
&& (devOptions.resetMode === 'before' || devOptions.resetMode === 'before-and-after');
|
|
413
423
|
const shouldResetAfter = devOptions.selection.devAuth === 'player'
|
|
414
424
|
&& (devOptions.resetMode === 'after' || devOptions.resetMode === 'before-and-after');
|
|
415
|
-
const
|
|
425
|
+
const savedSessionViewerCapture = devOptions.selection.devAuth === 'viewer' && !loginOverride && taskScoped;
|
|
426
|
+
const requiresRegisteredApp = devOptions.selection.devAuth === 'player'
|
|
427
|
+
|| shouldResetBefore
|
|
428
|
+
|| shouldResetAfter
|
|
429
|
+
|| (devOptions.selection.devAuth === 'viewer' && !savedSessionViewerCapture);
|
|
416
430
|
let registeredApp = null;
|
|
417
431
|
if (requiresRegisteredApp) {
|
|
418
432
|
try {
|
|
@@ -458,6 +472,7 @@ async function capture(targetArg, options = {}) {
|
|
|
458
472
|
devAuth: devOptions.selection.devAuth,
|
|
459
473
|
player: devOptions.selection.player ? String(devOptions.selection.player) : null,
|
|
460
474
|
launchCheck: true,
|
|
475
|
+
localDevPort: devRouterPort === devServer_1.DEV_ROUTER_PORT ? null : devRouterPort,
|
|
461
476
|
});
|
|
462
477
|
const frameUrl = (0, devAuth_1.applyHostedDevAuthSelectionToUrl)(captureBaseUrl, devOptions.selection);
|
|
463
478
|
console.log(`[capture] Launching Playwright against ${frameUrl}`);
|
|
@@ -484,7 +499,9 @@ async function capture(targetArg, options = {}) {
|
|
|
484
499
|
username: loginOverride.username,
|
|
485
500
|
password: loginOverride.password,
|
|
486
501
|
} : undefined,
|
|
487
|
-
|
|
502
|
+
token: savedSessionViewerCapture ? token : undefined,
|
|
503
|
+
user: savedSessionViewerCapture ? currentUser : undefined,
|
|
504
|
+
savedSessionBootstrap: savedSessionViewerCapture,
|
|
488
505
|
enableCaptureBridge: true,
|
|
489
506
|
requireHostedLaunchReady: true,
|
|
490
507
|
});
|