@playdrop/playdrop-cli 0.13.15 → 0.13.17
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/staticHtml.js +1 -0
- package/dist/commandContext.js +1 -1
- package/dist/commands/captureListing.d.ts +43 -0
- package/dist/commands/captureListing.js +184 -5
- package/dist/commands/upload.d.ts +12 -1
- package/dist/commands/upload.js +97 -88
- package/dist/commands/whoami.js +10 -1
- package/dist/commands/worker/runtime.js +2 -0
- package/dist/commands/worker.d.ts +28 -1
- package/dist/commands/worker.js +376 -28
- package/dist/index.js +17 -2
- package/dist/listingPreflight.d.ts +2 -1
- package/dist/listingPreflight.js +49 -18
- package/dist/workerAppProject.d.ts +2 -0
- package/dist/workerAppProject.js +33 -0
- package/node_modules/@playdrop/api-client/dist/client.d.ts +5 -2
- package/node_modules/@playdrop/api-client/dist/client.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/domains/agent-tasks.d.ts +27 -1
- package/node_modules/@playdrop/api-client/dist/domains/agent-tasks.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/domains/agent-tasks.js +103 -0
- package/node_modules/@playdrop/api-client/dist/index.d.ts +4 -2
- package/node_modules/@playdrop/api-client/dist/index.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/index.js +10 -0
- package/node_modules/@playdrop/config/client-meta.json +2 -2
- package/node_modules/@playdrop/types/dist/api.d.ts +150 -2
- package/node_modules/@playdrop/types/dist/api.d.ts.map +1 -1
- package/node_modules/@playdrop/types/dist/api.js +3 -1
- package/package.json +1 -1
package/config/client-meta.json
CHANGED
package/dist/apps/staticHtml.js
CHANGED
|
@@ -240,6 +240,7 @@ function buildStaticAppTask(input) {
|
|
|
240
240
|
hasValidateScript: false,
|
|
241
241
|
hasFormatScript: false,
|
|
242
242
|
displayName: input.metadata.title,
|
|
243
|
+
...(input.metadata.description ? { subtitle: input.metadata.description } : {}),
|
|
243
244
|
...(input.metadata.description ? { description: input.metadata.description } : {}),
|
|
244
245
|
type: 'GAME',
|
|
245
246
|
surfaceTargets: [...STATIC_GAME_SURFACES],
|
package/dist/commandContext.js
CHANGED
|
@@ -154,7 +154,7 @@ function resolveWorkspaceSelectedAccount(cfg, currentAccount, workspaceAuth, req
|
|
|
154
154
|
matchingSessions,
|
|
155
155
|
};
|
|
156
156
|
}
|
|
157
|
-
if (
|
|
157
|
+
if (currentAccount) {
|
|
158
158
|
if (currentAccount.env === preferredEnv) {
|
|
159
159
|
return {
|
|
160
160
|
account: currentAccount,
|
|
@@ -22,6 +22,7 @@ type ParsedCaptureListingOptions = {
|
|
|
22
22
|
posterAtSeconds: number;
|
|
23
23
|
audio: boolean;
|
|
24
24
|
outputDir: string | null;
|
|
25
|
+
outputDirInput: string | null;
|
|
25
26
|
keepRaw: boolean;
|
|
26
27
|
explicitDimensions: boolean;
|
|
27
28
|
};
|
|
@@ -70,6 +71,43 @@ type ListingRecorderMetadata = {
|
|
|
70
71
|
recordedDurationSeconds: number;
|
|
71
72
|
recordedFileSizeBytes: number;
|
|
72
73
|
};
|
|
74
|
+
type ListingCaptureSurfaceReport = {
|
|
75
|
+
surface: AppSurface;
|
|
76
|
+
targetUrl: string;
|
|
77
|
+
command: {
|
|
78
|
+
durationSeconds: number;
|
|
79
|
+
width: number;
|
|
80
|
+
height: number;
|
|
81
|
+
fps: number;
|
|
82
|
+
posterAtSeconds: number;
|
|
83
|
+
audio: boolean;
|
|
84
|
+
keepRaw: boolean;
|
|
85
|
+
};
|
|
86
|
+
measurement: HostedGameMeasurement;
|
|
87
|
+
recorder: ListingRecorderMetadata;
|
|
88
|
+
crop: CropRect;
|
|
89
|
+
rawVideo: {
|
|
90
|
+
path: string | null;
|
|
91
|
+
width: number;
|
|
92
|
+
height: number;
|
|
93
|
+
durationSeconds: number;
|
|
94
|
+
audioTrackCount: number;
|
|
95
|
+
};
|
|
96
|
+
finalVideo: {
|
|
97
|
+
path: string;
|
|
98
|
+
width: number;
|
|
99
|
+
height: number;
|
|
100
|
+
durationSeconds: number;
|
|
101
|
+
audioTrackCount: number;
|
|
102
|
+
fps: number | null;
|
|
103
|
+
};
|
|
104
|
+
posterPath: string;
|
|
105
|
+
warnings: string[];
|
|
106
|
+
};
|
|
107
|
+
export type ExportedListingAudio = {
|
|
108
|
+
mimeType: string;
|
|
109
|
+
base64: string;
|
|
110
|
+
};
|
|
73
111
|
export declare function resolveListingCaptureRouteSegment(previewable: boolean): 'dev-preview';
|
|
74
112
|
export declare function resolveCaptureListingDevRouterPort(env?: NodeJS.ProcessEnv): number;
|
|
75
113
|
export declare function resolveListingCaptureLocalDevPort(devRouterPort: number): number | null;
|
|
@@ -84,6 +122,7 @@ export declare function buildListingCaptureFrameUrl({ webBase, currentUsername,
|
|
|
84
122
|
}): string;
|
|
85
123
|
export declare function resolveListingCaptureSceneId(width: number, height: number): 'listing-landscape' | 'listing-portrait';
|
|
86
124
|
export declare function parseCaptureListingOptions(targetArg: string | undefined, options?: CaptureListingOptions): ParsedCaptureListingOptions;
|
|
125
|
+
export declare function resolveCaptureListingExplicitOutputDir(outputDirInput: string | null, cwdResolvedOutputDir: string | null, projectDir: string): string | null;
|
|
87
126
|
export declare function assertSupportedListingEnvironment(platform?: NodeJS.Platform, macosVersion?: string): void;
|
|
88
127
|
export declare function resolveListingRecorderPath(workerHome?: string): string;
|
|
89
128
|
export declare function buildSurfaceOutputPaths(outputDir: string, surface: AppSurface, useSurfacePrefix: boolean): {
|
|
@@ -92,6 +131,7 @@ export declare function buildSurfaceOutputPaths(outputDir: string, surface: AppS
|
|
|
92
131
|
posterPath: string;
|
|
93
132
|
finalVideoPath: string;
|
|
94
133
|
};
|
|
134
|
+
export declare function resolveWorkerCaptureOutputDir(projectDir: string, isWorkerGameTask: boolean): string | null;
|
|
95
135
|
export declare function resolveCaptureDimensions(surface: AppSurface, parsedOptions: ParsedCaptureListingOptions): {
|
|
96
136
|
width: number;
|
|
97
137
|
height: number;
|
|
@@ -100,7 +140,10 @@ export declare function resolveListingCaptureBrowserContextOptions(surface: AppS
|
|
|
100
140
|
width: number;
|
|
101
141
|
height: number;
|
|
102
142
|
}): BrowserContextOptions;
|
|
143
|
+
export declare function assertExportedListingAudio(value: unknown): ExportedListingAudio;
|
|
103
144
|
export declare function computeRecordedCrop(measurement: HostedGameMeasurement, recorder: ListingRecorderMetadata, rawWidth: number, rawHeight: number): CropRect;
|
|
104
145
|
export declare function runListingRecorder(recorderPath: string, pid: number, durationSeconds: number, rawOutputPath: string, metadataPath: string, audio: boolean, deadlineAt: number): Promise<ListingRecorderMetadata>;
|
|
146
|
+
export declare function captureCommandMatchesExceptPoster(capture: ListingCaptureSurfaceReport, surface: AppSurface, parsedOptions: ParsedCaptureListingOptions): boolean;
|
|
147
|
+
export declare function resolveCurrentCaptureReuseAction(captures: ListingCaptureSurfaceReport[], surfaces: AppSurface[], parsedOptions: ParsedCaptureListingOptions): 'reuse' | 'refresh_poster';
|
|
105
148
|
export declare function captureListing(targetArg: string | undefined, options?: CaptureListingOptions): Promise<void>;
|
|
106
149
|
export {};
|
|
@@ -39,13 +39,18 @@ exports.resolveListingCaptureLocalDevPort = resolveListingCaptureLocalDevPort;
|
|
|
39
39
|
exports.buildListingCaptureFrameUrl = buildListingCaptureFrameUrl;
|
|
40
40
|
exports.resolveListingCaptureSceneId = resolveListingCaptureSceneId;
|
|
41
41
|
exports.parseCaptureListingOptions = parseCaptureListingOptions;
|
|
42
|
+
exports.resolveCaptureListingExplicitOutputDir = resolveCaptureListingExplicitOutputDir;
|
|
42
43
|
exports.assertSupportedListingEnvironment = assertSupportedListingEnvironment;
|
|
43
44
|
exports.resolveListingRecorderPath = resolveListingRecorderPath;
|
|
44
45
|
exports.buildSurfaceOutputPaths = buildSurfaceOutputPaths;
|
|
46
|
+
exports.resolveWorkerCaptureOutputDir = resolveWorkerCaptureOutputDir;
|
|
45
47
|
exports.resolveCaptureDimensions = resolveCaptureDimensions;
|
|
46
48
|
exports.resolveListingCaptureBrowserContextOptions = resolveListingCaptureBrowserContextOptions;
|
|
49
|
+
exports.assertExportedListingAudio = assertExportedListingAudio;
|
|
47
50
|
exports.computeRecordedCrop = computeRecordedCrop;
|
|
48
51
|
exports.runListingRecorder = runListingRecorder;
|
|
52
|
+
exports.captureCommandMatchesExceptPoster = captureCommandMatchesExceptPoster;
|
|
53
|
+
exports.resolveCurrentCaptureReuseAction = resolveCurrentCaptureReuseAction;
|
|
49
54
|
exports.captureListing = captureListing;
|
|
50
55
|
const types_1 = require("@playdrop/types");
|
|
51
56
|
const node_child_process_1 = require("node:child_process");
|
|
@@ -53,10 +58,12 @@ const node_crypto_1 = require("node:crypto");
|
|
|
53
58
|
const promises_1 = require("node:fs/promises");
|
|
54
59
|
const node_path_1 = require("node:path");
|
|
55
60
|
const appUrls_1 = require("../appUrls");
|
|
61
|
+
const build_1 = require("../apps/build");
|
|
56
62
|
const catalogue_1 = require("../catalogue");
|
|
57
63
|
const catalogue_utils_1 = require("../catalogue-utils");
|
|
58
64
|
const commandContext_1 = require("../commandContext");
|
|
59
65
|
const http_1 = require("../http");
|
|
66
|
+
const listingPreflight_1 = require("../listingPreflight");
|
|
60
67
|
const messages_1 = require("../messages");
|
|
61
68
|
const playwright_1 = require("../playwright");
|
|
62
69
|
const recorderRelease_1 = require("../recorderRelease");
|
|
@@ -66,6 +73,7 @@ const devRuntimeAssets_1 = require("./devRuntimeAssets");
|
|
|
66
73
|
const devServer_1 = require("./devServer");
|
|
67
74
|
const devShared_1 = require("./devShared");
|
|
68
75
|
const taskCaptureSession_1 = require("./taskCaptureSession");
|
|
76
|
+
const workerAppProject_1 = require("../workerAppProject");
|
|
69
77
|
const CAPTURE_FRAME_SELECTOR = 'iframe[title="Game"]';
|
|
70
78
|
const DEFAULT_DURATION_SECONDS = 8;
|
|
71
79
|
const DEFAULT_WIDTH = 1280;
|
|
@@ -158,7 +166,8 @@ function parseCaptureListingOptions(targetArg, options = {}) {
|
|
|
158
166
|
minimum: 0,
|
|
159
167
|
maximum: durationSeconds,
|
|
160
168
|
});
|
|
161
|
-
const
|
|
169
|
+
const outputDirInput = options.outputDir?.trim() || null;
|
|
170
|
+
const outputDir = outputDirInput ? (0, node_path_1.resolve)(process.cwd(), outputDirInput) : null;
|
|
162
171
|
return {
|
|
163
172
|
targetArg,
|
|
164
173
|
appName: options.app?.trim() || undefined,
|
|
@@ -169,10 +178,18 @@ function parseCaptureListingOptions(targetArg, options = {}) {
|
|
|
169
178
|
posterAtSeconds,
|
|
170
179
|
audio: Boolean(options.audio),
|
|
171
180
|
outputDir,
|
|
181
|
+
outputDirInput,
|
|
172
182
|
keepRaw: Boolean(options.keepRaw),
|
|
173
183
|
explicitDimensions,
|
|
174
184
|
};
|
|
175
185
|
}
|
|
186
|
+
function resolveCaptureListingExplicitOutputDir(outputDirInput, cwdResolvedOutputDir, projectDir) {
|
|
187
|
+
if (!outputDirInput || !cwdResolvedOutputDir)
|
|
188
|
+
return null;
|
|
189
|
+
return (0, node_path_1.isAbsolute)(outputDirInput)
|
|
190
|
+
? cwdResolvedOutputDir
|
|
191
|
+
: (0, node_path_1.resolve)(projectDir, outputDirInput);
|
|
192
|
+
}
|
|
176
193
|
function readMacOsVersion() {
|
|
177
194
|
const result = (0, node_child_process_1.spawnSync)('sw_vers', ['-productVersion'], {
|
|
178
195
|
encoding: 'utf8',
|
|
@@ -231,6 +248,11 @@ function buildSurfaceOutputPaths(outputDir, surface, useSurfacePrefix) {
|
|
|
231
248
|
finalVideoPath: (0, node_path_1.join)(outputDir, `${prefix}listing.mp4`),
|
|
232
249
|
};
|
|
233
250
|
}
|
|
251
|
+
function resolveWorkerCaptureOutputDir(projectDir, isWorkerGameTask) {
|
|
252
|
+
return isWorkerGameTask
|
|
253
|
+
? (0, node_path_1.join)(projectDir, 'assets', 'marketing', 'playdrop', 'capture')
|
|
254
|
+
: null;
|
|
255
|
+
}
|
|
234
256
|
function resolveCaptureDimensions(surface, parsedOptions) {
|
|
235
257
|
if (parsedOptions.explicitDimensions) {
|
|
236
258
|
return {
|
|
@@ -432,6 +454,21 @@ async function startHostedListingAudioCapture(page) {
|
|
|
432
454
|
await hook.startAudioCapture();
|
|
433
455
|
});
|
|
434
456
|
}
|
|
457
|
+
function assertExportedListingAudio(value) {
|
|
458
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
459
|
+
throw new Error('listing_audio_capture_export_missing');
|
|
460
|
+
}
|
|
461
|
+
const mimeType = typeof value.mimeType === 'string'
|
|
462
|
+
? value.mimeType.trim()
|
|
463
|
+
: '';
|
|
464
|
+
const base64 = typeof value.base64 === 'string'
|
|
465
|
+
? value.base64.trim()
|
|
466
|
+
: '';
|
|
467
|
+
if (!mimeType || !base64) {
|
|
468
|
+
throw new Error('listing_audio_capture_export_missing');
|
|
469
|
+
}
|
|
470
|
+
return { mimeType, base64 };
|
|
471
|
+
}
|
|
435
472
|
function resolveExportedAudioFileName(mimeType) {
|
|
436
473
|
if (mimeType.includes('webm')) {
|
|
437
474
|
return 'listing-audio.webm';
|
|
@@ -443,18 +480,23 @@ function resolveExportedAudioFileName(mimeType) {
|
|
|
443
480
|
}
|
|
444
481
|
async function stopHostedListingAudioCapture(page, outputDir) {
|
|
445
482
|
const frame = await waitForHostedGameFrame(page, 20000);
|
|
446
|
-
const exportedAudio = await frame.evaluate(async () => {
|
|
483
|
+
const exportedAudio = assertExportedListingAudio(await frame.evaluate(async () => {
|
|
447
484
|
const captureWindow = window;
|
|
448
485
|
const hook = captureWindow.__listingCapture;
|
|
449
486
|
if (!hook || typeof hook.stopAudioCapture !== 'function') {
|
|
450
487
|
throw new Error('listing_audio_capture_hook_missing');
|
|
451
488
|
}
|
|
452
489
|
return hook.stopAudioCapture();
|
|
453
|
-
});
|
|
490
|
+
}));
|
|
454
491
|
const filePath = (0, node_path_1.join)(outputDir, resolveExportedAudioFileName(exportedAudio.mimeType));
|
|
455
492
|
await (0, promises_1.writeFile)(filePath, Buffer.from(exportedAudio.base64, 'base64'));
|
|
456
493
|
return filePath;
|
|
457
494
|
}
|
|
495
|
+
async function assertHostedListingAudioCaptureContract(page, outputDir) {
|
|
496
|
+
await startHostedListingAudioCapture(page);
|
|
497
|
+
await page.waitForTimeout(300);
|
|
498
|
+
await stopHostedListingAudioCapture(page, outputDir);
|
|
499
|
+
}
|
|
458
500
|
async function fitWindowToRequestedGameplay(page, requestedWidth, requestedHeight) {
|
|
459
501
|
const cdpSession = await page.context().newCDPSession(page);
|
|
460
502
|
let measurement = await waitForHostedGameMeasurement(page, 15000);
|
|
@@ -666,6 +708,24 @@ function formatCommandError(error) {
|
|
|
666
708
|
suggestions: ['Set previewable: true in catalogue.json, implement the preview scene and __listingCapture hooks, then rerun "playdrop project capture".'],
|
|
667
709
|
};
|
|
668
710
|
}
|
|
711
|
+
if (error.message === 'listing_audio_capture_export_missing') {
|
|
712
|
+
return {
|
|
713
|
+
message: 'The game preview audio hook did not export recorded audio.',
|
|
714
|
+
suggestions: ['Make window.__listingCapture.stopAudioCapture() resolve { mimeType, base64 }; toggling game audio on or off is not an export.'],
|
|
715
|
+
};
|
|
716
|
+
}
|
|
717
|
+
if (error.message === 'listing_capture_runtime_already_recorded') {
|
|
718
|
+
return {
|
|
719
|
+
message: 'This exact game runtime already has a canonical recording with different capture settings.',
|
|
720
|
+
suggestions: ['Use the existing video, or change and revalidate the preview implementation before recording the changed runtime once.'],
|
|
721
|
+
};
|
|
722
|
+
}
|
|
723
|
+
if (error.message === 'listing_capture_current_report_incomplete') {
|
|
724
|
+
return {
|
|
725
|
+
message: 'The canonical capture report is incomplete for this runtime.',
|
|
726
|
+
suggestions: ['Fix or remove the incomplete canonical capture directory, then record the runtime once.'],
|
|
727
|
+
};
|
|
728
|
+
}
|
|
669
729
|
if (error.message.startsWith('listing_recorder_failed:')) {
|
|
670
730
|
return {
|
|
671
731
|
message: error.message.slice('listing_recorder_failed:'.length),
|
|
@@ -710,6 +770,77 @@ async function encodeListingVideo(rawVideoPath, finalVideoPath, posterPath, crop
|
|
|
710
770
|
posterPath,
|
|
711
771
|
], 'ffmpeg_failed', deadlineAt);
|
|
712
772
|
}
|
|
773
|
+
function captureCommandMatchesExceptPoster(capture, surface, parsedOptions) {
|
|
774
|
+
const dimensions = resolveCaptureDimensions(surface, parsedOptions);
|
|
775
|
+
return capture.command.durationSeconds === parsedOptions.durationSeconds
|
|
776
|
+
&& capture.command.width === dimensions.width
|
|
777
|
+
&& capture.command.height === dimensions.height
|
|
778
|
+
&& capture.command.fps === parsedOptions.fps
|
|
779
|
+
&& capture.command.audio === parsedOptions.audio
|
|
780
|
+
&& capture.command.keepRaw === parsedOptions.keepRaw;
|
|
781
|
+
}
|
|
782
|
+
function resolveCurrentCaptureReuseAction(captures, surfaces, parsedOptions) {
|
|
783
|
+
if (captures.length !== surfaces.length) {
|
|
784
|
+
throw new Error('listing_capture_current_report_incomplete');
|
|
785
|
+
}
|
|
786
|
+
const orderedCaptures = surfaces.map((surface) => captures.find((candidate) => candidate.surface === surface));
|
|
787
|
+
if (orderedCaptures.some((capture) => !capture)) {
|
|
788
|
+
throw new Error('listing_capture_current_report_incomplete');
|
|
789
|
+
}
|
|
790
|
+
if (orderedCaptures.some((capture, index) => !captureCommandMatchesExceptPoster(capture, surfaces[index], parsedOptions))) {
|
|
791
|
+
throw new Error('listing_capture_runtime_already_recorded');
|
|
792
|
+
}
|
|
793
|
+
return orderedCaptures.some((capture) => capture.command.posterAtSeconds !== parsedOptions.posterAtSeconds)
|
|
794
|
+
? 'refresh_poster'
|
|
795
|
+
: 'reuse';
|
|
796
|
+
}
|
|
797
|
+
async function reuseCurrentListingCapture(input) {
|
|
798
|
+
let report;
|
|
799
|
+
try {
|
|
800
|
+
report = JSON.parse(await (0, promises_1.readFile)(input.outputPaths.reportPath, 'utf8'));
|
|
801
|
+
}
|
|
802
|
+
catch (error) {
|
|
803
|
+
if (error?.code === 'ENOENT') {
|
|
804
|
+
return false;
|
|
805
|
+
}
|
|
806
|
+
throw new Error(`listing_capture_report_invalid:${input.outputPaths.reportPath}`);
|
|
807
|
+
}
|
|
808
|
+
if (report.appName !== input.appName) {
|
|
809
|
+
throw new Error(`listing_capture_report_app_mismatch:${report.appName}:${input.appName}`);
|
|
810
|
+
}
|
|
811
|
+
if (!input.runtimeBundleHash || report.binding?.runtimeBundleHash !== input.runtimeBundleHash) {
|
|
812
|
+
return false;
|
|
813
|
+
}
|
|
814
|
+
const action = resolveCurrentCaptureReuseAction(report.captures, input.surfaces, input.parsedOptions);
|
|
815
|
+
const captures = input.surfaces.map((surface) => report.captures.find((candidate) => candidate.surface === surface));
|
|
816
|
+
for (const capture of captures) {
|
|
817
|
+
await (0, promises_1.access)(capture.finalVideo.path);
|
|
818
|
+
}
|
|
819
|
+
if (action === 'reuse') {
|
|
820
|
+
console.log('[listing] The current runtime already has a matching canonical capture; reusing it.');
|
|
821
|
+
console.log(`[listing] Existing report: ${(0, node_path_1.relative)(process.cwd(), input.outputPaths.reportPath) || input.outputPaths.reportPath}`);
|
|
822
|
+
return true;
|
|
823
|
+
}
|
|
824
|
+
for (const capture of captures) {
|
|
825
|
+
runTool('ffmpeg', [
|
|
826
|
+
'-y',
|
|
827
|
+
'-ss',
|
|
828
|
+
String(input.parsedOptions.posterAtSeconds),
|
|
829
|
+
'-i',
|
|
830
|
+
capture.finalVideo.path,
|
|
831
|
+
'-frames:v',
|
|
832
|
+
'1',
|
|
833
|
+
capture.posterPath,
|
|
834
|
+
], 'ffmpeg_failed', input.deadlineAt);
|
|
835
|
+
capture.command.posterAtSeconds = input.parsedOptions.posterAtSeconds;
|
|
836
|
+
const artifactKey = (0, node_path_1.relative)(input.outputPaths.outputDir, capture.posterPath) || 'poster.png';
|
|
837
|
+
report.binding.artifactHashes[artifactKey] = await computeFileSha256(capture.posterPath);
|
|
838
|
+
}
|
|
839
|
+
await (0, promises_1.writeFile)(input.outputPaths.reportPath, `${JSON.stringify(report, null, 2)}\n`, 'utf8');
|
|
840
|
+
console.log(`[listing] Refreshed the canonical poster at ${input.parsedOptions.posterAtSeconds}s without relaunching the game.`);
|
|
841
|
+
console.log(`[listing] Updated report: ${(0, node_path_1.relative)(process.cwd(), input.outputPaths.reportPath) || input.outputPaths.reportPath}`);
|
|
842
|
+
return true;
|
|
843
|
+
}
|
|
713
844
|
async function muxListingAudio(finalVideoPath, audioPath, deadlineAt) {
|
|
714
845
|
const muxedVideoPath = `${finalVideoPath}.muxed.mp4`;
|
|
715
846
|
runTool('ffmpeg', [
|
|
@@ -824,8 +955,20 @@ async function captureListing(targetArg, options = {}) {
|
|
|
824
955
|
process.exitCode = 1;
|
|
825
956
|
return;
|
|
826
957
|
}
|
|
827
|
-
const outputPaths = await ensureOutputPaths(appName, parsedOptions.outputDir);
|
|
828
958
|
const projectInfo = (0, devShared_1.findProjectInfo)(resolvedTarget.htmlPath);
|
|
959
|
+
const projectDir = projectInfo.projectDir ?? (0, node_path_1.dirname)(resolvedTarget.htmlPath);
|
|
960
|
+
let isWorkerGameTask = false;
|
|
961
|
+
try {
|
|
962
|
+
isWorkerGameTask = Boolean((0, listingPreflight_1.readWorkerUploadPreflightContext)(projectDir));
|
|
963
|
+
}
|
|
964
|
+
catch (error) {
|
|
965
|
+
const detail = formatCommandError(error instanceof Error ? error : new Error(String(error)));
|
|
966
|
+
(0, messages_1.printErrorWithHelp)(detail.message, detail.suggestions, { command: 'project capture' });
|
|
967
|
+
process.exitCode = 1;
|
|
968
|
+
return;
|
|
969
|
+
}
|
|
970
|
+
const explicitOutputDir = resolveCaptureListingExplicitOutputDir(parsedOptions.outputDirInput, parsedOptions.outputDir, projectDir);
|
|
971
|
+
const outputPaths = await ensureOutputPaths(appName, explicitOutputDir ?? resolveWorkerCaptureOutputDir(projectDir, isWorkerGameTask));
|
|
829
972
|
const devScriptAvailable = Boolean(projectInfo.projectDir && projectInfo.packageJson && typeof projectInfo.packageJson.scripts?.dev === 'string');
|
|
830
973
|
await (0, commandContext_1.withEnvironment)('project capture', 'Capturing game media', async ({ client, env, envConfig, account, workspaceAuth }) => {
|
|
831
974
|
let currentUsername = '';
|
|
@@ -902,6 +1045,40 @@ async function captureListing(targetArg, options = {}) {
|
|
|
902
1045
|
process.exitCode = 1;
|
|
903
1046
|
return;
|
|
904
1047
|
}
|
|
1048
|
+
let runtimeBundleHash = null;
|
|
1049
|
+
if (taskLookup.task) {
|
|
1050
|
+
try {
|
|
1051
|
+
const captureTask = (0, listingPreflight_1.readWorkerUploadPreflightContext)(taskLookup.task.projectDir)
|
|
1052
|
+
? (0, workerAppProject_1.prepareWorkerAppTask)(taskLookup.task)
|
|
1053
|
+
: taskLookup.task;
|
|
1054
|
+
runtimeBundleHash = (await (0, build_1.buildApp)(captureTask)).bundleHash;
|
|
1055
|
+
}
|
|
1056
|
+
catch (error) {
|
|
1057
|
+
(0, messages_1.printErrorWithHelp)(error instanceof Error ? error.message : String(error), ['Fix the game build before recording final gameplay.'], { command: 'project capture' });
|
|
1058
|
+
process.exitCode = 1;
|
|
1059
|
+
return;
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
const captureDeadlineAt = Date.now() + listingCaptureLease_1.LISTING_CAPTURE_MAX_DURATION_MS;
|
|
1063
|
+
try {
|
|
1064
|
+
const reused = await reuseCurrentListingCapture({
|
|
1065
|
+
appName,
|
|
1066
|
+
outputPaths,
|
|
1067
|
+
parsedOptions,
|
|
1068
|
+
runtimeBundleHash,
|
|
1069
|
+
surfaces: resolvedTarget.surfaceTargets.list,
|
|
1070
|
+
deadlineAt: captureDeadlineAt,
|
|
1071
|
+
});
|
|
1072
|
+
if (reused) {
|
|
1073
|
+
return;
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
catch (error) {
|
|
1077
|
+
const detail = formatCommandError(error instanceof Error ? error : new Error(String(error)));
|
|
1078
|
+
(0, messages_1.printErrorWithHelp)(detail.message, detail.suggestions, { command: 'project capture' });
|
|
1079
|
+
process.exitCode = 1;
|
|
1080
|
+
return;
|
|
1081
|
+
}
|
|
905
1082
|
const localDevAppUrl = (0, devServer_1.buildLocalDevAppUrl)({
|
|
906
1083
|
creatorUsername: currentUsername,
|
|
907
1084
|
appType: appTypeSlug,
|
|
@@ -926,7 +1103,6 @@ async function captureListing(targetArg, options = {}) {
|
|
|
926
1103
|
return;
|
|
927
1104
|
}
|
|
928
1105
|
}
|
|
929
|
-
const captureDeadlineAt = Date.now() + listingCaptureLease_1.LISTING_CAPTURE_MAX_DURATION_MS;
|
|
930
1106
|
let serverAlreadyRunning = false;
|
|
931
1107
|
let devServerStartedByCapture = false;
|
|
932
1108
|
let serverHandle = null;
|
|
@@ -1020,6 +1196,8 @@ async function captureListing(targetArg, options = {}) {
|
|
|
1020
1196
|
await browserHandle.page.waitForTimeout(1000);
|
|
1021
1197
|
}
|
|
1022
1198
|
if (shouldExportPreviewAudio) {
|
|
1199
|
+
console.log('[listing] Checking the in-app preview audio export contract.');
|
|
1200
|
+
await assertHostedListingAudioCaptureContract(browserHandle.page, outputPaths.outputDir);
|
|
1023
1201
|
console.log('[listing] Starting in-app preview audio capture.');
|
|
1024
1202
|
await startHostedListingAudioCapture(browserHandle.page);
|
|
1025
1203
|
}
|
|
@@ -1126,6 +1304,7 @@ async function captureListing(targetArg, options = {}) {
|
|
|
1126
1304
|
binding: {
|
|
1127
1305
|
artifactHashes,
|
|
1128
1306
|
capturedSurfaces,
|
|
1307
|
+
runtimeBundleHash,
|
|
1129
1308
|
},
|
|
1130
1309
|
};
|
|
1131
1310
|
await (0, promises_1.writeFile)(outputPaths.reportPath, `${JSON.stringify(report, null, 2)}\n`, 'utf8');
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ApiClient } from '@playdrop/api-client';
|
|
2
|
-
import type { AgentExecutionTarget, UserResponse } from '@playdrop/types';
|
|
2
|
+
import type { AgentExecutionTarget, AppSurface, UserResponse } from '@playdrop/types';
|
|
3
3
|
import { type AppTask } from '../catalogue';
|
|
4
4
|
export type UploadCommandOptions = {
|
|
5
5
|
env?: string;
|
|
@@ -17,6 +17,9 @@ export type WorkerAppPublishInput = {
|
|
|
17
17
|
kind: 'NEW_GAME' | 'REMIX_GAME' | 'GAME_UPDATE';
|
|
18
18
|
executionTarget?: AgentExecutionTarget;
|
|
19
19
|
expectedAppName?: string | null;
|
|
20
|
+
expectedDisplayName?: string | null;
|
|
21
|
+
expectedSubtitle?: string | null;
|
|
22
|
+
expectedPrimarySurface?: AppSurface | null;
|
|
20
23
|
remixSourceRef?: string | null;
|
|
21
24
|
playdropAssetRequirement?: WorkerPlaydropAssetRequirement | null;
|
|
22
25
|
creatorRequest?: string | null;
|
|
@@ -69,6 +72,14 @@ export declare function assertWorkerAppLocalUploadPreflight(input: {
|
|
|
69
72
|
creatorUsername?: string | null;
|
|
70
73
|
playdropAssetRequirement?: WorkerPlaydropAssetRequirement | null;
|
|
71
74
|
creatorRequest?: string | null;
|
|
75
|
+
stage?: 'PRE_CAPTURE' | 'COMPLETE';
|
|
76
|
+
expectedRuntimeBundleHash?: string;
|
|
72
77
|
}): Promise<void>;
|
|
78
|
+
export type WorkerAppPreflightResult = {
|
|
79
|
+
appName: string;
|
|
80
|
+
creatorUsername: string;
|
|
81
|
+
warnings: string[];
|
|
82
|
+
};
|
|
83
|
+
export declare function preflightWorkerAppProject(input: WorkerAppPublishInput): Promise<WorkerAppPreflightResult>;
|
|
73
84
|
export declare function publishWorkerAppProject(input: WorkerAppPublishInput): Promise<WorkerAppPublishResult>;
|
|
74
85
|
export declare function publishStaticHtmlProject(input: StaticHtmlPublishInput): Promise<StaticHtmlPublishResult>;
|