@playdrop/playdrop-cli 0.14.0 → 0.14.2

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.
@@ -10,54 +10,6 @@ const clientInfo_1 = require("../clientInfo");
10
10
  const build_1 = require("./build");
11
11
  const projectValidateCache = new Map();
12
12
  const projectFormatCache = new Map();
13
- const LEGACY_SCAN_EXTENSIONS = new Set(['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs', '.html']);
14
- const LEGACY_SCAN_IGNORED_DIRS = new Set(['node_modules', '.git', 'dist-test', 'coverage']);
15
- const SDK_SCAN_EXTENSIONS = new Set(['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs', '.mts', '.cts', '.vue', '.svelte', '.astro', '.html']);
16
- const SDK_SCAN_IGNORED_DIRS = new Set([
17
- 'node_modules',
18
- '.git',
19
- 'dist',
20
- 'build',
21
- 'out',
22
- '.next',
23
- '.turbo',
24
- '.cache',
25
- '.svelte-kit',
26
- 'coverage',
27
- 'tmp',
28
- 'logs',
29
- 'test',
30
- 'tests',
31
- '__tests__',
32
- ]);
33
- const SDK_ENTRY_HTML_FILENAMES = ['index.html', 'main.html', 'template.html'];
34
- const SDK_ENTRY_SOURCE_BASENAMES = ['main', 'index', 'app', 'bootstrap', 'entry', 'client'];
35
- const SDK_ENTRY_ROOT_DIRS = ['src', 'app', 'pages'];
36
- const SDK_SPECIAL_ENTRY_PATHS = ['app/page', 'app/layout', 'pages/index', 'pages/_app'];
37
- const LEGACY_SDK_SYMBOL_PATTERNS = [
38
- { symbol: 'selectedAvatarKey', pattern: /\bselectedAvatarKey\b/g },
39
- { symbol: 'sdk.entities', pattern: /\bsdk\s*\.\s*entities\b/g },
40
- { symbol: 'sdk.assets.avatar', pattern: /\bsdk\s*\.\s*assets\s*\.\s*avatar\b/g },
41
- { symbol: 'sdk.assets.block', pattern: /\bsdk\s*\.\s*assets\s*\.\s*block\b/g },
42
- { symbol: 'loadEntity(', pattern: /\.\s*loadEntity\s*\(/g },
43
- ];
44
- const SDK_REFERENCE_PATTERNS = [
45
- /\/sdk\/playdrop\.js(?:[?"'])/g,
46
- /@playdrop\/sdk(?:\/browser)?/g,
47
- ];
48
- const SDK_INIT_PATTERNS = [
49
- /\bwindow\s*\.\s*playdrop\s*\.\s*init\s*\(/g,
50
- /\bplaydrop\s*\.\s*init\s*\(/g,
51
- ];
52
- const SDK_READY_PATTERNS = [
53
- /\bsdk\s*\??\.\s*host\s*\??\.\s*ready\s*\(/g,
54
- ];
55
- const LOCAL_HTML_SCRIPT_PATTERN = /<script\b[^>]*\bsrc\s*=\s*["']([^"']+)["'][^>]*>/gi;
56
- const LOCAL_IMPORT_PATTERNS = [
57
- /(?:import|export)\s+(?:[^'"`]*?\s+from\s+)?["']([^"']+)["']/g,
58
- /import\s*\(\s*["']([^"']+)["']\s*\)/g,
59
- ];
60
- const LOCAL_SCRIPT_COMMAND_PATTERN = /(?:^|(?:&&|\|\||;)\s*)(?:node(?:\s+--[^\s"'`;|&]+)*|tsx|ts-node(?:-esm)?|bun|deno\s+run)\s+(["']?)([^"'`\s;&|]+)\1/g;
61
13
  function ensureWithinProject(task) {
62
14
  const relativePath = (0, node_path_1.relative)(task.projectDir, task.filePath);
63
15
  const segments = relativePath.split(/[/\\]+/);
@@ -75,84 +27,6 @@ async function ensureValidateScript(task) {
75
27
  }
76
28
  await projectValidateCache.get(task.projectDir);
77
29
  }
78
- function scanForLegacySdkSymbols(task) {
79
- const findings = [];
80
- const scanFile = (filePath) => {
81
- const extension = (0, node_path_1.extname)(filePath).toLowerCase();
82
- if (!LEGACY_SCAN_EXTENSIONS.has(extension)) {
83
- return;
84
- }
85
- const source = (0, node_fs_1.readFileSync)(filePath, 'utf8');
86
- for (const { symbol, pattern } of LEGACY_SDK_SYMBOL_PATTERNS) {
87
- pattern.lastIndex = 0;
88
- const hit = pattern.exec(source);
89
- if (!hit) {
90
- continue;
91
- }
92
- const index = typeof hit.index === 'number' ? hit.index : 0;
93
- const line = source.slice(0, index).split('\n').length;
94
- findings.push({
95
- file: (0, node_path_1.relative)(task.projectDir, filePath),
96
- line,
97
- symbol,
98
- });
99
- }
100
- };
101
- const walk = (directory) => {
102
- const entries = (0, node_fs_1.readdirSync)(directory, { withFileTypes: true });
103
- for (const entry of entries) {
104
- if (LEGACY_SCAN_IGNORED_DIRS.has(entry.name)) {
105
- continue;
106
- }
107
- const nextPath = (0, node_path_1.join)(directory, entry.name);
108
- if (entry.isDirectory()) {
109
- walk(nextPath);
110
- }
111
- else if (entry.isFile()) {
112
- scanFile(nextPath);
113
- }
114
- }
115
- };
116
- walk(task.projectDir);
117
- if (findings.length > 0) {
118
- const details = findings
119
- .slice(0, 10)
120
- .map((entry) => `${entry.file}:${entry.line} (${entry.symbol})`)
121
- .join(', ');
122
- const suffix = findings.length > 10 ? ` (+${findings.length - 10} more)` : '';
123
- throw new Error(`[apps][validate] legacy SDK symbols detected for ${task.name}: ${details}${suffix}. Remove entity-era APIs before publishing.`);
124
- }
125
- }
126
- function stripSpecifierDecorators(specifier) {
127
- return specifier.split(/[?#]/, 1)[0]?.trim() ?? '';
128
- }
129
- function escapeRegexLiteral(value) {
130
- return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
131
- }
132
- function detectSdkInitInSource(source) {
133
- if (SDK_INIT_PATTERNS.some((pattern) => {
134
- pattern.lastIndex = 0;
135
- return pattern.test(source);
136
- })) {
137
- return true;
138
- }
139
- const playdropAliases = new Set();
140
- const aliasAssignmentPattern = /\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*[^;\n]*\bplaydrop\b[^;\n]*/g;
141
- let aliasMatch;
142
- while ((aliasMatch = aliasAssignmentPattern.exec(source)) !== null) {
143
- const alias = aliasMatch[1]?.trim();
144
- if (alias) {
145
- playdropAliases.add(alias);
146
- }
147
- }
148
- for (const alias of playdropAliases) {
149
- const aliasInitPattern = new RegExp(`\\b${escapeRegexLiteral(alias)}\\s*(?:\\.\\s*|\\?\\.\\s*)init\\s*\\(`, 'g');
150
- if (aliasInitPattern.test(source)) {
151
- return true;
152
- }
153
- }
154
- return false;
155
- }
156
30
  function safeReadFile(filePath) {
157
31
  try {
158
32
  return (0, node_fs_1.readFileSync)(filePath, 'utf8');
@@ -161,280 +35,6 @@ function safeReadFile(filePath) {
161
35
  return null;
162
36
  }
163
37
  }
164
- function isIgnoredSdkScanDirectory(name) {
165
- return SDK_SCAN_IGNORED_DIRS.has(name);
166
- }
167
- function resolveSdkModulePath(basePath) {
168
- const tryFile = (candidate) => {
169
- if (!(0, node_fs_1.existsSync)(candidate)) {
170
- return null;
171
- }
172
- try {
173
- return (0, node_fs_1.statSync)(candidate).isFile() ? candidate : null;
174
- }
175
- catch {
176
- return null;
177
- }
178
- };
179
- const direct = tryFile(basePath);
180
- if (direct) {
181
- return direct;
182
- }
183
- if (!(0, node_path_1.extname)(basePath)) {
184
- for (const extension of SDK_SCAN_EXTENSIONS) {
185
- const withExtension = tryFile(`${basePath}${extension}`);
186
- if (withExtension) {
187
- return withExtension;
188
- }
189
- }
190
- }
191
- if ((0, node_fs_1.existsSync)(basePath)) {
192
- try {
193
- if ((0, node_fs_1.statSync)(basePath).isDirectory()) {
194
- for (const extension of SDK_SCAN_EXTENSIONS) {
195
- const indexCandidate = tryFile((0, node_path_1.join)(basePath, `index${extension}`));
196
- if (indexCandidate) {
197
- return indexCandidate;
198
- }
199
- }
200
- }
201
- }
202
- catch {
203
- return null;
204
- }
205
- }
206
- return null;
207
- }
208
- function resolveLocalSpecifier(task, sourceFilePath, specifier) {
209
- const normalized = stripSpecifierDecorators(specifier);
210
- if (!normalized) {
211
- return null;
212
- }
213
- if (normalized.startsWith('/')) {
214
- return resolveSdkModulePath((0, node_path_1.resolve)(task.projectDir, `.${normalized}`));
215
- }
216
- if (!normalized.startsWith('.')) {
217
- return null;
218
- }
219
- return resolveSdkModulePath((0, node_path_1.resolve)((0, node_path_1.dirname)(sourceFilePath), normalized));
220
- }
221
- function extractLocalHtmlScriptFiles(task, filePath, source) {
222
- const matches = [];
223
- LOCAL_HTML_SCRIPT_PATTERN.lastIndex = 0;
224
- let match = null;
225
- while ((match = LOCAL_HTML_SCRIPT_PATTERN.exec(source)) !== null) {
226
- const specifier = match[1];
227
- if (!specifier) {
228
- continue;
229
- }
230
- const resolved = resolveLocalSpecifier(task, filePath, specifier);
231
- if (resolved) {
232
- matches.push(resolved);
233
- }
234
- }
235
- return matches;
236
- }
237
- function extractLocalImportedFiles(task, filePath, source) {
238
- const matches = [];
239
- for (const pattern of LOCAL_IMPORT_PATTERNS) {
240
- pattern.lastIndex = 0;
241
- let match = null;
242
- while ((match = pattern.exec(source)) !== null) {
243
- const specifier = match[1];
244
- if (!specifier) {
245
- continue;
246
- }
247
- const resolved = resolveLocalSpecifier(task, filePath, specifier);
248
- if (resolved) {
249
- matches.push(resolved);
250
- }
251
- }
252
- }
253
- return matches;
254
- }
255
- function addFileIfPresent(target, filePath) {
256
- if (!(0, node_fs_1.existsSync)(filePath)) {
257
- return;
258
- }
259
- try {
260
- if ((0, node_fs_1.statSync)(filePath).isFile()) {
261
- target.add(filePath);
262
- }
263
- }
264
- catch {
265
- // Ignore unreadable candidates and continue scanning the rest.
266
- }
267
- }
268
- function extractLocalScriptCommandFiles(task) {
269
- if (!task.packageJsonPath) {
270
- return [];
271
- }
272
- const rawPackageJson = safeReadFile(task.packageJsonPath);
273
- if (!rawPackageJson) {
274
- return [];
275
- }
276
- let packageJson;
277
- try {
278
- packageJson = JSON.parse(rawPackageJson);
279
- }
280
- catch {
281
- return [];
282
- }
283
- const scripts = packageJson?.scripts;
284
- if (!scripts || typeof scripts !== 'object') {
285
- return [];
286
- }
287
- const matches = new Set();
288
- Object.values(scripts).forEach((scriptValue) => {
289
- if (typeof scriptValue !== 'string') {
290
- return;
291
- }
292
- LOCAL_SCRIPT_COMMAND_PATTERN.lastIndex = 0;
293
- let match = null;
294
- while ((match = LOCAL_SCRIPT_COMMAND_PATTERN.exec(scriptValue)) !== null) {
295
- const specifier = stripSpecifierDecorators(match[2] ?? '');
296
- if (!specifier || specifier.startsWith('-')) {
297
- continue;
298
- }
299
- const resolvedPath = specifier.startsWith('/')
300
- ? resolveSdkModulePath((0, node_path_1.resolve)(specifier))
301
- : resolveSdkModulePath((0, node_path_1.resolve)(task.projectDir, specifier));
302
- if (resolvedPath) {
303
- matches.add(resolvedPath);
304
- }
305
- }
306
- });
307
- return [...matches];
308
- }
309
- function collectSourceSeedFiles(task) {
310
- const seeds = new Set();
311
- const entryRelativePath = (0, node_path_1.relative)(task.projectDir, task.filePath);
312
- const entrySegments = entryRelativePath.split(/[/\\]+/);
313
- const entryCrossesIgnoredSourceDirectory = entrySegments.some((segment) => isIgnoredSdkScanDirectory(segment));
314
- if (entryRelativePath && !entryRelativePath.startsWith('..') && !entryCrossesIgnoredSourceDirectory) {
315
- addFileIfPresent(seeds, task.filePath);
316
- }
317
- SDK_ENTRY_HTML_FILENAMES.forEach((filename) => {
318
- addFileIfPresent(seeds, (0, node_path_1.join)(task.projectDir, filename));
319
- addFileIfPresent(seeds, (0, node_path_1.join)(task.projectDir, 'public', filename));
320
- });
321
- const sourceExtensions = [...SDK_SCAN_EXTENSIONS].filter((extension) => extension !== '.html');
322
- const addEntryBasenames = (directory) => {
323
- SDK_ENTRY_SOURCE_BASENAMES.forEach((basename) => {
324
- sourceExtensions.forEach((extension) => {
325
- addFileIfPresent(seeds, (0, node_path_1.join)(directory, `${basename}${extension}`));
326
- });
327
- });
328
- };
329
- addEntryBasenames(task.projectDir);
330
- SDK_ENTRY_ROOT_DIRS.forEach((directory) => {
331
- addEntryBasenames((0, node_path_1.join)(task.projectDir, directory));
332
- });
333
- SDK_SPECIAL_ENTRY_PATHS.forEach((relativePath) => {
334
- sourceExtensions.forEach((extension) => {
335
- addFileIfPresent(seeds, (0, node_path_1.join)(task.projectDir, `${relativePath}${extension}`));
336
- });
337
- });
338
- extractLocalScriptCommandFiles(task).forEach((filePath) => {
339
- const relativePath = (0, node_path_1.relative)(task.projectDir, filePath);
340
- const segments = relativePath.split(/[/\\]+/);
341
- const crossesIgnoredSourceDirectory = segments.some((segment) => isIgnoredSdkScanDirectory(segment));
342
- if (!relativePath || relativePath.startsWith('..') || crossesIgnoredSourceDirectory) {
343
- return;
344
- }
345
- seeds.add(filePath);
346
- });
347
- return [...seeds];
348
- }
349
- function collectBundleSeedFiles(task) {
350
- const seeds = new Set();
351
- addFileIfPresent(seeds, task.filePath);
352
- return [...seeds];
353
- }
354
- function collectSdkDetectionFiles(task, mode) {
355
- const queue = mode === 'bundle' ? collectBundleSeedFiles(task) : collectSourceSeedFiles(task);
356
- const queued = new Set(queue);
357
- const visited = new Set();
358
- if (task.packageJsonPath) {
359
- visited.add(task.packageJsonPath);
360
- }
361
- while (queue.length > 0) {
362
- const filePath = queue.shift();
363
- if (!filePath || visited.has(filePath)) {
364
- continue;
365
- }
366
- if (!(0, node_fs_1.existsSync)(filePath)) {
367
- continue;
368
- }
369
- let isFile = false;
370
- try {
371
- isFile = (0, node_fs_1.statSync)(filePath).isFile();
372
- }
373
- catch {
374
- isFile = false;
375
- }
376
- if (!isFile) {
377
- continue;
378
- }
379
- const extension = (0, node_path_1.extname)(filePath).toLowerCase();
380
- if (extension && !SDK_SCAN_EXTENSIONS.has(extension)) {
381
- continue;
382
- }
383
- visited.add(filePath);
384
- const source = safeReadFile(filePath);
385
- if (!source) {
386
- continue;
387
- }
388
- const localReferences = extension === '.html'
389
- ? extractLocalHtmlScriptFiles(task, filePath, source)
390
- : extractLocalImportedFiles(task, filePath, source);
391
- localReferences.forEach((resolvedPath) => {
392
- const relativePath = (0, node_path_1.relative)(task.projectDir, resolvedPath);
393
- const segments = relativePath.split(/[/\\]+/);
394
- const crossesIgnoredSourceDirectory = mode === 'source'
395
- && segments.some((segment) => isIgnoredSdkScanDirectory(segment));
396
- if (!relativePath || relativePath.startsWith('..') || crossesIgnoredSourceDirectory) {
397
- return;
398
- }
399
- if (!queued.has(resolvedPath) && !visited.has(resolvedPath)) {
400
- queue.push(resolvedPath);
401
- queued.add(resolvedPath);
402
- }
403
- });
404
- }
405
- return [...visited];
406
- }
407
- function detectSdkUsage(task, mode) {
408
- let hasSdkReference = false;
409
- let hasSdkInit = false;
410
- let hasSdkReady = false;
411
- const files = collectSdkDetectionFiles(task, mode);
412
- for (const filePath of files) {
413
- if (hasSdkReference && hasSdkInit && hasSdkReady) {
414
- break;
415
- }
416
- const source = safeReadFile(filePath);
417
- if (!source) {
418
- continue;
419
- }
420
- if (!hasSdkReference) {
421
- hasSdkReference = SDK_REFERENCE_PATTERNS.some((pattern) => {
422
- pattern.lastIndex = 0;
423
- return pattern.test(source);
424
- });
425
- }
426
- if (!hasSdkInit) {
427
- hasSdkInit = detectSdkInitInSource(source);
428
- }
429
- if (!hasSdkReady) {
430
- hasSdkReady = SDK_READY_PATTERNS.some((pattern) => {
431
- pattern.lastIndex = 0;
432
- return pattern.test(source);
433
- });
434
- }
435
- }
436
- return { hasSdkReference, hasSdkInit, hasSdkReady };
437
- }
438
38
  function collectOutdatedSdkVersionWarnings(task) {
439
39
  if (!task.packageJsonPath) {
440
40
  return [];
@@ -473,29 +73,12 @@ function collectOutdatedSdkVersionWarnings(task) {
473
73
  `[apps][validate] ${task.name} vendors playdrop-sdk-types.tgz at ${sdkTypesVersion}, which is older than this CLI version ${cliVersion}. Refresh vendor/playdrop-sdk-types.tgz and package.json playdrop.sdkTypesVersion before upload.`,
474
74
  ];
475
75
  }
476
- function collectAppValidationWarnings(task, mode = 'source') {
76
+ function collectAppValidationWarnings(task, _mode = 'source') {
477
77
  const isExternal = task.hostingMode === 'EXTERNAL' || !!task.externalUrl;
478
78
  if (isExternal) {
479
79
  return [];
480
80
  }
481
- const primaryDetection = detectSdkUsage(task, mode);
482
- const sourceDetection = mode === 'bundle'
483
- ? detectSdkUsage(task, 'source')
484
- : primaryDetection;
485
- const hasSdkReference = primaryDetection.hasSdkReference || sourceDetection.hasSdkReference;
486
- const hasSdkInit = primaryDetection.hasSdkInit || sourceDetection.hasSdkInit;
487
- const hasSdkReady = primaryDetection.hasSdkReady || sourceDetection.hasSdkReady;
488
- const warnings = [];
489
- if (!hasSdkReference) {
490
- warnings.push(`[apps][validate] Could not detect the Playdrop SDK loader or @playdrop/sdk import for ${task.name}. We could not find /sdk/playdrop.js, https://assets.playdrop.ai/sdk/playdrop.js, or @playdrop/sdk in the app files, so it might not work once uploaded.`);
491
- }
492
- if (!hasSdkInit) {
493
- warnings.push(`[apps][validate] Could not detect Playdrop SDK initialization for ${task.name}. We could not find playdrop.init() in the app files, so it might not work once uploaded.`);
494
- }
495
- if (!hasSdkReady) {
496
- warnings.push(`[apps][validate] Could not detect Playdrop host readiness for ${task.name}. We could not find sdk.host.ready() in the app files, so the app will fail to start correctly inside Playdrop.`);
497
- }
498
- return [...warnings, ...collectOutdatedSdkVersionWarnings(task)];
81
+ return collectOutdatedSdkVersionWarnings(task);
499
82
  }
500
83
  async function runFormatScript(task) {
501
84
  if (!task.packageJsonPath || !task.hasFormatScript) {
@@ -548,5 +131,4 @@ async function validateAppTask(task) {
548
131
  throw new Error(`[apps][validate] npm run validate failed for ${task.name}: ${message}`);
549
132
  }
550
133
  }
551
- scanForLegacySdkSymbols(task);
552
134
  }
package/dist/catalogue.js CHANGED
@@ -897,7 +897,7 @@ function validateAppMetadata(entry) {
897
897
  requireComplete: true,
898
898
  });
899
899
  if (!normalized.ok) {
900
- errors.push(normalized.error);
900
+ errors.push(formatAppPlaytestTapeError(normalized.error));
901
901
  }
902
902
  else {
903
903
  playtestTapes = normalized.value;
@@ -919,6 +919,37 @@ function validateAppMetadata(entry) {
919
919
  errors,
920
920
  };
921
921
  }
922
+ function formatAppPlaytestTapeError(error) {
923
+ const parts = error.split(':');
924
+ const code = parts[0] ?? error;
925
+ const surface = parts[1] ?? 'UNKNOWN';
926
+ if (code === 'playtest_tape_start_only_input_left_active') {
927
+ const count = parts[2] ?? 'unknown';
928
+ return `${error}: playtestTapes.${surface}.startOnlyEventCount=${count} ends while an input is held; include the matching keyUp or pointerUp inside the startup prefix.`;
929
+ }
930
+ if (code === 'playtest_tape_primary_verb_events_required') {
931
+ const verb = parts[2] ?? 'unknown';
932
+ return `${error}: playtestTapes.${surface} must demonstrate primaryVerb "${verb}" at least once after startup; secondary controls are allowed.`;
933
+ }
934
+ if (code === 'playtest_tape_primary_verb_gesture_required') {
935
+ const verb = parts[2] ?? 'unknown';
936
+ return `${error}: playtestTapes.${surface} primaryVerb "${verb}" requires one complete pointerDown, pointerMove, pointerUp gesture after startup.`;
937
+ }
938
+ if (code === 'invalid_playtest_tape_key') {
939
+ const index = parts[2] ?? 'unknown';
940
+ return `${error}: playtestTapes.${surface}.events[${index}].key must be a browser key string with 1..64 characters; use a single space " " for the space bar.`;
941
+ }
942
+ if (code === 'invalid_playtest_tape_event_type') {
943
+ const index = parts[2] ?? 'unknown';
944
+ const actual = parts[3] ?? 'missing';
945
+ return `${error}: playtestTapes.${surface}.events[${index}].type="${actual}"; expected tap, pointerDown, pointerMove, pointerUp, keyDown, or keyUp.`;
946
+ }
947
+ if (code === 'invalid_playtest_tape_success_signal_description') {
948
+ const index = parts[2] ?? 'unknown';
949
+ return `${error}: playtestTapes.${surface}.successSignals[${index}].description must contain 1..240 characters.`;
950
+ }
951
+ return error;
952
+ }
922
953
  function buildAppTasks(rootDir, catalogues, options) {
923
954
  const tasks = [];
924
955
  const warnings = [];
@@ -106,8 +106,20 @@ type ListingCaptureSurfaceReport = {
106
106
  fps: number | null;
107
107
  };
108
108
  posterPath: string;
109
+ validation: ListingCapturePixelMetrics & {
110
+ status: 'passed';
111
+ };
109
112
  warnings: string[];
110
113
  };
114
+ export type ListingCapturePixelMetrics = {
115
+ referenceFrameSimilarity: number;
116
+ firstFrameMeanLuma: number;
117
+ firstFrameVisiblePixelRatio: number;
118
+ posterFrameMeanLuma: number;
119
+ posterFrameVisiblePixelRatio: number;
120
+ motionMeanDelta: number;
121
+ motionChangedPixelRatio: number;
122
+ };
111
123
  export type ExportedListingAudio = {
112
124
  mimeType: string;
113
125
  base64: string;
@@ -147,6 +159,7 @@ export declare function resolveListingCaptureBrowserContextOptions(surface: AppS
147
159
  export declare function assertExportedListingAudio(value: unknown): ExportedListingAudio;
148
160
  export declare function assertListingCaptureWindowCanContainViewport(measurement: HostedGameMeasurement, windowBounds: BrowserWindowBounds): void;
149
161
  export declare function computeRecordedCrop(measurement: HostedGameMeasurement, recorder: ListingRecorderMetadata, rawWidth: number, rawHeight: number): CropRect;
162
+ export declare function assertListingCapturePixelMetrics(surface: AppSurface, metrics: ListingCapturePixelMetrics): void;
150
163
  export declare function runListingRecorder(recorderPath: string, pid: number, durationSeconds: number, rawOutputPath: string, metadataPath: string, audio: boolean, deadlineAt: number): Promise<ListingRecorderMetadata>;
151
164
  export declare function formatCommandError(error: Error): {
152
165
  message: string;