@playdrop/playdrop-cli 0.5.2 → 0.5.3
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 +4 -4
- package/dist/apps/build.js +49 -6
- package/dist/apps/index.d.ts +2 -0
- package/dist/apps/index.js +2 -0
- package/dist/apps/upload.d.ts +2 -0
- package/dist/apps/upload.js +126 -28
- package/dist/assetSpecs.d.ts +16 -0
- package/dist/assetSpecs.js +263 -0
- package/dist/assets/model-artifacts.js +3 -0
- package/dist/catalogue.d.ts +57 -3
- package/dist/catalogue.js +342 -16
- package/dist/commands/ads.d.ts +8 -0
- package/dist/commands/ads.js +124 -0
- package/dist/commands/boosts.d.ts +25 -0
- package/dist/commands/boosts.js +209 -0
- package/dist/commands/browse.d.ts +6 -1
- package/dist/commands/browse.js +365 -124
- package/dist/commands/captureListing.d.ts +53 -0
- package/dist/commands/captureListing.js +804 -0
- package/dist/commands/create.d.ts +1 -0
- package/dist/commands/create.js +183 -3
- package/dist/commands/credits.d.ts +6 -0
- package/dist/commands/credits.js +47 -1
- package/dist/commands/detail.js +38 -4
- package/dist/commands/devServer.js +10 -5
- package/dist/commands/search.d.ts +5 -0
- package/dist/commands/search.js +139 -17
- package/dist/commands/tags.d.ts +7 -0
- package/dist/commands/tags.js +63 -0
- package/dist/commands/upload-content.d.ts +13 -3
- package/dist/commands/upload-content.js +86 -20
- package/dist/commands/upload.d.ts +2 -0
- package/dist/commands/upload.js +187 -11
- package/dist/commands/validate.js +163 -2
- package/dist/commands/versionsBrowse.js +128 -91
- package/dist/index.js +145 -3
- package/dist/refs.d.ts +2 -2
- package/dist/refs.js +13 -1
- package/dist/taskSelection.js +6 -3
- package/dist/taskUtils.d.ts +2 -2
- package/dist/taskUtils.js +1 -0
- package/dist/uploadLog.d.ts +1 -1
- package/dist/uploadLog.js +2 -2
- package/node_modules/@playdrop/ai-client/package.json +1 -1
- package/node_modules/@playdrop/api-client/dist/client.d.ts +131 -10
- package/node_modules/@playdrop/api-client/dist/client.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/client.js +6 -0
- package/node_modules/@playdrop/api-client/dist/domains/admin.d.ts +9 -1
- package/node_modules/@playdrop/api-client/dist/domains/admin.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/domains/admin.js +45 -0
- package/node_modules/@playdrop/api-client/dist/domains/apps.d.ts +3 -0
- package/node_modules/@playdrop/api-client/dist/domains/apps.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/domains/apps.js +27 -0
- package/node_modules/@playdrop/api-client/dist/domains/asset-packs.d.ts +2 -0
- package/node_modules/@playdrop/api-client/dist/domains/asset-packs.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/domains/asset-packs.js +16 -0
- package/node_modules/@playdrop/api-client/dist/domains/assets.d.ts +44 -2
- package/node_modules/@playdrop/api-client/dist/domains/assets.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/domains/assets.js +260 -3
- package/node_modules/@playdrop/api-client/dist/domains/payments.d.ts +17 -1
- package/node_modules/@playdrop/api-client/dist/domains/payments.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/domains/payments.js +173 -0
- package/node_modules/@playdrop/api-client/dist/domains/search.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/domains/search.js +39 -11
- package/node_modules/@playdrop/api-client/dist/domains/tags.d.ts +34 -0
- package/node_modules/@playdrop/api-client/dist/domains/tags.d.ts.map +1 -0
- package/node_modules/@playdrop/api-client/dist/domains/tags.js +111 -0
- package/node_modules/@playdrop/api-client/dist/index.d.ts +61 -1
- package/node_modules/@playdrop/api-client/dist/index.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/index.js +50 -0
- package/node_modules/@playdrop/api-client/package.json +1 -1
- package/node_modules/@playdrop/boxel-core/package.json +1 -1
- package/node_modules/@playdrop/boxel-three/package.json +1 -1
- package/node_modules/@playdrop/config/client-meta.json +4 -4
- package/node_modules/@playdrop/config/dist/src/constants.d.ts +11 -0
- package/node_modules/@playdrop/config/dist/src/constants.d.ts.map +1 -1
- package/node_modules/@playdrop/config/dist/src/constants.js +12 -1
- package/node_modules/@playdrop/config/dist/tsconfig.tsbuildinfo +1 -1
- package/node_modules/@playdrop/config/package.json +1 -1
- package/node_modules/@playdrop/types/dist/api.d.ts +346 -6
- package/node_modules/@playdrop/types/dist/api.d.ts.map +1 -1
- package/node_modules/@playdrop/types/dist/api.js +52 -1
- package/node_modules/@playdrop/types/dist/asset-pack.d.ts +7 -1
- package/node_modules/@playdrop/types/dist/asset-pack.d.ts.map +1 -1
- package/node_modules/@playdrop/types/dist/asset-spec-contract-meta-schema.json +86 -0
- package/node_modules/@playdrop/types/dist/asset-spec.d.ts +163 -0
- package/node_modules/@playdrop/types/dist/asset-spec.d.ts.map +1 -0
- package/node_modules/@playdrop/types/dist/asset-spec.js +101 -0
- package/node_modules/@playdrop/types/dist/asset.d.ts +23 -6
- package/node_modules/@playdrop/types/dist/asset.d.ts.map +1 -1
- package/node_modules/@playdrop/types/dist/asset.js +4 -1
- package/node_modules/@playdrop/types/dist/graph.d.ts +4 -2
- package/node_modules/@playdrop/types/dist/graph.d.ts.map +1 -1
- package/node_modules/@playdrop/types/dist/graph.js +9 -2
- package/node_modules/@playdrop/types/dist/index.d.ts +1 -0
- package/node_modules/@playdrop/types/dist/index.d.ts.map +1 -1
- package/node_modules/@playdrop/types/dist/index.js +1 -0
- package/node_modules/@playdrop/types/dist/version.d.ts +13 -0
- package/node_modules/@playdrop/types/dist/version.d.ts.map +1 -1
- package/node_modules/@playdrop/types/dist/version.js +21 -0
- package/node_modules/@playdrop/types/package.json +6 -1
- package/node_modules/@playdrop/vox-three/package.json +1 -1
- package/package.json +3 -1
package/config/client-meta.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.5.
|
|
2
|
+
"version": "0.5.3",
|
|
3
3
|
"build": 1,
|
|
4
4
|
"platforms": {
|
|
5
5
|
"ios": {
|
|
@@ -26,11 +26,11 @@
|
|
|
26
26
|
},
|
|
27
27
|
"clients": {
|
|
28
28
|
"web": {
|
|
29
|
-
"minimumVersion": "0.5.
|
|
29
|
+
"minimumVersion": "0.5.3",
|
|
30
30
|
"minimumBuild": 1
|
|
31
31
|
},
|
|
32
32
|
"admin": {
|
|
33
|
-
"minimumVersion": "0.5.
|
|
33
|
+
"minimumVersion": "0.5.3",
|
|
34
34
|
"minimumBuild": 1
|
|
35
35
|
},
|
|
36
36
|
"apple": {
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"minimumBuild": 1
|
|
39
39
|
},
|
|
40
40
|
"cli": {
|
|
41
|
-
"minimumVersion": "0.5.
|
|
41
|
+
"minimumVersion": "0.5.3"
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
44
|
}
|
package/dist/apps/build.js
CHANGED
|
@@ -190,7 +190,7 @@ function readProjectDirEntries(current) {
|
|
|
190
190
|
return [];
|
|
191
191
|
}
|
|
192
192
|
}
|
|
193
|
-
function appendIncludedProjectFile(rootDir, includePath, fileByRelativePath, results, enforceReservedBundleNames) {
|
|
193
|
+
function appendIncludedProjectFile(rootDir, includePath, excludedRelativeFiles, fileByRelativePath, results, enforceReservedBundleNames) {
|
|
194
194
|
const normalizedIncludePath = (0, node_path_1.normalize)(includePath).split(node_path_1.sep).join('/');
|
|
195
195
|
if (!normalizedIncludePath || normalizedIncludePath === '.') {
|
|
196
196
|
return;
|
|
@@ -198,6 +198,9 @@ function appendIncludedProjectFile(rootDir, includePath, fileByRelativePath, res
|
|
|
198
198
|
if (normalizedIncludePath.startsWith('..')) {
|
|
199
199
|
throw new Error(`[apps][build] Invalid include file path "${includePath}".`);
|
|
200
200
|
}
|
|
201
|
+
if (excludedRelativeFiles.has(normalizedIncludePath)) {
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
201
204
|
if (fileByRelativePath.has(normalizedIncludePath)) {
|
|
202
205
|
return;
|
|
203
206
|
}
|
|
@@ -223,8 +226,12 @@ function appendIncludedProjectFile(rootDir, includePath, fileByRelativePath, res
|
|
|
223
226
|
results.push(record);
|
|
224
227
|
fileByRelativePath.set(record.relativePath, record);
|
|
225
228
|
}
|
|
229
|
+
// eslint-disable-next-line complexity -- Source archive collection needs one pass to enforce ignore/include/exclude rules deterministically.
|
|
226
230
|
function collectProjectFiles(rootDir, rules, options) {
|
|
227
231
|
const includeRelativeFiles = options?.includeRelativeFiles ?? [];
|
|
232
|
+
const excludeRelativeFiles = new Set(Array.from(options?.excludeRelativeFiles ?? [])
|
|
233
|
+
.map((filePath) => (0, node_path_1.normalize)(filePath).split(node_path_1.sep).join('/'))
|
|
234
|
+
.filter((filePath) => filePath.length > 0 && filePath !== '.'));
|
|
228
235
|
const enforceReservedBundleNames = options?.enforceReservedBundleNames ?? false;
|
|
229
236
|
const results = [];
|
|
230
237
|
const fileByRelativePath = new Map();
|
|
@@ -263,6 +270,8 @@ function collectProjectFiles(rootDir, rules, options) {
|
|
|
263
270
|
}
|
|
264
271
|
if (!stats.isFile())
|
|
265
272
|
continue;
|
|
273
|
+
if (excludeRelativeFiles.has(relativePath))
|
|
274
|
+
continue;
|
|
266
275
|
const record = {
|
|
267
276
|
absolutePath,
|
|
268
277
|
relativePath: relativePath.split('/').join('/'),
|
|
@@ -273,7 +282,7 @@ function collectProjectFiles(rootDir, rules, options) {
|
|
|
273
282
|
}
|
|
274
283
|
}
|
|
275
284
|
for (const includePath of includeRelativeFiles) {
|
|
276
|
-
appendIncludedProjectFile(rootDir, includePath, fileByRelativePath, results, enforceReservedBundleNames);
|
|
285
|
+
appendIncludedProjectFile(rootDir, includePath, excludeRelativeFiles, fileByRelativePath, results, enforceReservedBundleNames);
|
|
277
286
|
}
|
|
278
287
|
results.sort((a, b) => a.relativePath.localeCompare(b.relativePath));
|
|
279
288
|
return results;
|
|
@@ -288,6 +297,32 @@ function isReservedBundlePath(relativePath) {
|
|
|
288
297
|
function resolveRuntimeRoot(task) {
|
|
289
298
|
return (0, node_path_1.resolve)((0, node_path_1.dirname)(task.filePath));
|
|
290
299
|
}
|
|
300
|
+
function collectSeparatelyUploadedSourceFiles(task) {
|
|
301
|
+
const excludedFiles = new Set();
|
|
302
|
+
const addFile = (filePath) => {
|
|
303
|
+
if (!filePath) {
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
const absolutePath = (0, node_path_1.resolve)(filePath);
|
|
307
|
+
if (!isPathWithinRoot(task.projectDir, absolutePath)) {
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
const relativePath = normalizeRelativePath(task.projectDir, absolutePath);
|
|
311
|
+
if (!relativePath || relativePath.startsWith('..')) {
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
excludedFiles.add(relativePath);
|
|
315
|
+
};
|
|
316
|
+
addFile(task.listing?.iconPath);
|
|
317
|
+
addFile(task.listing?.heroPortraitPath);
|
|
318
|
+
addFile(task.listing?.heroLandscapePath);
|
|
319
|
+
task.listing?.screenshotPortraitPaths?.forEach(addFile);
|
|
320
|
+
task.listing?.screenshotLandscapePaths?.forEach(addFile);
|
|
321
|
+
task.listing?.videoPortraitPaths?.forEach(addFile);
|
|
322
|
+
task.listing?.videoLandscapePaths?.forEach(addFile);
|
|
323
|
+
task.achievements?.forEach((definition) => addFile(definition.iconPath));
|
|
324
|
+
return Array.from(excludedFiles).sort((left, right) => left.localeCompare(right));
|
|
325
|
+
}
|
|
291
326
|
function resolveBundleEntryPoint(task, runtimeRoot) {
|
|
292
327
|
const entryPoint = normalizeRelativePath(runtimeRoot, task.filePath);
|
|
293
328
|
if (!entryPoint || entryPoint.startsWith('..')) {
|
|
@@ -451,23 +486,31 @@ function createZipArchive(entries) {
|
|
|
451
486
|
function createSourceArchive(task) {
|
|
452
487
|
const rules = buildIgnoreRules(task.projectDir);
|
|
453
488
|
const includeFiles = ['README.md', 'AGENTS.md'];
|
|
489
|
+
const excludedFiles = collectSeparatelyUploadedSourceFiles(task);
|
|
454
490
|
if (task.catalogueAbsolutePath && (0, node_fs_1.existsSync)(task.catalogueAbsolutePath)) {
|
|
455
491
|
const relativeCataloguePath = normalizeRelativePath(task.projectDir, task.catalogueAbsolutePath) || 'catalogue.json';
|
|
456
492
|
includeFiles.push(relativeCataloguePath);
|
|
457
493
|
}
|
|
458
494
|
if (task.isTypescriptProject) {
|
|
459
|
-
const
|
|
495
|
+
const cacheKey = [task.projectDir, ...excludedFiles].join('\u0000');
|
|
496
|
+
const cached = typescriptSourceCache.get(cacheKey);
|
|
460
497
|
if (cached) {
|
|
461
498
|
return cached;
|
|
462
499
|
}
|
|
463
|
-
const entries = collectProjectFiles(task.projectDir, rules, {
|
|
500
|
+
const entries = collectProjectFiles(task.projectDir, rules, {
|
|
501
|
+
includeRelativeFiles: includeFiles,
|
|
502
|
+
excludeRelativeFiles: excludedFiles,
|
|
503
|
+
});
|
|
464
504
|
const buffer = createZipArchive(entries);
|
|
465
505
|
const hash = (0, node_crypto_1.createHash)('sha256').update(buffer).digest('hex');
|
|
466
506
|
const record = { buffer, hash };
|
|
467
|
-
typescriptSourceCache.set(
|
|
507
|
+
typescriptSourceCache.set(cacheKey, record);
|
|
468
508
|
return record;
|
|
469
509
|
}
|
|
470
|
-
const entries = collectProjectFiles(task.projectDir, rules, {
|
|
510
|
+
const entries = collectProjectFiles(task.projectDir, rules, {
|
|
511
|
+
includeRelativeFiles: includeFiles,
|
|
512
|
+
excludeRelativeFiles: excludedFiles,
|
|
513
|
+
});
|
|
471
514
|
const buffer = createZipArchive(entries);
|
|
472
515
|
const hash = (0, node_crypto_1.createHash)('sha256').update(buffer).digest('hex');
|
|
473
516
|
return { buffer, hash };
|
package/dist/apps/index.d.ts
CHANGED
|
@@ -9,6 +9,8 @@ export type AppPipelineResult = {
|
|
|
9
9
|
};
|
|
10
10
|
export type AppPipelineOptions = {
|
|
11
11
|
skipEcs?: boolean;
|
|
12
|
+
skipReview?: boolean;
|
|
13
|
+
clearTags?: boolean;
|
|
12
14
|
creatorUsername?: string;
|
|
13
15
|
};
|
|
14
16
|
export declare function runAppPipeline(client: ApiClient, task: AppTask, options?: AppPipelineOptions): Promise<AppPipelineResult>;
|
package/dist/apps/index.js
CHANGED
|
@@ -16,6 +16,8 @@ async function runAppPipeline(client, task, options) {
|
|
|
16
16
|
const artifacts = isExternal ? null : await (0, build_1.buildApp)(task);
|
|
17
17
|
const uploadOptions = {
|
|
18
18
|
skipEcs: options?.skipEcs,
|
|
19
|
+
skipReview: options?.skipReview,
|
|
20
|
+
clearTags: options?.clearTags,
|
|
19
21
|
creatorUsername: options?.creatorUsername,
|
|
20
22
|
};
|
|
21
23
|
const upload = await (0, upload_1.uploadApp)(client, task, artifacts, uploadOptions);
|
package/dist/apps/upload.d.ts
CHANGED
|
@@ -14,6 +14,8 @@ export type AppUploadResult = {
|
|
|
14
14
|
};
|
|
15
15
|
export type AppUploadOptions = {
|
|
16
16
|
skipEcs?: boolean;
|
|
17
|
+
skipReview?: boolean;
|
|
18
|
+
clearTags?: boolean;
|
|
17
19
|
creatorUsername?: string;
|
|
18
20
|
};
|
|
19
21
|
export declare function uploadApp(client: ApiClient, task: AppTask, artifacts: AppBuildArtifacts | null, options?: AppUploadOptions): Promise<AppUploadResult>;
|
package/dist/apps/upload.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.uploadApp = uploadApp;
|
|
4
4
|
const types_1 = require("@playdrop/types");
|
|
5
|
+
const config_1 = require("@playdrop/config");
|
|
5
6
|
const node_fs_1 = require("node:fs");
|
|
6
7
|
const node_path_1 = require("node:path");
|
|
7
8
|
const http_1 = require("../http");
|
|
@@ -24,16 +25,68 @@ function isMp4Signature(content) {
|
|
|
24
25
|
}
|
|
25
26
|
return content.toString('ascii', 4, 8) === 'ftyp';
|
|
26
27
|
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
function createUploadValidationError(code, message) {
|
|
29
|
+
const error = new Error(`${code}: ${message}`);
|
|
30
|
+
error.code = code;
|
|
31
|
+
return error;
|
|
32
|
+
}
|
|
33
|
+
function formatLimitBytes(maxBytes) {
|
|
34
|
+
if (maxBytes >= 1024 * 1024) {
|
|
35
|
+
return `${maxBytes / 1024 / 1024}MB`;
|
|
36
|
+
}
|
|
37
|
+
return `${maxBytes / 1024}KB`;
|
|
38
|
+
}
|
|
39
|
+
function createFileFromPath(filePath, mimeType, maxBytes, sizeCode, typeCode, label, validator) {
|
|
31
40
|
const content = (0, node_fs_1.readFileSync)(filePath);
|
|
41
|
+
if (content.length > maxBytes) {
|
|
42
|
+
throw createUploadValidationError(sizeCode, `${label} must be under ${formatLimitBytes(maxBytes)}. Received ${content.length} bytes from ${filePath}.`);
|
|
43
|
+
}
|
|
32
44
|
if (validator && !validator(content)) {
|
|
33
|
-
throw
|
|
45
|
+
throw createUploadValidationError(typeCode, `${label} must match ${mimeType}. Invalid file signature at ${filePath}.`);
|
|
34
46
|
}
|
|
35
47
|
const name = (0, node_path_1.basename)(filePath);
|
|
36
|
-
return
|
|
48
|
+
return {
|
|
49
|
+
file: new File([content], name, { type: mimeType }),
|
|
50
|
+
size: content.length,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
// eslint-disable-next-line complexity -- Source archive failures need distinct guidance when publish-only media is already excluded.
|
|
54
|
+
function ensurePreparedArtifactSize(file, maxBytes, code, label, task) {
|
|
55
|
+
if (!file) {
|
|
56
|
+
return 0;
|
|
57
|
+
}
|
|
58
|
+
if (file.size > maxBytes) {
|
|
59
|
+
if (code === 'source_too_large' && task) {
|
|
60
|
+
const hasSeparatePublishMedia = Boolean(task.listing?.iconPath
|
|
61
|
+
|| task.listing?.heroPortraitPath
|
|
62
|
+
|| task.listing?.heroLandscapePath
|
|
63
|
+
|| task.listing?.screenshotPortraitPaths?.length
|
|
64
|
+
|| task.listing?.screenshotLandscapePaths?.length
|
|
65
|
+
|| task.listing?.videoPortraitPaths?.length
|
|
66
|
+
|| task.listing?.videoLandscapePaths?.length
|
|
67
|
+
|| task.achievements?.length);
|
|
68
|
+
const suffix = hasSeparatePublishMedia
|
|
69
|
+
? ' Listing and achievement media are uploaded separately and excluded from source.zip. The remaining source archive is still too large.'
|
|
70
|
+
: '';
|
|
71
|
+
throw createUploadValidationError(code, `${label} must be under ${formatLimitBytes(maxBytes)}. Received ${file.size} bytes.${suffix}`);
|
|
72
|
+
}
|
|
73
|
+
throw createUploadValidationError(code, `${label} must be under ${formatLimitBytes(maxBytes)}. Received ${file.size} bytes.`);
|
|
74
|
+
}
|
|
75
|
+
return file.size;
|
|
76
|
+
}
|
|
77
|
+
function accumulateUploadBytes(taskName, currentTotal, nextBytes) {
|
|
78
|
+
const nextTotal = currentTotal + nextBytes;
|
|
79
|
+
if (nextTotal > config_1.MAX_VERSION_TOTAL_BYTES) {
|
|
80
|
+
throw createUploadValidationError('upload_too_large', `Total upload for "${taskName}" exceeds ${formatLimitBytes(config_1.MAX_VERSION_TOTAL_BYTES)}. Received ${nextTotal} bytes.`);
|
|
81
|
+
}
|
|
82
|
+
return nextTotal;
|
|
83
|
+
}
|
|
84
|
+
function pushPreparedFile(taskName, currentTotal, prepared) {
|
|
85
|
+
return accumulateUploadBytes(taskName, currentTotal, prepared.size);
|
|
86
|
+
}
|
|
87
|
+
function pushPreparedArtifact(task, currentTotal, file, maxBytes, code, label) {
|
|
88
|
+
const bytes = ensurePreparedArtifactSize(file, maxBytes, code, label, task);
|
|
89
|
+
return accumulateUploadBytes(task.name, currentTotal, bytes);
|
|
37
90
|
}
|
|
38
91
|
async function uploadApp(client, task, artifacts, options) {
|
|
39
92
|
// Version is required - no legacy fallback
|
|
@@ -46,28 +99,37 @@ async function uploadApp(client, task, artifacts, options) {
|
|
|
46
99
|
/**
|
|
47
100
|
* Upload an app version using the versioned API.
|
|
48
101
|
*/
|
|
102
|
+
// eslint-disable-next-line complexity -- Upload assembly mirrors the versioned multipart contract in one place.
|
|
49
103
|
async function uploadAppVersion(client, task, artifacts, options) {
|
|
50
104
|
const creatorUsername = typeof options?.creatorUsername === 'string' ? options.creatorUsername.trim() : '';
|
|
51
105
|
if (!creatorUsername) {
|
|
52
106
|
throw new Error(`Missing creator username for app "${task.name}" upload.`);
|
|
53
107
|
}
|
|
108
|
+
let totalUploadBytes = 0;
|
|
109
|
+
const achievements = task.achievements ?? [];
|
|
110
|
+
const leaderboards = task.leaderboards ?? [];
|
|
111
|
+
const listing = task.listing;
|
|
54
112
|
const versionOptions = {
|
|
55
113
|
version: task.version,
|
|
56
114
|
releaseNotes: task.releaseNotes,
|
|
57
115
|
visibility: task.versionVisibility,
|
|
58
116
|
remixRef: task.remix,
|
|
117
|
+
tags: task.tags,
|
|
118
|
+
clearTags: options?.clearTags,
|
|
59
119
|
surfaceTargets: task.surfaceTargets,
|
|
120
|
+
assetSpecSupport: task.assetSpecSupport,
|
|
60
121
|
entryPoint: artifacts?.metadata?.entryPoint ?? undefined,
|
|
61
122
|
authMode: task.authMode,
|
|
62
123
|
controllerMode: task.controllerMode,
|
|
63
124
|
previewable: task.previewable,
|
|
125
|
+
skipReview: options?.skipReview,
|
|
64
126
|
// App metadata (used when creating app if it doesn't exist)
|
|
65
127
|
displayName: task.displayName,
|
|
66
128
|
description: task.description,
|
|
67
129
|
type: task.type,
|
|
68
130
|
emoji: task.emoji ?? undefined,
|
|
69
131
|
color: task.color ?? undefined,
|
|
70
|
-
achievements:
|
|
132
|
+
achievements: achievements.map((definition) => ({
|
|
71
133
|
key: definition.key,
|
|
72
134
|
displayName: definition.displayName,
|
|
73
135
|
description: definition.description,
|
|
@@ -77,7 +139,7 @@ async function uploadAppVersion(client, task, artifacts, options) {
|
|
|
77
139
|
icon: definition.icon,
|
|
78
140
|
status: definition.status,
|
|
79
141
|
})),
|
|
80
|
-
leaderboards:
|
|
142
|
+
leaderboards: leaderboards.map((definition) => ({
|
|
81
143
|
key: definition.key,
|
|
82
144
|
displayName: definition.displayName,
|
|
83
145
|
description: definition.description,
|
|
@@ -86,10 +148,14 @@ async function uploadAppVersion(client, task, artifacts, options) {
|
|
|
86
148
|
...(definition.unitLabel ? { unitLabel: definition.unitLabel } : {}),
|
|
87
149
|
status: definition.status,
|
|
88
150
|
})),
|
|
89
|
-
achievementIconFiles:
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
151
|
+
achievementIconFiles: achievements.map((definition) => {
|
|
152
|
+
const prepared = createFileFromPath(definition.iconPath, 'image/png', config_1.MAX_VERSION_ICON_BYTES, 'achievement_icon_too_large', 'invalid_achievement_icon_content_type', `Achievement icon "${definition.key}"`, isPngSignature);
|
|
153
|
+
totalUploadBytes = pushPreparedFile(task.name, totalUploadBytes, prepared);
|
|
154
|
+
return {
|
|
155
|
+
key: definition.key,
|
|
156
|
+
file: prepared.file,
|
|
157
|
+
};
|
|
158
|
+
}),
|
|
93
159
|
};
|
|
94
160
|
// For EXTERNAL hosting mode, use externalUrl instead of bundle
|
|
95
161
|
if (task.hostingMode === 'EXTERNAL') {
|
|
@@ -97,40 +163,66 @@ async function uploadAppVersion(client, task, artifacts, options) {
|
|
|
97
163
|
}
|
|
98
164
|
else if (artifacts) {
|
|
99
165
|
// HOSTED mode - include bundle and optional files
|
|
166
|
+
totalUploadBytes = pushPreparedArtifact(task, totalUploadBytes, artifacts.bundleFile, config_1.MAX_VERSION_BUNDLE_BYTES, 'bundle_too_large', 'Bundle');
|
|
167
|
+
totalUploadBytes = pushPreparedArtifact(task, totalUploadBytes, artifacts.sourceFile, config_1.MAX_APP_SOURCE_BYTES, 'source_too_large', 'Source archive');
|
|
100
168
|
versionOptions.bundle = artifacts.bundleFile;
|
|
101
169
|
versionOptions.source = artifacts.sourceFile;
|
|
102
170
|
// Include ecs/server files unless skipEcs is set
|
|
103
171
|
if (!options?.skipEcs) {
|
|
104
172
|
if (artifacts.ecsFile) {
|
|
173
|
+
totalUploadBytes = pushPreparedArtifact(task, totalUploadBytes, artifacts.ecsFile, config_1.MAX_APP_ECS_BYTES, 'ecs_too_large', 'ECS config');
|
|
105
174
|
versionOptions.ecs = artifacts.ecsFile;
|
|
106
175
|
}
|
|
107
176
|
if (artifacts.serverFile) {
|
|
177
|
+
totalUploadBytes = pushPreparedArtifact(task, totalUploadBytes, artifacts.serverFile, config_1.MAX_APP_SERVER_BYTES, 'server_too_large', 'Server.js');
|
|
108
178
|
versionOptions.server = artifacts.serverFile;
|
|
109
179
|
}
|
|
110
180
|
}
|
|
111
181
|
}
|
|
112
182
|
// Add listing assets if provided
|
|
113
|
-
if (
|
|
114
|
-
if (
|
|
115
|
-
|
|
183
|
+
if (listing) {
|
|
184
|
+
if (listing.iconPath) {
|
|
185
|
+
const prepared = createFileFromPath(listing.iconPath, 'image/png', config_1.MAX_VERSION_ICON_BYTES, 'icon_too_large', 'invalid_icon_content_type', 'Icon', isPngSignature);
|
|
186
|
+
totalUploadBytes = pushPreparedFile(task.name, totalUploadBytes, prepared);
|
|
187
|
+
versionOptions.icon = prepared.file;
|
|
116
188
|
}
|
|
117
|
-
if (
|
|
118
|
-
|
|
189
|
+
if (listing.heroPortraitPath) {
|
|
190
|
+
const prepared = createFileFromPath(listing.heroPortraitPath, 'image/png', config_1.MAX_VERSION_HERO_BYTES, 'hero_portrait_too_large', 'invalid_hero_portrait_content_type', 'Hero portrait', isPngSignature);
|
|
191
|
+
totalUploadBytes = pushPreparedFile(task.name, totalUploadBytes, prepared);
|
|
192
|
+
versionOptions.heroPortrait = prepared.file;
|
|
119
193
|
}
|
|
120
|
-
if (
|
|
121
|
-
|
|
194
|
+
if (listing.heroLandscapePath) {
|
|
195
|
+
const prepared = createFileFromPath(listing.heroLandscapePath, 'image/png', config_1.MAX_VERSION_HERO_BYTES, 'hero_landscape_too_large', 'invalid_hero_landscape_content_type', 'Hero landscape', isPngSignature);
|
|
196
|
+
totalUploadBytes = pushPreparedFile(task.name, totalUploadBytes, prepared);
|
|
197
|
+
versionOptions.heroLandscape = prepared.file;
|
|
122
198
|
}
|
|
123
|
-
if (
|
|
124
|
-
versionOptions.screenshotsPortrait =
|
|
199
|
+
if ((listing.screenshotPortraitPaths ?? []).length > 0) {
|
|
200
|
+
versionOptions.screenshotsPortrait = listing.screenshotPortraitPaths.map((filePath, index) => {
|
|
201
|
+
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);
|
|
202
|
+
totalUploadBytes = pushPreparedFile(task.name, totalUploadBytes, prepared);
|
|
203
|
+
return prepared.file;
|
|
204
|
+
});
|
|
125
205
|
}
|
|
126
|
-
if (
|
|
127
|
-
versionOptions.screenshotsLandscape =
|
|
206
|
+
if ((listing.screenshotLandscapePaths ?? []).length > 0) {
|
|
207
|
+
versionOptions.screenshotsLandscape = listing.screenshotLandscapePaths.map((filePath, index) => {
|
|
208
|
+
const prepared = createFileFromPath(filePath, 'image/png', config_1.MAX_VERSION_SCREENSHOT_BYTES, 'screenshot_landscape_too_large', 'invalid_screenshot_landscape_content_type', `Landscape screenshot ${index + 1}`, isPngSignature);
|
|
209
|
+
totalUploadBytes = pushPreparedFile(task.name, totalUploadBytes, prepared);
|
|
210
|
+
return prepared.file;
|
|
211
|
+
});
|
|
128
212
|
}
|
|
129
|
-
if (
|
|
130
|
-
versionOptions.videosPortrait =
|
|
213
|
+
if ((listing.videoPortraitPaths ?? []).length > 0) {
|
|
214
|
+
versionOptions.videosPortrait = listing.videoPortraitPaths.map((filePath, index) => {
|
|
215
|
+
const prepared = createFileFromPath(filePath, 'video/mp4', config_1.MAX_VERSION_VIDEO_BYTES, 'video_portrait_too_large', 'invalid_video_portrait_content_type', `Portrait video ${index + 1}`, isMp4Signature);
|
|
216
|
+
totalUploadBytes = pushPreparedFile(task.name, totalUploadBytes, prepared);
|
|
217
|
+
return prepared.file;
|
|
218
|
+
});
|
|
131
219
|
}
|
|
132
|
-
if (
|
|
133
|
-
versionOptions.videosLandscape =
|
|
220
|
+
if ((listing.videoLandscapePaths ?? []).length > 0) {
|
|
221
|
+
versionOptions.videosLandscape = listing.videoLandscapePaths.map((filePath, index) => {
|
|
222
|
+
const prepared = createFileFromPath(filePath, 'video/mp4', config_1.MAX_VERSION_VIDEO_BYTES, 'video_landscape_too_large', 'invalid_video_landscape_content_type', `Landscape video ${index + 1}`, isMp4Signature);
|
|
223
|
+
totalUploadBytes = pushPreparedFile(task.name, totalUploadBytes, prepared);
|
|
224
|
+
return prepared.file;
|
|
225
|
+
});
|
|
134
226
|
}
|
|
135
227
|
}
|
|
136
228
|
try {
|
|
@@ -164,7 +256,13 @@ async function uploadAppVersion(client, task, artifacts, options) {
|
|
|
164
256
|
}
|
|
165
257
|
const statusText = `status ${unknownError.status}`;
|
|
166
258
|
const message = detail ? `${statusText} - ${detail}` : statusText;
|
|
167
|
-
|
|
259
|
+
const errorCode = typeof unknownError.code === 'string' && unknownError.code.trim().length > 0
|
|
260
|
+
? unknownError.code.trim()
|
|
261
|
+
: '';
|
|
262
|
+
const annotatedMessage = detail
|
|
263
|
+
? `${errorCode ? `${errorCode}: ` : ''}${detail}`
|
|
264
|
+
: `${statusText}${errorCode ? ` (${errorCode})` : ''}`;
|
|
265
|
+
throw new Error(`Upload failed for ${task.name}: ${annotatedMessage}`);
|
|
168
266
|
}
|
|
169
267
|
throw unknownError;
|
|
170
268
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type AssetSpecCapability, type AssetSpecContract, type AssetSpecValidationKind } from '@playdrop/types';
|
|
2
|
+
export type AssetSpecSupportDeclaration = {
|
|
3
|
+
assetSpec: string;
|
|
4
|
+
versionRange: string;
|
|
5
|
+
capabilities: AssetSpecCapability[];
|
|
6
|
+
};
|
|
7
|
+
export type LoadedAssetSpecContract = {
|
|
8
|
+
contract: AssetSpecContract;
|
|
9
|
+
validationKind: AssetSpecValidationKind;
|
|
10
|
+
contractPath: string;
|
|
11
|
+
};
|
|
12
|
+
export declare function normalizeAssetSpecSupportEntries(raw: unknown, errors: string[], context: string): AssetSpecSupportDeclaration[];
|
|
13
|
+
export declare function loadAndValidateAssetSpecContract(catalogueDir: string, relativeContractPath: string, errors: string[], context: string): LoadedAssetSpecContract | null;
|
|
14
|
+
export declare function validateCustomAssetFilesAgainstContract(contract: AssetSpecContract, filePaths: Record<string, string>, errors: string[], context: string): void;
|
|
15
|
+
export declare function normalizeAssetSpecVersionRef(raw: unknown, errors: string[], context: string): string | undefined;
|
|
16
|
+
export declare function validateAssetSpecName(name: string, errors: string[], context: string): boolean;
|