@playdrop/playdrop-cli 0.18.8 → 0.19.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/README.md +27 -0
- package/config/client-meta.json +2 -2
- package/dist/apps/upload.d.ts +2 -0
- package/dist/apps/upload.js +146 -60
- package/dist/catalogue.d.ts +18 -0
- package/dist/catalogue.js +171 -56
- package/dist/commands/devServer.js +3 -0
- package/dist/commands/validate.js +1 -0
- package/dist/commands/worker/game-source-git.js +7 -0
- package/dist/commands/worker/generation.d.ts +16 -1
- package/dist/commands/worker/generation.js +8 -2
- package/dist/commands/worker/ugc-processing.d.ts +13 -0
- package/dist/commands/worker/ugc-processing.js +146 -0
- package/dist/commands/worker.d.ts +27 -0
- package/dist/commands/worker.js +127 -38
- package/dist/proxyFetch.d.ts +5 -0
- package/dist/proxyFetch.js +10 -35
- package/dist/uploadDispatchers.d.ts +3 -0
- package/dist/uploadDispatchers.js +10 -0
- package/node_modules/@playdrop/api-client/dist/acquisition.d.ts +3 -1
- package/node_modules/@playdrop/api-client/dist/acquisition.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/acquisition.js +2 -2
- package/node_modules/@playdrop/api-client/dist/chat.d.ts +13 -4
- package/node_modules/@playdrop/api-client/dist/chat.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/client.d.ts +31 -10
- package/node_modules/@playdrop/api-client/dist/client.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/core/request.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/core/request.js +11 -1
- package/node_modules/@playdrop/api-client/dist/domains/chat.d.ts +30 -9
- package/node_modules/@playdrop/api-client/dist/domains/chat.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/domains/chat.js +24 -19
- package/node_modules/@playdrop/api-client/dist/domains/comments.d.ts +3 -1
- package/node_modules/@playdrop/api-client/dist/domains/comments.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/domains/comments.js +2 -2
- package/node_modules/@playdrop/api-client/dist/index.d.ts +17 -6
- package/node_modules/@playdrop/api-client/dist/index.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/index.js +12 -10
- package/node_modules/@playdrop/api-client/dist/runtime.d.ts +3 -0
- package/node_modules/@playdrop/api-client/dist/runtime.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/runtime.js +10 -1
- package/node_modules/@playdrop/config/client-meta.json +2 -2
- package/node_modules/@playdrop/game-source-git/dist/src/repository.d.ts +8 -0
- package/node_modules/@playdrop/game-source-git/dist/src/repository.d.ts.map +1 -1
- package/node_modules/@playdrop/game-source-git/dist/src/repository.js +30 -17
- package/node_modules/@playdrop/game-source-git/dist/test/core.test.js +10 -0
- package/node_modules/@playdrop/game-source-git/dist/tsconfig.tsbuildinfo +1 -1
- package/node_modules/@playdrop/game-source-git/package.json +2 -1
- package/node_modules/@playdrop/types/dist/agent-task-terminal.d.ts.map +1 -1
- package/node_modules/@playdrop/types/dist/agent-task-terminal.js +1 -1
- package/node_modules/@playdrop/types/dist/api.d.ts +30 -3
- package/node_modules/@playdrop/types/dist/api.d.ts.map +1 -1
- package/node_modules/@playdrop/types/dist/api.js +1 -0
- package/node_modules/@playdrop/types/dist/app-listing-localization.d.ts +34 -0
- package/node_modules/@playdrop/types/dist/app-listing-localization.d.ts.map +1 -0
- package/node_modules/@playdrop/types/dist/app-listing-localization.js +146 -0
- package/node_modules/@playdrop/types/dist/chat.d.ts +7 -2
- package/node_modules/@playdrop/types/dist/chat.d.ts.map +1 -1
- package/node_modules/@playdrop/types/dist/index.d.ts +3 -0
- package/node_modules/@playdrop/types/dist/index.d.ts.map +1 -1
- package/node_modules/@playdrop/types/dist/index.js +6 -0
- package/node_modules/@playdrop/types/dist/ugc-processing.d.ts +211 -0
- package/node_modules/@playdrop/types/dist/ugc-processing.d.ts.map +1 -0
- package/node_modules/@playdrop/types/dist/ugc-processing.js +144 -0
- package/node_modules/@playdrop/types/dist/ugc.d.ts +39 -0
- package/node_modules/@playdrop/types/dist/ugc.d.ts.map +1 -0
- package/node_modules/@playdrop/types/dist/ugc.js +70 -0
- package/node_modules/@playdrop/types/dist/version.d.ts +4 -0
- package/node_modules/@playdrop/types/dist/version.d.ts.map +1 -1
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -153,3 +153,30 @@ For direct first-party server uploads from this checkout, start `npm run dev:age
|
|
|
153
153
|
playdrop documentation browse
|
|
154
154
|
playdrop documentation read runtime
|
|
155
155
|
```
|
|
156
|
+
|
|
157
|
+
## Localized game listings
|
|
158
|
+
|
|
159
|
+
An app in `catalogue.json` can declare a BCP47 `sourceLocale` (including languages such as Italian or Arabic) and an inline `listingLocalizations` object keyed by a supported PlayDrop locale. The original app fields remain the source listing. Each localization is a complete presentation: `displayName`, optional `subtitle`, `description`, `controls`, `makingOf`, `releaseNotes`, and its own `listing` media paths and captions. Omitted optional fields and arrays stay empty; they do not inherit source values. Explicitly reuse a source path when the same artwork should appear in both languages. Social artwork accepts PNG or JPEG, other images use PNG, and trailers use MP4.
|
|
160
|
+
|
|
161
|
+
Run `playdrop project validate .` to obtain `listingSourceHash`. Each localization records that value as `sourceHash`. Source text or media changes make older localizations stale; runtime version changes do not. Stale localizations remain inspectable but are excluded from public presentation. Uploads replace the complete localization set, so omitting an entry removes that locale from the new version.
|
|
162
|
+
|
|
163
|
+
```json
|
|
164
|
+
{
|
|
165
|
+
"sourceLocale": "en",
|
|
166
|
+
"listingLocalizations": {
|
|
167
|
+
"fr": {
|
|
168
|
+
"sourceHash": "<64-character listingSourceHash from validation>",
|
|
169
|
+
"displayName": "Mon jeu",
|
|
170
|
+
"description": "Description du jeu.",
|
|
171
|
+
"listing": {
|
|
172
|
+
"heroLandscape": "listing/hero-landscape.png",
|
|
173
|
+
"screenshotsLandscape": [
|
|
174
|
+
{ "path": "listing/fr/screenshot.png", "alt": "Une partie en cours" }
|
|
175
|
+
]
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Cloud task checkout, upload, and publish preserve this contract and include localized listing files in the source sync and listing locks.
|
package/config/client-meta.json
CHANGED
package/dist/apps/upload.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ApiClient } from "@playdrop/api-client";
|
|
2
2
|
import type { AgentTaskPlaytestProof, FinalizeAppUploadRequest, UserResponse } from "@playdrop/types";
|
|
3
|
+
import { type AppListingLocalization } from "@playdrop/types";
|
|
3
4
|
import type { AppTask } from "../catalogue";
|
|
4
5
|
import type { TaskCaptureSession } from "../appUrls";
|
|
5
6
|
import type { AppBuildArtifacts } from "./build";
|
|
@@ -73,6 +74,7 @@ type PreparedAppUploadFiles = {
|
|
|
73
74
|
sessionFiles: PreparedSessionFile[];
|
|
74
75
|
ownedAssets: PreparedOwnedAssetUpload[];
|
|
75
76
|
gameServerBuild: GameServerBuild | null;
|
|
77
|
+
listingLocalizations: AppListingLocalization[];
|
|
76
78
|
};
|
|
77
79
|
export declare function prepareAppUploadFiles(task: AppTask, artifacts: AppBuildArtifacts | null, options?: {
|
|
78
80
|
apiBase?: string;
|
package/dist/apps/upload.js
CHANGED
|
@@ -7,6 +7,8 @@ const types_1 = require("@playdrop/types");
|
|
|
7
7
|
const node_crypto_1 = require("node:crypto");
|
|
8
8
|
const node_fs_1 = require("node:fs");
|
|
9
9
|
const node_path_1 = require("node:path");
|
|
10
|
+
const proxyFetch_1 = require("../proxyFetch");
|
|
11
|
+
const uploadDispatchers_1 = require("../uploadDispatchers");
|
|
10
12
|
const http_1 = require("../http");
|
|
11
13
|
const proceduralValidation_1 = require("../proceduralValidation");
|
|
12
14
|
const game_source_git_1 = require("../commands/worker/game-source-git");
|
|
@@ -178,17 +180,60 @@ function pushPreparedArtifact(task, currentTotal, file, maxBytes, code, label) {
|
|
|
178
180
|
const bytes = ensurePreparedArtifactSize(file, maxBytes, code, label);
|
|
179
181
|
return accumulateUploadBytes(task.name, currentTotal, bytes);
|
|
180
182
|
}
|
|
181
|
-
|
|
183
|
+
function objectUploadTransportCode(error) {
|
|
184
|
+
return error && typeof error === "object" && "code" in error &&
|
|
185
|
+
typeof error.code === "string" && /^[A-Z][A-Z0-9_]{0,63}$/.test(error.code)
|
|
186
|
+
? error.code
|
|
187
|
+
: null;
|
|
188
|
+
}
|
|
189
|
+
function objectUploadTransportDetail(error, signedUrl) {
|
|
190
|
+
const details = [];
|
|
191
|
+
const seen = new Set();
|
|
192
|
+
let current = error;
|
|
193
|
+
while (current && typeof current === "object" && !seen.has(current) && seen.size < 4) {
|
|
194
|
+
seen.add(current);
|
|
195
|
+
const value = current;
|
|
196
|
+
const code = objectUploadTransportCode(value);
|
|
197
|
+
// Error messages can echo the signed request. Keep the transport explanation,
|
|
198
|
+
// never its URL, authorization header or credentials, in task/operator logs.
|
|
199
|
+
const message = typeof value.message === "string" ? value.message
|
|
200
|
+
.split(signedUrl).join("[redacted-url]")
|
|
201
|
+
.replace(/\bhttps?:\/\/[^\s"'<>]+/gi, "[redacted-url]")
|
|
202
|
+
.replace(/\b(?:proxy-authorization|authorization)\s*:\s*[^\r\n]+/gi, "authorization: [redacted]")
|
|
203
|
+
.replace(/\b(?:Bearer|Basic)\s+[^\s,;]+/gi, "[redacted-authorization]")
|
|
204
|
+
.replace(/\b(?:X-Goog-(?:Signature|Credential|Security-Token)|token|signature|credential|authorization)\b["']?\s*[=:]\s*["']?[^\s"',;}]+/gi, "[redacted-credential]")
|
|
205
|
+
.replace(/[\x00-\x1f\x7f]+/g, " ").trim().slice(0, 200) : "";
|
|
206
|
+
if (message || code)
|
|
207
|
+
details.push([code, message].filter(Boolean).join(": "));
|
|
208
|
+
current = value.cause;
|
|
209
|
+
}
|
|
210
|
+
return details.join(" <- ").slice(0, 400);
|
|
211
|
+
}
|
|
212
|
+
async function uploadSignedObject(client, creatorUsername, appName, sessionId, file, descriptor, transport) {
|
|
182
213
|
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
214
|
+
let response;
|
|
215
|
+
try {
|
|
216
|
+
response = await fetch(descriptor.url, {
|
|
217
|
+
method: descriptor.method,
|
|
218
|
+
headers: descriptor.headers,
|
|
219
|
+
body: file.file,
|
|
220
|
+
...transport,
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
catch (error) {
|
|
224
|
+
const cause = error instanceof Error ? error.cause : null;
|
|
225
|
+
const code = objectUploadTransportCode(cause) ?? objectUploadTransportCode(error) ?? "UNKNOWN";
|
|
226
|
+
const detail = objectUploadTransportDetail(cause ?? error, descriptor.url);
|
|
227
|
+
const annotated = new Error(`direct_object_upload_network_failed:${file.relativePath}:${code}${detail ? `: ${detail}` : ""}`);
|
|
228
|
+
Object.defineProperty(annotated, "cause", { value: error, configurable: true, writable: true });
|
|
229
|
+
throw annotated;
|
|
230
|
+
}
|
|
188
231
|
if (response.ok) {
|
|
232
|
+
await response.body?.cancel();
|
|
189
233
|
return;
|
|
190
234
|
}
|
|
191
235
|
if ((response.status === 401 || response.status === 403) && attempt === 0) {
|
|
236
|
+
await response.body?.cancel();
|
|
192
237
|
const signed = await client.signAppUploadObjects(creatorUsername, appName, sessionId, {
|
|
193
238
|
objectIds: [file.id],
|
|
194
239
|
});
|
|
@@ -204,23 +249,48 @@ async function uploadSignedObject(client, creatorUsername, appName, sessionId, f
|
|
|
204
249
|
}
|
|
205
250
|
}
|
|
206
251
|
async function uploadSignedObjects(client, creatorUsername, appName, sessionId, files) {
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
252
|
+
if (files.length === 0)
|
|
253
|
+
return;
|
|
254
|
+
// Uploads run in the long-lived worker supervisor. Own this pool here so a
|
|
255
|
+
// later upload cannot inherit its connections or Node's changing H2 defaults.
|
|
256
|
+
const dispatcher = new uploadDispatchers_1.ObjectUploadAgent({
|
|
257
|
+
...(0, proxyFetch_1.resolveProxyEnvironment)(),
|
|
258
|
+
allowH2: false,
|
|
259
|
+
requestTls: { allowH2: false },
|
|
260
|
+
proxyTls: { allowH2: false },
|
|
261
|
+
});
|
|
262
|
+
const controller = new AbortController();
|
|
263
|
+
const transport = { dispatcher, signal: controller.signal };
|
|
264
|
+
try {
|
|
265
|
+
for (let offset = 0; offset < files.length; offset += 100) {
|
|
266
|
+
const batch = files.slice(offset, offset + 100);
|
|
267
|
+
const signed = await client.signAppUploadObjects(creatorUsername, appName, sessionId, {
|
|
268
|
+
objectIds: batch.map((file) => file.id),
|
|
269
|
+
});
|
|
270
|
+
const descriptors = new Map(signed.uploads.map((entry) => [entry.id, entry]));
|
|
271
|
+
let cursor = 0;
|
|
272
|
+
const workers = Array.from({ length: Math.min(8, batch.length) }, async () => {
|
|
273
|
+
try {
|
|
274
|
+
while (!controller.signal.aborted && cursor < batch.length) {
|
|
275
|
+
const file = batch[cursor++];
|
|
276
|
+
const descriptor = descriptors.get(file.id);
|
|
277
|
+
// A missing descriptor means the API verified this object is stored.
|
|
278
|
+
if (descriptor)
|
|
279
|
+
await uploadSignedObject(client, creatorUsername, appName, sessionId, file, descriptor, transport);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
catch (error) {
|
|
283
|
+
// Preserve the triggering failure, cancel peers and stop taking work.
|
|
284
|
+
controller.abort(error);
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
await Promise.all(workers);
|
|
288
|
+
controller.signal.throwIfAborted();
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
finally {
|
|
292
|
+
// Finish cancellation before the caller aborts the server upload session.
|
|
293
|
+
await dispatcher.destroy();
|
|
224
294
|
}
|
|
225
295
|
}
|
|
226
296
|
async function prepareAppUploadFiles(task, artifacts, options = {}) {
|
|
@@ -264,46 +334,59 @@ async function prepareAppUploadFiles(task, artifacts, options = {}) {
|
|
|
264
334
|
sessionFiles.push(await buildPreparedSessionFile("SERVER", "server/bundle.mjs", bundle.file));
|
|
265
335
|
sessionFiles.push(await buildPreparedSessionFile("SERVER", "server/manifest.json", manifest.file));
|
|
266
336
|
}
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
const
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
sessionFiles.push(await buildPreparedSessionFile("LISTING", "listing/hero-landscape.png", prepared.file));
|
|
281
|
-
}
|
|
282
|
-
for (let index = 0; index < (listing?.screenshotPortraitPaths ?? []).length; index += 1) {
|
|
283
|
-
const filePath = listing.screenshotPortraitPaths[index];
|
|
284
|
-
const prepared = createFileFromPath(filePath, "image/png", config_1.MAX_VERSION_SCREENSHOT_BYTES, "screenshot_portrait_too_large", "invalid_screenshot_portrait_content_type", `Portrait screenshot ${index + 1}`, isPngSignature);
|
|
337
|
+
const preparedByHash = new Map();
|
|
338
|
+
const prepareListingFile = async (filePath, destination, slot, reuse) => {
|
|
339
|
+
const video = slot.startsWith("videos");
|
|
340
|
+
const socialJpeg = slot === "socialLandscapePath" && [".jpg", ".jpeg"].includes((0, node_path_1.extname)(filePath).toLowerCase());
|
|
341
|
+
if (socialJpeg)
|
|
342
|
+
destination = destination.replace(/\.png$/, ".jpg");
|
|
343
|
+
const maxBytes = video ? config_1.MAX_VERSION_VIDEO_BYTES : slot === "iconPath" ? config_1.MAX_VERSION_ICON_BYTES : slot.startsWith("screenshots") ? config_1.MAX_VERSION_SCREENSHOT_BYTES : config_1.MAX_VERSION_HERO_BYTES;
|
|
344
|
+
const prepared = createFileFromPath(filePath, video ? "video/mp4" : socialJpeg ? "image/jpeg" : "image/png", maxBytes, "listing_media_too_large", "invalid_listing_media_content_type", destination, video ? isMp4Signature : socialJpeg ? (bytes) => bytes[0] === 0xff && bytes[1] === 0xd8 && bytes[2] === 0xff : isPngSignature);
|
|
345
|
+
const file = await buildPreparedSessionFile("LISTING", destination, prepared.file);
|
|
346
|
+
const hashKey = `${file.contentType}:${file.sha256}`;
|
|
347
|
+
const existing = preparedByHash.get(hashKey);
|
|
348
|
+
if (reuse && existing)
|
|
349
|
+
return existing;
|
|
285
350
|
totalUploadBytes = pushPreparedFile(task.name, totalUploadBytes, prepared);
|
|
286
|
-
sessionFiles.push(
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
351
|
+
sessionFiles.push(file);
|
|
352
|
+
preparedByHash.set(hashKey, destination);
|
|
353
|
+
return destination;
|
|
354
|
+
};
|
|
355
|
+
const singletonSlots = [
|
|
356
|
+
["iconPath", "icon.png"], ["heroPortraitPath", "hero-portrait.png"],
|
|
357
|
+
["heroLandscapePath", "hero-landscape.png"], ["socialLandscapePath", "social-landscape.png"],
|
|
358
|
+
];
|
|
359
|
+
const arraySlots = [
|
|
360
|
+
["screenshotsPortrait", "screenshotPortraitPaths", "screenshots/portrait", "png"],
|
|
361
|
+
["screenshotsLandscape", "screenshotLandscapePaths", "screenshots/landscape", "png"],
|
|
362
|
+
["videosPortrait", "videoPortraitPaths", "videos/portrait", "mp4"],
|
|
363
|
+
["videosLandscape", "videoLandscapePaths", "videos/landscape", "mp4"],
|
|
364
|
+
];
|
|
365
|
+
for (const [slot, filename] of singletonSlots) {
|
|
366
|
+
const file = listing?.[slot];
|
|
367
|
+
if (file)
|
|
368
|
+
await prepareListingFile(file, `listing/${filename}`, slot, false);
|
|
293
369
|
}
|
|
294
|
-
for (
|
|
295
|
-
const
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
sessionFiles.push(await buildPreparedSessionFile("LISTING", `listing/videos/portrait/${index}.mp4`, prepared.file));
|
|
370
|
+
for (const [slot, field, directory, extension] of arraySlots) {
|
|
371
|
+
const files = listing?.[field] ?? [];
|
|
372
|
+
for (const [index, file] of files.entries())
|
|
373
|
+
await prepareListingFile(file, `listing/${directory}/${index}.${extension}`, slot, false);
|
|
299
374
|
}
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
const
|
|
303
|
-
|
|
304
|
-
|
|
375
|
+
const listingLocalizations = [];
|
|
376
|
+
for (const localization of task.listingLocalizations ?? []) {
|
|
377
|
+
const localized = { ...localization };
|
|
378
|
+
for (const [slot, filename] of singletonSlots) {
|
|
379
|
+
const file = localization[slot];
|
|
380
|
+
localized[slot] = file ? await prepareListingFile(file, `listing/locales/${localization.locale}/${filename}`, slot, true) : null;
|
|
381
|
+
}
|
|
382
|
+
for (const [slot, , directory, extension] of arraySlots) {
|
|
383
|
+
localized[slot] = [];
|
|
384
|
+
for (const [index, file] of localization[slot].entries())
|
|
385
|
+
localized[slot].push(await prepareListingFile(file, `listing/locales/${localization.locale}/${directory}/${index}.${extension}`, slot, true));
|
|
386
|
+
}
|
|
387
|
+
listingLocalizations.push(localized);
|
|
305
388
|
}
|
|
306
|
-
return { sessionFiles, ownedAssets, gameServerBuild: gameServer };
|
|
389
|
+
return { sessionFiles, ownedAssets, gameServerBuild: gameServer, listingLocalizations };
|
|
307
390
|
}
|
|
308
391
|
async function uploadApp(client, task, artifacts, options) {
|
|
309
392
|
// Version is required - no legacy fallback
|
|
@@ -336,7 +419,7 @@ async function uploadAppVersion(client, task, artifacts, options) {
|
|
|
336
419
|
"The prepared upload does not match the project; finish edits and checks before retrying.");
|
|
337
420
|
}
|
|
338
421
|
};
|
|
339
|
-
const { sessionFiles: preparedSessionFiles, ownedAssets: preparedOwnedAssets, gameServerBuild } = await prepareAppUploadFiles(task, artifacts, { apiBase: options?.apiBase, client });
|
|
422
|
+
const { sessionFiles: preparedSessionFiles, ownedAssets: preparedOwnedAssets, gameServerBuild, listingLocalizations } = await prepareAppUploadFiles(task, artifacts, { apiBase: options?.apiBase, client });
|
|
340
423
|
await assertPublicationUnchanged(inputFingerprint, false);
|
|
341
424
|
const publicationFingerprint = options?.prepareGitTransition
|
|
342
425
|
? await (0, game_source_git_1.fingerprintGameSourcePublication)(task.projectDir)
|
|
@@ -354,6 +437,9 @@ async function uploadAppVersion(client, task, artifacts, options) {
|
|
|
354
437
|
})));
|
|
355
438
|
const preparedObjects = [...preparedSessionFiles, ...preparedOwnedAssetFiles];
|
|
356
439
|
const initializeRequest = {
|
|
440
|
+
sourceLocale: task.sourceLocale,
|
|
441
|
+
listingSourceHash: task.listingSourceHash,
|
|
442
|
+
listingLocalizations,
|
|
357
443
|
version: task.version,
|
|
358
444
|
agentTaskId: options?.agentTaskId,
|
|
359
445
|
playtestProof: options?.playtestProof,
|
package/dist/catalogue.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type AppListingContent, type AppListingLocalization } from "@playdrop/types";
|
|
1
2
|
import { type AppAchievementCatalogueDefinition, type AppAuthMode, type AppControllerMode, type AppCreationMetadata, type AppHostingMode, type AppLeaderboardCatalogueDefinition, type AppListingMediaMetadata, type AppMetadataAssetSpecSupport, type AppPlaytestTapes, type AppPlayersConfig, type AppSurface, type AppType, type AppTweaksDeclaration, type AppVersionDesign, type AppVersionVisibility, type AssetSpecContract, type AssetSpecStatus, type AssetSpecValidationKind, type CatalogueTagGroupDefinition, type ContentLicense, type PlayerMetaDefinitionStatus } from "@playdrop/types";
|
|
2
3
|
import { type AssetSpecSupportDeclaration } from "./assetSpecs";
|
|
3
4
|
export type CatalogueJson = {
|
|
@@ -21,6 +22,7 @@ export type AppListingConfig = {
|
|
|
21
22
|
icon?: string;
|
|
22
23
|
heroPortrait?: string;
|
|
23
24
|
heroLandscape?: string;
|
|
25
|
+
socialLandscape?: string;
|
|
24
26
|
screenshotsPortrait?: AppListingMediaConfig[];
|
|
25
27
|
screenshotsLandscape?: AppListingMediaConfig[];
|
|
26
28
|
videosPortrait?: AppListingMediaConfig[];
|
|
@@ -177,6 +179,7 @@ export type ResolvedListingAssets = {
|
|
|
177
179
|
iconPath?: string;
|
|
178
180
|
heroPortraitPath?: string;
|
|
179
181
|
heroLandscapePath?: string;
|
|
182
|
+
socialLandscapePath?: string;
|
|
180
183
|
screenshotPortraitPaths: string[];
|
|
181
184
|
screenshotLandscapePaths: string[];
|
|
182
185
|
videoPortraitPaths: string[];
|
|
@@ -194,6 +197,9 @@ export type AppServerTask = {
|
|
|
194
197
|
rooms: string[];
|
|
195
198
|
};
|
|
196
199
|
export type AppTask = {
|
|
200
|
+
sourceLocale?: string | null;
|
|
201
|
+
listingSourceHash?: string;
|
|
202
|
+
listingLocalizations?: AppListingLocalization[];
|
|
197
203
|
kind: "app";
|
|
198
204
|
name: string;
|
|
199
205
|
cataloguePath: string;
|
|
@@ -437,4 +443,16 @@ export declare function findAppTaskByFile(htmlPath: string, options?: Pick<Catal
|
|
|
437
443
|
warnings: string[];
|
|
438
444
|
errors: string[];
|
|
439
445
|
};
|
|
446
|
+
export declare function catalogueListingContent(task: {
|
|
447
|
+
displayName?: string;
|
|
448
|
+
subtitle?: string;
|
|
449
|
+
description?: string;
|
|
450
|
+
controls?: string;
|
|
451
|
+
makingOf?: string;
|
|
452
|
+
releaseNotes?: string;
|
|
453
|
+
listing?: ResolvedListingAssets;
|
|
454
|
+
}): AppListingContent;
|
|
455
|
+
export declare function calculateCatalogueListingSourceHash(task: Parameters<typeof catalogueListingContent>[0] & {
|
|
456
|
+
sourceLocale?: string | null;
|
|
457
|
+
}): string;
|
|
440
458
|
export {};
|