@playdrop/playdrop-cli 0.12.12 → 0.12.13

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "version": "0.12.8",
3
- "build": 2,
3
+ "build": 5,
4
4
  "runtimeSdkVersion": "0.12.5",
5
5
  "runtimeSdkBuild": 2,
6
6
  "clients": {
@@ -8,6 +8,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
10
  const loadCheck_1 = require("./loadCheck");
11
+ const listingPreflight_1 = require("../listingPreflight");
11
12
  const EXTENSION_TO_MIME = {
12
13
  '.png': 'image/png',
13
14
  '.jpg': 'image/jpeg',
@@ -74,89 +75,6 @@ async function bufferFromFile(file) {
74
75
  function computeSha256Hex(buffer) {
75
76
  return (0, node_crypto_1.createHash)('sha256').update(buffer).digest('hex');
76
77
  }
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 rawArtifactHashes = binding.artifactHashes;
104
- if (!rawArtifactHashes || typeof rawArtifactHashes !== 'object' || Array.isArray(rawArtifactHashes)) {
105
- throw new Error(`agent_task_media_capture_report_invalid: ${reportPath} binding.artifactHashes must be an object.`);
106
- }
107
- const captureArtifactHashEntries = Object.values(rawArtifactHashes)
108
- .map((value) => typeof value === 'string' ? value.trim().toLowerCase() : '')
109
- .filter((value) => isSha256Hex(value));
110
- const captureArtifactHashes = new Set(captureArtifactHashEntries);
111
- if (captureArtifactHashEntries.length === 0) {
112
- throw new Error(`agent_task_media_capture_report_invalid: ${reportPath} binding.artifactHashes is empty.`);
113
- }
114
- const captureArtifactHashCounts = new Map();
115
- for (const hash of captureArtifactHashEntries) {
116
- captureArtifactHashCounts.set(hash, (captureArtifactHashCounts.get(hash) ?? 0) + 1);
117
- }
118
- const mediaFiles = input.preparedSessionFiles.filter((file) => isCaptureMediaFileKey(file.fileKey));
119
- if (mediaFiles.length === 0) {
120
- throw new Error(`agent_task_media_capture_artifacts_missing: ${input.task.name} must upload recorder stills and video in listing.screenshots* and listing.videos*.`);
121
- }
122
- const uploadedMediaHashCounts = new Map();
123
- for (const file of mediaFiles) {
124
- uploadedMediaHashCounts.set(file.sha256, (uploadedMediaHashCounts.get(file.sha256) ?? 0) + 1);
125
- }
126
- const missingUploadedCaptureArtifacts = [...captureArtifactHashCounts.entries()]
127
- .filter(([hash, expectedCount]) => (uploadedMediaHashCounts.get(hash) ?? 0) < expectedCount);
128
- if (missingUploadedCaptureArtifacts.length > 0) {
129
- 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.`);
130
- }
131
- let hasStill = false;
132
- let hasVideo = false;
133
- const artifactHashes = {};
134
- for (const file of mediaFiles) {
135
- if (!captureArtifactHashes.has(file.sha256)) {
136
- throw new Error(`agent_task_media_capture_artifact_mismatch: ${file.fileKey} was not produced by listing.captureReport for ${input.task.name}.`);
137
- }
138
- hasStill = hasStill || file.fileKey.startsWith('screenshots');
139
- hasVideo = hasVideo || file.fileKey.startsWith('videos');
140
- artifactHashes[file.fileKey] = file.sha256;
141
- }
142
- if (!hasStill || !hasVideo) {
143
- throw new Error(`agent_task_media_capture_artifacts_missing: ${input.task.name} must upload at least one recorder still and one recorder video.`);
144
- }
145
- const capturedSurfaces = Array.isArray(binding.capturedSurfaces)
146
- ? Array.from(new Set(binding.capturedSurfaces
147
- .map((surface) => typeof surface === 'string' ? surface.trim().toUpperCase() : '')
148
- .filter((surface) => input.task.surfaceTargets.includes(surface))))
149
- : [];
150
- const capturedSurfaceSet = new Set(capturedSurfaces);
151
- const missingSurfaces = input.task.surfaceTargets.filter((surface) => !capturedSurfaceSet.has(surface));
152
- if (missingSurfaces.length > 0) {
153
- throw new Error(`agent_task_media_capture_surface_missing: listing.captureReport did not capture ${missingSurfaces.join(', ')} for ${input.task.name}.`);
154
- }
155
- return {
156
- artifactHashes,
157
- capturedSurfaces,
158
- };
159
- }
160
78
  function resolveMimeType(filename, fallback = 'application/octet-stream') {
161
79
  const type = EXTENSION_TO_MIME[(0, node_path_1.extname)(filename).toLowerCase()];
162
80
  return type ?? fallback;
@@ -425,7 +343,7 @@ async function uploadAppVersion(client, task, artifacts, options) {
425
343
  if (options?.mediaCaptureRequired === true
426
344
  && task.hostingMode !== 'EXTERNAL'
427
345
  && !task.externalUrl) {
428
- const mediaCaptureProof = readMediaCaptureProof({
346
+ const mediaCaptureProof = (0, listingPreflight_1.readMediaCaptureProof)({
429
347
  task,
430
348
  preparedSessionFiles,
431
349
  });
@@ -7,4 +7,5 @@ type LoginOptions = {
7
7
  handoffToken?: string;
8
8
  json?: boolean;
9
9
  };
10
+ export declare function buildCliLoginTokenName(machineHostname?: string): string;
10
11
  export declare function login(env: string, options?: LoginOptions): Promise<void>;
@@ -1,8 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.setOpenBrowserForTests = exports.resetOpenBrowserForTests = void 0;
4
+ exports.buildCliLoginTokenName = buildCliLoginTokenName;
4
5
  exports.login = login;
5
6
  const types_1 = require("@playdrop/types");
7
+ const node_os_1 = require("node:os");
6
8
  const config_1 = require("../config");
7
9
  const apiClient_1 = require("../apiClient");
8
10
  const http_1 = require("../http");
@@ -22,6 +24,13 @@ function extractAccessToken(data) {
22
24
  function isLoginResponse(data) {
23
25
  return typeof data === 'object' && data !== null && 'user' in data;
24
26
  }
27
+ function buildCliLoginTokenName(machineHostname = (0, node_os_1.hostname)()) {
28
+ const normalizedHostname = machineHostname.trim();
29
+ if (!normalizedHostname) {
30
+ throw new Error('cli_login_hostname_missing');
31
+ }
32
+ return `CLI on ${normalizedHostname}`;
33
+ }
25
34
  function storeLogin(env, data, options = {}) {
26
35
  const token = extractAccessToken(data);
27
36
  if (!token) {
@@ -53,11 +62,50 @@ function storeLogin(env, data, options = {}) {
53
62
  console.log(`Logged in as ${username} on ${env}.`);
54
63
  console.log('Next: run "playdrop auth whoami" to confirm your session.');
55
64
  }
65
+ async function storeDeviceLogin(env, baseUrl, initialLogin, options = {}) {
66
+ const sessionToken = extractAccessToken(initialLogin);
67
+ const username = typeof initialLogin.user?.username === 'string'
68
+ ? initialLogin.user.username.trim()
69
+ : '';
70
+ if (!sessionToken) {
71
+ throw new Error('Login response is missing the session token required to create this computer token.');
72
+ }
73
+ if (!username) {
74
+ throw new Error('Login response is missing the username required to save this computer token.');
75
+ }
76
+ try {
77
+ const sessionClient = (0, apiClient_1.createCliApiClient)({ baseUrl, token: sessionToken });
78
+ const deviceToken = await sessionClient.createCliApiToken({
79
+ name: buildCliLoginTokenName(),
80
+ });
81
+ const deviceLogin = await (0, apiClient_1.createCliApiClient)({ baseUrl }).loginWithApiKey({
82
+ apiKey: deviceToken.apiKey,
83
+ });
84
+ storeLogin(env, deviceLogin, options);
85
+ }
86
+ catch (unknownError) {
87
+ if (unknownError instanceof types_1.UnsupportedClientError) {
88
+ (0, http_1.handleUnsupportedError)(unknownError, 'Login');
89
+ return;
90
+ }
91
+ if (unknownError instanceof types_1.ApiError) {
92
+ const tokenLimitReached = unknownError.code === 'cli_api_token_limit_reached';
93
+ (0, messages_1.printErrorWithHelp)(tokenLimitReached
94
+ ? 'Login succeeded, but this account already has the maximum of 25 CLI tokens.'
95
+ : `Login succeeded, but this computer's CLI token could not be created (status ${unknownError.status}).`, tokenLimitReached
96
+ ? ['Delete an unused CLI token in account settings, then rerun "playdrop auth login".']
97
+ : ['Rerun "playdrop auth login" to try again.'], { command: 'login' });
98
+ process.exitCode = 1;
99
+ return;
100
+ }
101
+ throw unknownError;
102
+ }
103
+ }
56
104
  async function loginWithUsernamePassword(env, baseUrl, username, password, options = {}) {
57
105
  const client = (0, apiClient_1.createCliApiClient)({ baseUrl });
58
106
  try {
59
107
  const data = await client.login({ username, password });
60
- storeLogin(env, data, options);
108
+ await storeDeviceLogin(env, baseUrl, data, options);
61
109
  }
62
110
  catch (unknownError) {
63
111
  if (unknownError instanceof types_1.UnsupportedClientError) {
@@ -101,7 +149,7 @@ async function loginWithHandoff(env, baseUrl, handoffToken, options = {}) {
101
149
  const client = (0, apiClient_1.createCliApiClient)({ baseUrl });
102
150
  try {
103
151
  const data = await client.exchangeAppleOAuthHandoff({ token: handoffToken });
104
- storeLogin(env, data, options);
152
+ await storeDeviceLogin(env, baseUrl, data, options);
105
153
  }
106
154
  catch (unknownError) {
107
155
  if (unknownError instanceof types_1.UnsupportedClientError) {
@@ -163,7 +211,7 @@ async function loginInBrowser(env, baseUrl, options = {}) {
163
211
  await sleep(intervalMs);
164
212
  continue;
165
213
  }
166
- storeLogin(env, result, options);
214
+ await storeDeviceLogin(env, baseUrl, result, options);
167
215
  return;
168
216
  }
169
217
  catch (unknownError) {
@@ -51,6 +51,7 @@ const uploadLog_1 = require("../uploadLog");
51
51
  const upload_content_1 = require("./upload-content");
52
52
  const appUrls_1 = require("../appUrls");
53
53
  const devRuntimeAssets_1 = require("./devRuntimeAssets");
54
+ const listingPreflight_1 = require("../listingPreflight");
54
55
  const upload_graph_1 = require("./upload-graph");
55
56
  function normalizeSavedItemKinds(kinds) {
56
57
  return [...new Set(Array.isArray(kinds) ? kinds : [])].sort();
@@ -847,6 +848,7 @@ function appendTaskRelations(graphState, fromNodeId, relations, contextLabel) {
847
848
  });
848
849
  }
849
850
  async function uploadAppTask(state, task, taskCreator, options) {
851
+ (0, listingPreflight_1.assertProjectListingPreflight)(task);
850
852
  const { upload, warnings } = await (0, apps_1.runAppPipeline)(state.client, task, {
851
853
  skipReview: options?.skipReview,
852
854
  clearTags: options?.clearTags,
@@ -1230,10 +1232,6 @@ const WORKER_SUPERVISOR_BUNDLE_EXCLUDES = [
1230
1232
  'assets/marketing/',
1231
1233
  'listing/',
1232
1234
  ];
1233
- const MIN_HERO_PORTRAIT_WIDTH = 512;
1234
- const MIN_HERO_PORTRAIT_HEIGHT = 768;
1235
- const MIN_HERO_LANDSCAPE_WIDTH = 768;
1236
- const MIN_HERO_LANDSCAPE_HEIGHT = 432;
1237
1235
  const OWNED_RUNTIME_IMAGE_CHECK_SIZE = 96;
1238
1236
  const OWNED_RUNTIME_IMAGE_GRID_CHECK_SIZE = 512;
1239
1237
  function appTaskSatisfiesPlaydropAssetRequirement(task, requirement) {
@@ -2024,118 +2022,6 @@ async function assertNewGameDeclaredAssetsAreUsedAtRuntime(input) {
2024
2022
  throw new Error('agent_task_game_asset_fallback_disallowed: new game tasks must fail clearly when declared gameplay assets cannot load instead of rendering placeholder or fallback visuals.');
2025
2023
  }
2026
2024
  }
2027
- function readPngDimensions(filePath) {
2028
- const buffer = (0, node_fs_1.readFileSync)(filePath);
2029
- if (buffer.length < 24) {
2030
- throw new Error(`agent_task_listing_hero_art_invalid: ${filePath} is too small to be a valid PNG.`);
2031
- }
2032
- const isPng = (buffer[0] === 0x89
2033
- && buffer[1] === 0x50
2034
- && buffer[2] === 0x4e
2035
- && buffer[3] === 0x47
2036
- && buffer[4] === 0x0d
2037
- && buffer[5] === 0x0a
2038
- && buffer[6] === 0x1a
2039
- && buffer[7] === 0x0a);
2040
- if (!isPng || buffer.toString('ascii', 12, 16) !== 'IHDR') {
2041
- throw new Error(`agent_task_listing_hero_art_invalid: ${filePath} must be a PNG with an IHDR header.`);
2042
- }
2043
- const width = buffer.readUInt32BE(16);
2044
- const height = buffer.readUInt32BE(20);
2045
- if (!Number.isInteger(width) || !Number.isInteger(height) || width <= 0 || height <= 0) {
2046
- throw new Error(`agent_task_listing_hero_art_invalid: ${filePath} has invalid PNG dimensions.`);
2047
- }
2048
- return { width, height };
2049
- }
2050
- function readAgentTaskCatalogueApp(task) {
2051
- let data;
2052
- try {
2053
- data = JSON.parse((0, node_fs_1.readFileSync)(task.catalogueAbsolutePath, 'utf8'));
2054
- }
2055
- catch (error) {
2056
- const reason = error instanceof Error ? error.message : String(error);
2057
- throw new Error(`agent_task_catalogue_invalid_json: ${task.cataloguePath} must be valid JSON. ${reason}`);
2058
- }
2059
- const apps = Array.isArray(data?.apps) ? data.apps : [];
2060
- const app = apps.find((entry) => entry && typeof entry === 'object' && entry.name === task.name);
2061
- if (!app || typeof app !== 'object') {
2062
- throw new Error(`agent_task_catalogue_app_missing: ${task.cataloguePath} must contain apps[] entry "${task.name}".`);
2063
- }
2064
- return app;
2065
- }
2066
- function assertNewGameHeroListingKeys(task) {
2067
- const app = readAgentTaskCatalogueApp(task);
2068
- const listing = app.listing;
2069
- if (!listing || typeof listing !== 'object' || Array.isArray(listing)) {
2070
- return;
2071
- }
2072
- const hasLegacyPortrait = typeof listing.heroPortraitPath === 'string' && listing.heroPortraitPath.trim().length > 0;
2073
- const hasLegacyLandscape = typeof listing.heroLandscapePath === 'string' && listing.heroLandscapePath.trim().length > 0;
2074
- if (hasLegacyPortrait || hasLegacyLandscape) {
2075
- throw new Error('agent_task_listing_hero_art_legacy_keys: new game tasks must reference hero art through listing.heroPortrait and listing.heroLandscape, not listing.heroPortraitPath or listing.heroLandscapePath.');
2076
- }
2077
- }
2078
- function assertNewGameHeroListingPath(task, filePath, field) {
2079
- const relativePath = (0, node_path_1.relative)((0, node_path_1.resolve)(task.projectDir), (0, node_path_1.resolve)(filePath)).replace(/\\/g, '/');
2080
- if (relativePath === '' || relativePath === '..' || relativePath.startsWith('../') || relativePath.startsWith('/')) {
2081
- throw new Error(`agent_task_listing_hero_art_path_invalid: ${field} must point to a PNG inside the project under assets/marketing/playdrop/.`);
2082
- }
2083
- if (!relativePath.startsWith('assets/marketing/playdrop/') || (0, node_path_1.extname)(relativePath).toLowerCase() !== '.png') {
2084
- throw new Error(`agent_task_listing_hero_art_path_invalid: ${field} must point to a PNG under assets/marketing/playdrop/.`);
2085
- }
2086
- }
2087
- function assertNewGameHeroListingArt(task) {
2088
- const heroPortraitPath = task.listing?.heroPortraitPath;
2089
- const heroLandscapePath = task.listing?.heroLandscapePath;
2090
- if (!heroPortraitPath || !heroLandscapePath) {
2091
- throw new Error('agent_task_listing_hero_art_missing: new game tasks must include listing.heroPortrait and listing.heroLandscape PNG assets.');
2092
- }
2093
- assertNewGameHeroListingKeys(task);
2094
- assertNewGameHeroListingPath(task, heroPortraitPath, 'listing.heroPortrait');
2095
- assertNewGameHeroListingPath(task, heroLandscapePath, 'listing.heroLandscape');
2096
- const portrait = readPngDimensions(heroPortraitPath);
2097
- if (portrait.width < MIN_HERO_PORTRAIT_WIDTH || portrait.height < MIN_HERO_PORTRAIT_HEIGHT || portrait.height <= portrait.width) {
2098
- throw new Error(`agent_task_listing_hero_art_invalid: listing.heroPortrait must be a portrait PNG at least ${MIN_HERO_PORTRAIT_WIDTH}x${MIN_HERO_PORTRAIT_HEIGHT}.`);
2099
- }
2100
- const landscape = readPngDimensions(heroLandscapePath);
2101
- if (landscape.width < MIN_HERO_LANDSCAPE_WIDTH || landscape.height < MIN_HERO_LANDSCAPE_HEIGHT || landscape.width <= landscape.height) {
2102
- throw new Error(`agent_task_listing_hero_art_invalid: listing.heroLandscape must be a landscape PNG at least ${MIN_HERO_LANDSCAPE_WIDTH}x${MIN_HERO_LANDSCAPE_HEIGHT}.`);
2103
- }
2104
- }
2105
- function assertListingScreenshotPath(task, filePath, field) {
2106
- const relativePath = (0, node_path_1.relative)((0, node_path_1.resolve)(task.projectDir), (0, node_path_1.resolve)(filePath)).replace(/\\/g, '/');
2107
- const requiredPrefix = field === 'listing.screenshotsPortrait'
2108
- ? 'assets/marketing/playdrop/screenshots/portrait/'
2109
- : 'assets/marketing/playdrop/screenshots/landscape/';
2110
- if (relativePath === '' || relativePath === '..' || relativePath.startsWith('../') || relativePath.startsWith('/')) {
2111
- throw new Error(`agent_task_listing_screenshots_path_invalid: ${field} entries must point to PNG files inside the project under ${requiredPrefix}.`);
2112
- }
2113
- if (!relativePath.startsWith(requiredPrefix) || (0, node_path_1.extname)(relativePath).toLowerCase() !== '.png') {
2114
- throw new Error(`agent_task_listing_screenshots_path_invalid: ${field} entries must point to PNG files under ${requiredPrefix}.`);
2115
- }
2116
- }
2117
- function assertNewGameListingScreenshots(task) {
2118
- const listing = task.listing;
2119
- const portraitPaths = listing?.screenshotPortraitPaths ?? [];
2120
- const landscapePaths = listing?.screenshotLandscapePaths ?? [];
2121
- if (portraitPaths.length === 0 && landscapePaths.length === 0) {
2122
- throw new Error('agent_task_listing_screenshots_missing: new game tasks must include listing.screenshotsPortrait and/or listing.screenshotsLandscape PNG captures.');
2123
- }
2124
- if (task.surfaceTargets.includes('MOBILE_PORTRAIT') && portraitPaths.length === 0) {
2125
- throw new Error('agent_task_listing_screenshots_missing: mobile portrait games must include at least one listing.screenshotsPortrait PNG capture.');
2126
- }
2127
- if ((task.surfaceTargets.includes('DESKTOP') || task.surfaceTargets.includes('MOBILE_LANDSCAPE')) && landscapePaths.length === 0) {
2128
- throw new Error('agent_task_listing_screenshots_missing: desktop or landscape games must include at least one listing.screenshotsLandscape PNG capture.');
2129
- }
2130
- for (const filePath of portraitPaths) {
2131
- assertListingScreenshotPath(task, filePath, 'listing.screenshotsPortrait');
2132
- readPngDimensions(filePath);
2133
- }
2134
- for (const filePath of landscapePaths) {
2135
- assertListingScreenshotPath(task, filePath, 'listing.screenshotsLandscape');
2136
- readPngDimensions(filePath);
2137
- }
2138
- }
2139
2025
  function readPlaytestEvidenceManifest(task) {
2140
2026
  const manifestPath = (0, node_path_1.join)(task.projectDir, 'playtest-evidence.json');
2141
2027
  if (!(0, node_fs_1.existsSync)(manifestPath)) {
@@ -2297,7 +2183,7 @@ async function publishWorkerAppProject(input) {
2297
2183
  assertTaskPlaytestEvidenceManifest(task);
2298
2184
  }
2299
2185
  if (input.kind === 'NEW_GAME' || input.kind === 'REMIX_GAME') {
2300
- assertNewGameHeroListingArt(task);
2186
+ (0, listingPreflight_1.assertAgentGameHeroListingPreflight)(task);
2301
2187
  }
2302
2188
  if (!appTaskSatisfiesPlaydropAssetRequirement(task, input.playdropAssetRequirement)) {
2303
2189
  throw new Error('agent_task_playdrop_asset_dependency_missing: the creator asked for PlayDrop assets, but catalogue.json does not declare a matching uses.assets, uses.packs, or ownedAssets dependency.');
@@ -2346,7 +2232,7 @@ async function publishWorkerAppProject(input) {
2346
2232
  taskToken,
2347
2233
  };
2348
2234
  if (input.kind === 'NEW_GAME' || input.kind === 'REMIX_GAME') {
2349
- assertNewGameListingScreenshots(task);
2235
+ (0, listingPreflight_1.assertAgentGameScreenshotListingPreflight)(task);
2350
2236
  assertTaskPlaytestEvidenceManifest(task);
2351
2237
  }
2352
2238
  const { upload: uploadResult, warnings } = await (0, apps_1.runAppPipeline)(input.client, task, {
@@ -9,6 +9,7 @@ const externalAssetPackValidation_1 = require("../externalAssetPackValidation");
9
9
  const taskSelection_1 = require("../taskSelection");
10
10
  const taskUtils_1 = require("../taskUtils");
11
11
  const uploadLog_1 = require("../uploadLog");
12
+ const listingPreflight_1 = require("../listingPreflight");
12
13
  const upload_content_1 = require("./upload-content");
13
14
  function buildLocalAssetSpecLookups(tasks) {
14
15
  const exactByRef = new Map();
@@ -191,6 +192,7 @@ async function validate(pathOrName, options = {}) {
191
192
  const entityId = task.name;
192
193
  try {
193
194
  if (task.kind === 'app') {
195
+ (0, listingPreflight_1.assertProjectListingPreflight)(task);
194
196
  await (0, apps_1.validateAppTask)(task);
195
197
  (0, apps_1.collectAppValidationWarnings)(task, 'source').forEach((warning) => warnings.add(warning));
196
198
  }
@@ -0,0 +1,16 @@
1
+ import type { RecordAppUploadMediaCaptureRequest } from '@playdrop/types';
2
+ import type { AppTask } from './catalogue';
3
+ export type ListingMediaFile = {
4
+ fileKey: string;
5
+ sha256: string;
6
+ };
7
+ export declare function readMediaCaptureProof(input: {
8
+ task: AppTask;
9
+ preparedSessionFiles: ListingMediaFile[];
10
+ }): RecordAppUploadMediaCaptureRequest;
11
+ export declare function assertAgentGameHeroListingPreflight(task: AppTask): void;
12
+ export declare function assertAgentGameScreenshotListingPreflight(task: AppTask): void;
13
+ export declare function assertAgentGameListingPreflight(task: AppTask, options: {
14
+ requireCaptureReport: boolean;
15
+ }): void;
16
+ export declare function assertProjectListingPreflight(task: AppTask): void;