@playdrop/playdrop-cli 0.11.9 → 0.12.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 +2 -2
- package/dist/apps/build.js +2 -3
- package/dist/apps/index.d.ts +4 -5
- package/dist/apps/index.js +14 -16
- package/dist/apps/loadCheck.d.ts +76 -0
- package/dist/apps/loadCheck.js +874 -0
- package/dist/apps/upload.d.ts +3 -2
- package/dist/apps/upload.js +110 -6
- package/dist/catalogue-utils.d.ts +1 -0
- package/dist/catalogue-utils.js +1 -0
- package/dist/catalogue.d.ts +2 -0
- package/dist/catalogue.js +17 -0
- package/dist/commands/build.js +4 -1
- package/dist/commands/captureListing.d.ts +30 -1
- package/dist/commands/captureListing.js +305 -120
- package/dist/commands/check.d.ts +8 -0
- package/dist/commands/check.js +188 -0
- package/dist/commands/review.d.ts +20 -0
- package/dist/commands/review.js +112 -0
- package/dist/commands/upload-content.d.ts +1 -1
- package/dist/commands/upload.d.ts +2 -1
- package/dist/commands/upload.js +9 -52
- package/dist/commands/validate.js +0 -155
- package/dist/commands/worker/runtime.d.ts +1 -0
- package/dist/commands/worker/runtime.js +43 -3
- package/dist/commands/worker.d.ts +1 -1
- package/dist/commands/worker.js +22 -44
- package/dist/index.js +33 -49
- package/dist/playwright.d.ts +5 -0
- package/dist/playwright.js +60 -1
- package/node_modules/@playdrop/api-client/dist/client.d.ts +4 -2
- package/node_modules/@playdrop/api-client/dist/client.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/client.js +0 -10
- package/node_modules/@playdrop/api-client/dist/domains/apps.d.ts +2 -1
- package/node_modules/@playdrop/api-client/dist/domains/apps.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/domains/apps.js +11 -0
- package/node_modules/@playdrop/api-client/dist/domains/auth.d.ts +3 -1
- package/node_modules/@playdrop/api-client/dist/domains/auth.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/domains/auth.js +22 -0
- package/node_modules/@playdrop/api-client/dist/index.d.ts +4 -3
- package/node_modules/@playdrop/api-client/dist/index.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/index.js +10 -5
- package/node_modules/@playdrop/config/client-meta.json +2 -2
- package/node_modules/@playdrop/config/dist/src/client-meta.d.ts +8 -0
- package/node_modules/@playdrop/config/dist/src/client-meta.d.ts.map +1 -0
- package/node_modules/@playdrop/config/dist/src/client-meta.js +51 -0
- package/node_modules/@playdrop/config/dist/src/deployment.d.ts.map +1 -1
- package/node_modules/@playdrop/config/dist/src/deployment.js +2 -32
- package/node_modules/@playdrop/config/dist/src/index.d.ts.map +1 -1
- package/node_modules/@playdrop/config/dist/src/index.js +2 -28
- package/node_modules/@playdrop/config/dist/src/runtime-service-origins.d.ts +0 -1
- package/node_modules/@playdrop/config/dist/src/runtime-service-origins.d.ts.map +1 -1
- package/node_modules/@playdrop/config/dist/src/runtime-service-origins.js +21 -5
- package/node_modules/@playdrop/config/dist/test/runtime-service-origins.test.js +8 -0
- package/node_modules/@playdrop/config/dist/tsconfig.tsbuildinfo +1 -1
- package/node_modules/@playdrop/types/dist/api.d.ts +19 -14
- package/node_modules/@playdrop/types/dist/api.d.ts.map +1 -1
- package/node_modules/@playdrop/types/dist/api.js +5 -10
- package/node_modules/@playdrop/types/dist/version.d.ts +14 -0
- package/node_modules/@playdrop/types/dist/version.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/apps/launchCheck.d.ts +0 -39
- package/dist/apps/launchCheck.js +0 -414
- package/dist/captureRuntime.d.ts +0 -73
- package/dist/captureRuntime.js +0 -1021
- package/dist/commands/capture.d.ts +0 -14
- package/dist/commands/capture.js +0 -548
- package/dist/commands/captureRemote.d.ts +0 -17
- package/dist/commands/captureRemote.js +0 -394
- package/node_modules/@playdrop/types/dist/entity.d.ts +0 -31
- package/node_modules/@playdrop/types/dist/entity.d.ts.map +0 -1
- package/node_modules/@playdrop/types/dist/entity.js +0 -67
package/dist/apps/upload.d.ts
CHANGED
|
@@ -17,8 +17,9 @@ export type AppUploadOptions = {
|
|
|
17
17
|
creatorUsername?: string;
|
|
18
18
|
token?: string | null;
|
|
19
19
|
user?: UserResponse | null;
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
runStagedUploadLoadCheck?: boolean;
|
|
21
|
+
loadCheckTimeoutMs?: number;
|
|
22
22
|
agentTaskId?: number;
|
|
23
|
+
mediaCaptureRequired?: boolean;
|
|
23
24
|
};
|
|
24
25
|
export declare function uploadApp(client: ApiClient, task: AppTask, artifacts: AppBuildArtifacts | null, options?: AppUploadOptions): Promise<AppUploadResult>;
|
package/dist/apps/upload.js
CHANGED
|
@@ -7,7 +7,7 @@ const node_crypto_1 = require("node:crypto");
|
|
|
7
7
|
const node_fs_1 = require("node:fs");
|
|
8
8
|
const node_path_1 = require("node:path");
|
|
9
9
|
const http_1 = require("../http");
|
|
10
|
-
const
|
|
10
|
+
const loadCheck_1 = require("./loadCheck");
|
|
11
11
|
const EXTENSION_TO_MIME = {
|
|
12
12
|
'.png': 'image/png',
|
|
13
13
|
'.jpg': 'image/jpeg',
|
|
@@ -74,6 +74,97 @@ async function bufferFromFile(file) {
|
|
|
74
74
|
function computeSha256Hex(buffer) {
|
|
75
75
|
return (0, node_crypto_1.createHash)('sha256').update(buffer).digest('hex');
|
|
76
76
|
}
|
|
77
|
+
function isSha256Hex(value) {
|
|
78
|
+
return typeof value === 'string' && /^[a-f0-9]{64}$/i.test(value.trim());
|
|
79
|
+
}
|
|
80
|
+
function isCaptureMediaFileKey(fileKey) {
|
|
81
|
+
return fileKey.startsWith('screenshotsPortrait:')
|
|
82
|
+
|| fileKey.startsWith('screenshotsLandscape:')
|
|
83
|
+
|| fileKey.startsWith('videosPortrait:')
|
|
84
|
+
|| fileKey.startsWith('videosLandscape:');
|
|
85
|
+
}
|
|
86
|
+
function readMediaCaptureProof(input) {
|
|
87
|
+
const reportPath = input.task.listing?.captureReportPath;
|
|
88
|
+
if (!reportPath) {
|
|
89
|
+
throw new Error(`agent_task_media_capture_report_missing: ${input.task.name} must include listing.captureReport from "playdrop project capture".`);
|
|
90
|
+
}
|
|
91
|
+
let report;
|
|
92
|
+
try {
|
|
93
|
+
report = JSON.parse((0, node_fs_1.readFileSync)(reportPath, 'utf8'));
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
97
|
+
throw new Error(`agent_task_media_capture_report_invalid: ${reportPath} must be valid JSON. ${reason}`);
|
|
98
|
+
}
|
|
99
|
+
const binding = report?.binding;
|
|
100
|
+
if (!binding || typeof binding !== 'object' || Array.isArray(binding)) {
|
|
101
|
+
throw new Error(`agent_task_media_capture_report_invalid: ${reportPath} is missing binding.`);
|
|
102
|
+
}
|
|
103
|
+
const contentHash = typeof binding.contentHash === 'string' ? binding.contentHash.trim().toLowerCase() : '';
|
|
104
|
+
if (!isSha256Hex(contentHash)) {
|
|
105
|
+
throw new Error(`agent_task_media_capture_report_invalid: ${reportPath} binding.contentHash must be a SHA-256 hex string.`);
|
|
106
|
+
}
|
|
107
|
+
if (contentHash !== input.artifacts.bundleHash) {
|
|
108
|
+
throw new Error(`agent_task_media_capture_build_mismatch: capture report contentHash does not match the bundle being uploaded for ${input.task.name}. Rerun "playdrop project capture" after the final build changes.`);
|
|
109
|
+
}
|
|
110
|
+
const rawArtifactHashes = binding.artifactHashes;
|
|
111
|
+
if (!rawArtifactHashes || typeof rawArtifactHashes !== 'object' || Array.isArray(rawArtifactHashes)) {
|
|
112
|
+
throw new Error(`agent_task_media_capture_report_invalid: ${reportPath} binding.artifactHashes must be an object.`);
|
|
113
|
+
}
|
|
114
|
+
const captureArtifactHashEntries = Object.values(rawArtifactHashes)
|
|
115
|
+
.map((value) => typeof value === 'string' ? value.trim().toLowerCase() : '')
|
|
116
|
+
.filter((value) => isSha256Hex(value));
|
|
117
|
+
const captureArtifactHashes = new Set(captureArtifactHashEntries);
|
|
118
|
+
if (captureArtifactHashEntries.length === 0) {
|
|
119
|
+
throw new Error(`agent_task_media_capture_report_invalid: ${reportPath} binding.artifactHashes is empty.`);
|
|
120
|
+
}
|
|
121
|
+
const captureArtifactHashCounts = new Map();
|
|
122
|
+
for (const hash of captureArtifactHashEntries) {
|
|
123
|
+
captureArtifactHashCounts.set(hash, (captureArtifactHashCounts.get(hash) ?? 0) + 1);
|
|
124
|
+
}
|
|
125
|
+
const mediaFiles = input.preparedSessionFiles.filter((file) => isCaptureMediaFileKey(file.fileKey));
|
|
126
|
+
if (mediaFiles.length === 0) {
|
|
127
|
+
throw new Error(`agent_task_media_capture_artifacts_missing: ${input.task.name} must upload recorder stills and video in listing.screenshots* and listing.videos*.`);
|
|
128
|
+
}
|
|
129
|
+
const uploadedMediaHashCounts = new Map();
|
|
130
|
+
for (const file of mediaFiles) {
|
|
131
|
+
uploadedMediaHashCounts.set(file.sha256, (uploadedMediaHashCounts.get(file.sha256) ?? 0) + 1);
|
|
132
|
+
}
|
|
133
|
+
const missingUploadedCaptureArtifacts = [...captureArtifactHashCounts.entries()]
|
|
134
|
+
.filter(([hash, expectedCount]) => (uploadedMediaHashCounts.get(hash) ?? 0) < expectedCount);
|
|
135
|
+
if (missingUploadedCaptureArtifacts.length > 0) {
|
|
136
|
+
throw new Error(`agent_task_media_capture_artifact_missing_from_listing: ${input.task.name} must list every recorder poster and video from listing.captureReport in catalogue.json.`);
|
|
137
|
+
}
|
|
138
|
+
let hasStill = false;
|
|
139
|
+
let hasVideo = false;
|
|
140
|
+
const artifactHashes = {};
|
|
141
|
+
for (const file of mediaFiles) {
|
|
142
|
+
if (!captureArtifactHashes.has(file.sha256)) {
|
|
143
|
+
throw new Error(`agent_task_media_capture_artifact_mismatch: ${file.fileKey} was not produced by listing.captureReport for ${input.task.name}.`);
|
|
144
|
+
}
|
|
145
|
+
hasStill = hasStill || file.fileKey.startsWith('screenshots');
|
|
146
|
+
hasVideo = hasVideo || file.fileKey.startsWith('videos');
|
|
147
|
+
artifactHashes[file.fileKey] = file.sha256;
|
|
148
|
+
}
|
|
149
|
+
if (!hasStill || !hasVideo) {
|
|
150
|
+
throw new Error(`agent_task_media_capture_artifacts_missing: ${input.task.name} must upload at least one recorder still and one recorder video.`);
|
|
151
|
+
}
|
|
152
|
+
const capturedSurfaces = Array.isArray(binding.capturedSurfaces)
|
|
153
|
+
? Array.from(new Set(binding.capturedSurfaces
|
|
154
|
+
.map((surface) => typeof surface === 'string' ? surface.trim().toUpperCase() : '')
|
|
155
|
+
.filter((surface) => input.task.surfaceTargets.includes(surface))))
|
|
156
|
+
: [];
|
|
157
|
+
const capturedSurfaceSet = new Set(capturedSurfaces);
|
|
158
|
+
const missingSurfaces = input.task.surfaceTargets.filter((surface) => !capturedSurfaceSet.has(surface));
|
|
159
|
+
if (missingSurfaces.length > 0) {
|
|
160
|
+
throw new Error(`agent_task_media_capture_surface_missing: listing.captureReport did not capture ${missingSurfaces.join(', ')} for ${input.task.name}.`);
|
|
161
|
+
}
|
|
162
|
+
return {
|
|
163
|
+
contentHash,
|
|
164
|
+
artifactHashes,
|
|
165
|
+
capturedSurfaces,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
77
168
|
function resolveMimeType(filename, fallback = 'application/octet-stream') {
|
|
78
169
|
const type = EXTENSION_TO_MIME[(0, node_path_1.extname)(filename).toLowerCase()];
|
|
79
170
|
return type ?? fallback;
|
|
@@ -339,20 +430,33 @@ async function uploadAppVersion(client, task, artifacts, options) {
|
|
|
339
430
|
})),
|
|
340
431
|
});
|
|
341
432
|
}
|
|
433
|
+
if (options?.mediaCaptureRequired === true
|
|
434
|
+
&& task.hostingMode !== 'EXTERNAL'
|
|
435
|
+
&& !task.externalUrl) {
|
|
436
|
+
if (!artifacts) {
|
|
437
|
+
throw new Error(`agent_task_media_capture_build_missing: ${task.name} has no hosted build artifacts to bind capture media to.`);
|
|
438
|
+
}
|
|
439
|
+
const mediaCaptureProof = readMediaCaptureProof({
|
|
440
|
+
task,
|
|
441
|
+
artifacts,
|
|
442
|
+
preparedSessionFiles,
|
|
443
|
+
});
|
|
444
|
+
await client.recordAppUploadMediaCapture(creatorUsername, task.name, sessionId, mediaCaptureProof);
|
|
445
|
+
}
|
|
342
446
|
if (task.hostingMode !== 'EXTERNAL'
|
|
343
447
|
&& !task.externalUrl
|
|
344
|
-
&& options?.
|
|
345
|
-
const
|
|
448
|
+
&& options?.runStagedUploadLoadCheck) {
|
|
449
|
+
const loadCheck = await (0, loadCheck_1.runUploadedHostedLoadCheck)({
|
|
346
450
|
client,
|
|
347
451
|
creatorUsername,
|
|
348
452
|
task,
|
|
349
453
|
sessionId,
|
|
350
|
-
timeoutMs: options.
|
|
454
|
+
timeoutMs: options.loadCheckTimeoutMs,
|
|
351
455
|
token: options?.token ?? null,
|
|
352
456
|
currentUser: options?.user ?? null,
|
|
353
457
|
});
|
|
354
|
-
if (
|
|
355
|
-
throw new Error((0,
|
|
458
|
+
if (loadCheck.status !== 'PASSED') {
|
|
459
|
+
throw new Error((0, loadCheck_1.formatHostedLoadCheckFailure)(task.name, loadCheck, 'staged'));
|
|
356
460
|
}
|
|
357
461
|
}
|
|
358
462
|
}
|
package/dist/catalogue-utils.js
CHANGED
package/dist/catalogue.d.ts
CHANGED
|
@@ -19,6 +19,7 @@ export type AppListingConfig = {
|
|
|
19
19
|
screenshotsLandscape?: string[];
|
|
20
20
|
videosPortrait?: string[];
|
|
21
21
|
videosLandscape?: string[];
|
|
22
|
+
captureReport?: string;
|
|
22
23
|
};
|
|
23
24
|
export type ResolvedAppAchievementDefinition = Omit<AppAchievementCatalogueDefinition, 'status'> & {
|
|
24
25
|
iconPath: string;
|
|
@@ -152,6 +153,7 @@ export type ResolvedListingAssets = {
|
|
|
152
153
|
screenshotLandscapePaths: string[];
|
|
153
154
|
videoPortraitPaths: string[];
|
|
154
155
|
videoLandscapePaths: string[];
|
|
156
|
+
captureReportPath?: string;
|
|
155
157
|
};
|
|
156
158
|
export type AppDependencyAssetRef = {
|
|
157
159
|
ref: string;
|
package/dist/catalogue.js
CHANGED
|
@@ -1249,6 +1249,23 @@ function buildAppTasks(rootDir, catalogues, options) {
|
|
|
1249
1249
|
}
|
|
1250
1250
|
}
|
|
1251
1251
|
}
|
|
1252
|
+
if (!listingValidationFailed && typeof rawListing.captureReport === 'string' && rawListing.captureReport.trim()) {
|
|
1253
|
+
const captureReportPath = rawListing.captureReport.trim();
|
|
1254
|
+
if ((0, node_path_1.extname)(captureReportPath).toLowerCase() !== '.json') {
|
|
1255
|
+
errors.push(`[${label}] Skipping ${rawName}: listing.captureReport must point to a JSON file.`);
|
|
1256
|
+
listingValidationFailed = true;
|
|
1257
|
+
}
|
|
1258
|
+
else {
|
|
1259
|
+
const validationError = validateListingAssetPath(file.directory, captureReportPath, 'listing.captureReport');
|
|
1260
|
+
if (validationError) {
|
|
1261
|
+
errors.push(`[${label}] Skipping ${rawName}: ${validationError}`);
|
|
1262
|
+
listingValidationFailed = true;
|
|
1263
|
+
}
|
|
1264
|
+
else {
|
|
1265
|
+
resolvedListing.captureReportPath = resolveListingAssetPath(file.directory, captureReportPath);
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1252
1269
|
if (listingValidationFailed) {
|
|
1253
1270
|
continue;
|
|
1254
1271
|
}
|
package/dist/commands/build.js
CHANGED
|
@@ -23,9 +23,11 @@ async function build(pathOrName) {
|
|
|
23
23
|
for (const task of (0, taskUtils_1.sortTasks)(tasks)) {
|
|
24
24
|
const entityId = task.name;
|
|
25
25
|
try {
|
|
26
|
+
let detail;
|
|
26
27
|
if (task.kind === 'app') {
|
|
27
28
|
await (0, apps_1.validateAppTask)(task);
|
|
28
|
-
await (0, apps_1.buildApp)(task);
|
|
29
|
+
const artifacts = await (0, apps_1.buildApp)(task);
|
|
30
|
+
detail = `bundleHash=${artifacts.bundleHash}`;
|
|
29
31
|
}
|
|
30
32
|
else if (task.kind === 'asset' || task.kind === 'owned-asset') {
|
|
31
33
|
const generated = await (0, model_artifacts_1.prepareModel3DAssetArtifacts)(task);
|
|
@@ -42,6 +44,7 @@ async function build(pathOrName) {
|
|
|
42
44
|
entityType: task.kind,
|
|
43
45
|
entityId,
|
|
44
46
|
catalogue: task.cataloguePath,
|
|
47
|
+
detail,
|
|
45
48
|
};
|
|
46
49
|
results.push(entry);
|
|
47
50
|
console.log((0, uploadLog_1.formatTaskLogLine)(entry));
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { type AppSurface } from '@playdrop/types';
|
|
2
|
+
import type { BrowserContextOptions } from 'playwright-core';
|
|
1
3
|
type CaptureListingOptions = {
|
|
2
4
|
app?: string;
|
|
3
5
|
duration?: string | number;
|
|
@@ -8,6 +10,7 @@ type CaptureListingOptions = {
|
|
|
8
10
|
audio?: boolean;
|
|
9
11
|
outputDir?: string;
|
|
10
12
|
keepRaw?: boolean;
|
|
13
|
+
contentHash?: string;
|
|
11
14
|
};
|
|
12
15
|
type ParsedCaptureListingOptions = {
|
|
13
16
|
targetArg?: string;
|
|
@@ -20,6 +23,8 @@ type ParsedCaptureListingOptions = {
|
|
|
20
23
|
audio: boolean;
|
|
21
24
|
outputDir: string | null;
|
|
22
25
|
keepRaw: boolean;
|
|
26
|
+
contentHash: string | null;
|
|
27
|
+
explicitDimensions: boolean;
|
|
23
28
|
};
|
|
24
29
|
type CaptureRect = {
|
|
25
30
|
x: number;
|
|
@@ -45,11 +50,35 @@ type CropRect = {
|
|
|
45
50
|
width: number;
|
|
46
51
|
height: number;
|
|
47
52
|
};
|
|
48
|
-
export declare function resolveListingCaptureRouteSegment(previewable: boolean): 'dev
|
|
53
|
+
export declare function resolveListingCaptureRouteSegment(previewable: boolean): 'dev-preview';
|
|
54
|
+
export declare function resolveCaptureListingDevRouterPort(env?: NodeJS.ProcessEnv): number;
|
|
55
|
+
export declare function resolveListingCaptureLocalDevPort(devRouterPort: number): number | null;
|
|
56
|
+
export declare function buildListingCaptureFrameUrl({ webBase, currentUsername, appTypeSlug, appName, routeSegment, localDevPort, }: {
|
|
57
|
+
webBase: string;
|
|
58
|
+
currentUsername: string;
|
|
59
|
+
appTypeSlug: string;
|
|
60
|
+
appName: string;
|
|
61
|
+
routeSegment: 'dev-preview';
|
|
62
|
+
localDevPort: number | null;
|
|
63
|
+
}): string;
|
|
49
64
|
export declare function resolveListingCaptureSceneId(width: number, height: number): 'listing-landscape' | 'listing-portrait';
|
|
50
65
|
export declare function parseCaptureListingOptions(targetArg: string | undefined, options?: CaptureListingOptions): ParsedCaptureListingOptions;
|
|
51
66
|
export declare function assertSupportedListingEnvironment(platform?: NodeJS.Platform, macosVersion?: string): void;
|
|
52
67
|
export declare function resolveListingRecorderPath(baseDir?: string): string;
|
|
68
|
+
export declare function buildSurfaceOutputPaths(outputDir: string, surface: AppSurface, useSurfacePrefix: boolean): {
|
|
69
|
+
rawVideoPath: string;
|
|
70
|
+
metadataPath: string;
|
|
71
|
+
posterPath: string;
|
|
72
|
+
finalVideoPath: string;
|
|
73
|
+
};
|
|
74
|
+
export declare function resolveCaptureDimensions(surface: AppSurface, parsedOptions: ParsedCaptureListingOptions): {
|
|
75
|
+
width: number;
|
|
76
|
+
height: number;
|
|
77
|
+
};
|
|
78
|
+
export declare function resolveListingCaptureBrowserContextOptions(surface: AppSurface, dimensions: {
|
|
79
|
+
width: number;
|
|
80
|
+
height: number;
|
|
81
|
+
}): BrowserContextOptions;
|
|
53
82
|
export declare function computeRecordedCrop(measurement: HostedGameMeasurement, rawWidth: number, rawHeight: number): CropRect;
|
|
54
83
|
export declare function captureListing(targetArg: string | undefined, options?: CaptureListingOptions): Promise<void>;
|
|
55
84
|
export {};
|