@playdrop/playdrop-cli 0.9.6 → 0.10.1
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 +2 -2
- package/dist/apiClient.d.ts +10 -0
- package/dist/apiClient.js +55 -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 +14 -0
- package/dist/captureRuntime.js +329 -0
- package/dist/catalogue.d.ts +4 -2
- package/dist/catalogue.js +50 -7
- package/dist/commandContext.js +61 -4
- package/dist/commands/capture.d.ts +1 -0
- package/dist/commands/capture.js +30 -13
- package/dist/commands/captureRemote.d.ts +2 -0
- package/dist/commands/captureRemote.js +90 -0
- 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/review.d.ts +46 -0
- package/dist/commands/review.js +353 -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 +81 -0
- package/dist/commands/worker/runtime.js +458 -0
- package/dist/commands/worker.d.ts +158 -0
- package/dist/commands/worker.js +2626 -0
- package/dist/config.d.ts +2 -0
- package/dist/config.js +23 -0
- package/dist/index.js +116 -30
- package/dist/shellProbe.d.ts +1 -1
- package/dist/shellProbe.js +3 -3
- package/dist/workspaceAuth.d.ts +3 -0
- package/dist/workspaceAuth.js +14 -0
- package/node_modules/@playdrop/api-client/dist/client.d.ts +36 -15
- 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 +5 -2
- package/node_modules/@playdrop/api-client/dist/domains/admin.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/domains/admin.js +51 -3
- package/node_modules/@playdrop/api-client/dist/domains/agent-tasks.d.ts +75 -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 +478 -0
- package/node_modules/@playdrop/api-client/dist/index.d.ts +36 -15
- package/node_modules/@playdrop/api-client/dist/index.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/index.js +153 -42
- package/node_modules/@playdrop/config/client-meta.json +2 -2
- package/node_modules/@playdrop/types/dist/api.d.ts +662 -75
- package/node_modules/@playdrop/types/dist/api.d.ts.map +1 -1
- package/node_modules/@playdrop/types/dist/api.js +100 -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,318 @@ 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 runCaptureActions(page, actions) {
|
|
510
|
+
if (!actions?.length) {
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
for (const action of actions) {
|
|
514
|
+
if (action.type === 'click') {
|
|
515
|
+
await page.mouse.click(action.x, action.y, { button: action.button ?? 'left' });
|
|
516
|
+
continue;
|
|
517
|
+
}
|
|
518
|
+
if (action.type === 'press') {
|
|
519
|
+
await page.keyboard.press(action.key);
|
|
520
|
+
continue;
|
|
521
|
+
}
|
|
522
|
+
if (action.type === 'wait') {
|
|
523
|
+
await page.waitForTimeout(action.ms);
|
|
524
|
+
continue;
|
|
525
|
+
}
|
|
526
|
+
throw new Error('capture_action_type_unknown');
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
async function assertHostedFrameVisualReadiness(page) {
|
|
530
|
+
const childFrames = page.frames().filter(frame => frame.parentFrame() !== null);
|
|
531
|
+
if (childFrames.length === 0) {
|
|
532
|
+
throw new Error('hosted_app_visual_blank: The hosted app reported ready but no game frame was available for visual validation.');
|
|
533
|
+
}
|
|
534
|
+
const summaries = await Promise.all(childFrames.map(frame => summarizeVisibleCanvases(frame)));
|
|
535
|
+
const framesWithCanvases = summaries.filter(summary => summary.visibleCanvasCount > 0);
|
|
536
|
+
if (framesWithCanvases.length === 0) {
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
if (framesWithCanvases.some(summary => summary.passingCanvasCount > 0)) {
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
const screenshotSummaries = await summarizeCanvasScreenshots(page, childFrames, summaries);
|
|
543
|
+
if (screenshotSummaries.some(summary => summary.pass)) {
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
const readableFrames = framesWithCanvases.filter(summary => summary.readableCanvasCount > 0);
|
|
547
|
+
if (readableFrames.length === 0) {
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
const screenshotDetail = screenshotSummaries.length > 0
|
|
551
|
+
? `; screenshot foregroundRatio=${screenshotSummaries[0].foregroundRatio.toFixed(3)}, colorRange=${screenshotSummaries[0].colorRange.toFixed(1)}, opaqueRatio=${screenshotSummaries[0].opaqueRatio.toFixed(3)}`
|
|
552
|
+
: '';
|
|
553
|
+
const detail = `${readableFrames.map(formatCanvasVisualSummary).join('; ')}${screenshotDetail}`;
|
|
554
|
+
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.`);
|
|
555
|
+
}
|
|
235
556
|
async function writeLogFile(logPath, lines) {
|
|
236
557
|
if (!logPath || logPath.trim().length === 0) {
|
|
237
558
|
return;
|
|
@@ -621,8 +942,15 @@ async function runCapture(options) {
|
|
|
621
942
|
}
|
|
622
943
|
}
|
|
623
944
|
finalUrl = await assertPageState();
|
|
945
|
+
await runCaptureActions(page, options.actions);
|
|
624
946
|
await page.waitForTimeout(options.settleAfterReadyMs ?? options.timeoutMs);
|
|
625
947
|
finalUrl = await assertPageState();
|
|
948
|
+
if (shouldAssertHostedVisualReadiness({
|
|
949
|
+
requireHostedLaunchReady: options.requireHostedLaunchReady,
|
|
950
|
+
expectedHostedLaunchState,
|
|
951
|
+
})) {
|
|
952
|
+
await assertHostedFrameVisualReadiness(page);
|
|
953
|
+
}
|
|
626
954
|
if (options.screenshotPath) {
|
|
627
955
|
const targetDir = (0, node_path_1.dirname)(options.screenshotPath);
|
|
628
956
|
if (targetDir && targetDir !== '.' && targetDir !== '/') {
|
|
@@ -641,6 +969,7 @@ async function runCapture(options) {
|
|
|
641
969
|
}
|
|
642
970
|
return {
|
|
643
971
|
errorCount: errors.length,
|
|
972
|
+
errors: errors.slice(0, 10),
|
|
644
973
|
warningCount: warnings.length,
|
|
645
974
|
warnings,
|
|
646
975
|
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,26 @@ 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 agentTaskId = workspaceAuth?.config.taskId ?? null;
|
|
44
|
+
const agentTaskAttempt = workspaceAuth?.config.taskAttempt ?? null;
|
|
45
|
+
const client = (0, apiClient_1.createCliApiClient)({
|
|
46
|
+
baseUrl: envConfig.apiBase,
|
|
47
|
+
token,
|
|
48
|
+
onBehalfCreatorUsername,
|
|
49
|
+
agentTaskToken,
|
|
50
|
+
agentTaskId,
|
|
51
|
+
agentTaskAttempt,
|
|
52
|
+
});
|
|
53
|
+
const aiClient = (0, apiClient_1.createCliAiClient)({
|
|
54
|
+
baseUrl: envConfig.aiBase,
|
|
55
|
+
token,
|
|
56
|
+
onBehalfCreatorUsername,
|
|
57
|
+
agentTaskToken,
|
|
58
|
+
agentTaskId,
|
|
59
|
+
agentTaskAttempt,
|
|
60
|
+
});
|
|
43
61
|
return {
|
|
44
62
|
client,
|
|
45
63
|
aiClient,
|
|
@@ -90,8 +108,10 @@ async function migrateLegacyConfigIfNeeded(cfg, envConfig, command) {
|
|
|
90
108
|
async function loadWorkspaceAwareConfig(command, options) {
|
|
91
109
|
let cfg = (0, config_1.loadConfig)();
|
|
92
110
|
let workspaceAuth = null;
|
|
111
|
+
const workspacePath = options.workspacePath
|
|
112
|
+
?? (process.env.PLAYDROP_WORKER_CONTEXT === '1' ? process.cwd() : undefined);
|
|
93
113
|
try {
|
|
94
|
-
workspaceAuth =
|
|
114
|
+
workspaceAuth = workspacePath ? (0, workspaceAuth_1.findWorkspaceAuthConfig)(workspacePath) : null;
|
|
95
115
|
}
|
|
96
116
|
catch (error) {
|
|
97
117
|
if (error instanceof workspaceAuth_1.WorkspaceAuthConfigError) {
|
|
@@ -124,8 +144,30 @@ function resolveWorkspaceSelectedAccount(cfg, currentAccount, workspaceAuth, req
|
|
|
124
144
|
const matchingSessions = (0, config_1.listAccountSessionsForUsername)(workspaceAuth.config.ownerUsername, cfg);
|
|
125
145
|
const preferredEnv = normalizeRequestedEnv(requestedEnv) ?? workspaceAuth.config.env;
|
|
126
146
|
if (preferredEnv) {
|
|
147
|
+
const ownerAccount = (0, config_1.findAccountSession)(workspaceAuth.config.ownerUsername, preferredEnv, cfg);
|
|
148
|
+
if (ownerAccount) {
|
|
149
|
+
return {
|
|
150
|
+
account: ownerAccount,
|
|
151
|
+
matchingSessions,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
if (workspaceAuth.config.taskToken && currentAccount) {
|
|
155
|
+
if (currentAccount.env === preferredEnv) {
|
|
156
|
+
return {
|
|
157
|
+
account: currentAccount,
|
|
158
|
+
matchingSessions,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
const currentAccountForEnv = (0, config_1.findAccountSession)(currentAccount.username, preferredEnv, cfg);
|
|
162
|
+
if (currentAccountForEnv) {
|
|
163
|
+
return {
|
|
164
|
+
account: currentAccountForEnv,
|
|
165
|
+
matchingSessions,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
}
|
|
127
169
|
return {
|
|
128
|
-
account:
|
|
170
|
+
account: null,
|
|
129
171
|
matchingSessions,
|
|
130
172
|
};
|
|
131
173
|
}
|
|
@@ -141,6 +183,21 @@ function resolveWorkspaceSelectedAccount(cfg, currentAccount, workspaceAuth, req
|
|
|
141
183
|
matchingSessions,
|
|
142
184
|
};
|
|
143
185
|
}
|
|
186
|
+
if (currentAccount) {
|
|
187
|
+
if (!preferredEnv || currentAccount.env === preferredEnv) {
|
|
188
|
+
return {
|
|
189
|
+
account: currentAccount,
|
|
190
|
+
matchingSessions,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
const currentAccountForEnv = (0, config_1.findAccountSession)(currentAccount.username, preferredEnv, cfg);
|
|
194
|
+
if (currentAccountForEnv) {
|
|
195
|
+
return {
|
|
196
|
+
account: currentAccountForEnv,
|
|
197
|
+
matchingSessions,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
}
|
|
144
201
|
return {
|
|
145
202
|
account: null,
|
|
146
203
|
matchingSessions,
|