@playdrop/playdrop-cli 0.15.6 → 0.15.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/config/client-meta.json +2 -2
- package/dist/apps/index.d.ts +2 -3
- package/dist/apps/index.js +2 -2
- package/dist/apps/loadCheck.d.ts +4 -1
- package/dist/apps/loadCheck.js +64 -7
- package/dist/apps/staticHtml.js +1 -0
- package/dist/apps/upload.d.ts +38 -1
- package/dist/apps/upload.js +58 -44
- package/dist/catalogue.d.ts +3 -0
- package/dist/catalogue.js +26 -0
- package/dist/commands/create.js +45 -16
- package/dist/commands/devRuntimeAssets.d.ts +1 -0
- package/dist/commands/devRuntimeAssets.js +2 -0
- package/dist/commands/review.js +0 -3
- package/dist/commands/upload.d.ts +2 -3
- package/dist/commands/upload.js +23 -42
- package/dist/commands/validate.js +0 -4
- package/dist/commands/worker.d.ts +7 -1
- package/dist/commands/worker.js +65 -57
- package/dist/index.js +1 -1
- package/dist/listingPreflight.d.ts +1 -24
- package/dist/listingPreflight.js +3 -288
- package/node_modules/@playdrop/api-client/dist/client.d.ts +25 -2
- package/node_modules/@playdrop/api-client/dist/client.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/client.js +11 -0
- package/node_modules/@playdrop/api-client/dist/domains/admin.d.ts +4 -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 +31 -2
- package/node_modules/@playdrop/api-client/dist/domains/agent-tasks.d.ts +2 -1
- package/node_modules/@playdrop/api-client/dist/domains/agent-tasks.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/domains/agent-tasks.js +12 -0
- package/node_modules/@playdrop/api-client/dist/domains/apps.d.ts +9 -1
- package/node_modules/@playdrop/api-client/dist/domains/apps.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/domains/apps.js +66 -0
- package/node_modules/@playdrop/api-client/dist/domains/asset-packs.d.ts +1 -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 +3 -0
- package/node_modules/@playdrop/api-client/dist/domains/assets.d.ts +1 -0
- package/node_modules/@playdrop/api-client/dist/domains/assets.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/domains/assets.js +3 -0
- package/node_modules/@playdrop/api-client/dist/domains/payments.d.ts +10 -2
- package/node_modules/@playdrop/api-client/dist/domains/payments.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/domains/payments.js +66 -1
- package/node_modules/@playdrop/api-client/dist/index.d.ts +27 -5
- package/node_modules/@playdrop/api-client/dist/index.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/index.js +73 -2
- package/node_modules/@playdrop/config/client-meta.json +2 -2
- package/node_modules/@playdrop/config/dist/src/creator-plans.d.ts +18 -0
- package/node_modules/@playdrop/config/dist/src/creator-plans.d.ts.map +1 -0
- package/node_modules/@playdrop/config/dist/src/creator-plans.js +117 -0
- package/node_modules/@playdrop/config/dist/src/game-arena.d.ts +1 -1
- package/node_modules/@playdrop/config/dist/src/game-arena.js +1 -1
- package/node_modules/@playdrop/config/dist/src/index.d.ts +1 -0
- package/node_modules/@playdrop/config/dist/src/index.d.ts.map +1 -1
- package/node_modules/@playdrop/config/dist/src/index.js +1 -0
- package/node_modules/@playdrop/config/dist/test/creator-plans.test.d.ts +2 -0
- package/node_modules/@playdrop/config/dist/test/creator-plans.test.d.ts.map +1 -0
- package/node_modules/@playdrop/config/dist/test/creator-plans.test.js +28 -0
- package/node_modules/@playdrop/config/dist/test/game-arena.test.js +3 -3
- package/node_modules/@playdrop/config/dist/tsconfig.tsbuildinfo +1 -1
- package/node_modules/@playdrop/types/dist/api.d.ts +153 -15
- package/node_modules/@playdrop/types/dist/api.d.ts.map +1 -1
- package/node_modules/@playdrop/types/dist/api.js +27 -2
- package/node_modules/@playdrop/types/dist/app-review.d.ts +2 -0
- package/node_modules/@playdrop/types/dist/app-review.d.ts.map +1 -1
- package/node_modules/@playdrop/types/dist/app-review.js +31 -2
- package/node_modules/@playdrop/types/dist/index.d.ts +2 -0
- package/node_modules/@playdrop/types/dist/index.d.ts.map +1 -1
- package/node_modules/@playdrop/types/dist/index.js +5 -1
- package/node_modules/@playdrop/types/dist/locale.d.ts +5 -0
- package/node_modules/@playdrop/types/dist/locale.d.ts.map +1 -0
- package/node_modules/@playdrop/types/dist/locale.js +47 -0
- package/node_modules/@playdrop/types/dist/release-smoke.d.ts +3 -0
- package/node_modules/@playdrop/types/dist/release-smoke.d.ts.map +1 -0
- package/node_modules/@playdrop/types/dist/release-smoke.js +35 -0
- package/node_modules/@playdrop/types/dist/version.d.ts +5 -0
- package/node_modules/@playdrop/types/dist/version.d.ts.map +1 -1
- package/package.json +1 -1
package/config/client-meta.json
CHANGED
package/dist/apps/index.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ import type { AppTask } from '../catalogue';
|
|
|
4
4
|
import type { TaskCaptureSession } from '../appUrls';
|
|
5
5
|
import { collectAppValidationWarnings, validateAppTask, runFormatScript } from './validate';
|
|
6
6
|
import { buildApp, type AppBuildArtifacts } from './build';
|
|
7
|
-
import { uploadApp, type AppUploadResult } from './upload';
|
|
7
|
+
import { prepareAppUploadFiles, uploadApp, type AppUploadResult } from './upload';
|
|
8
8
|
export type AppPipelineResult = {
|
|
9
9
|
artifacts: AppBuildArtifacts | null;
|
|
10
10
|
upload: AppUploadResult;
|
|
@@ -25,10 +25,9 @@ export type AppPipelineOptions = {
|
|
|
25
25
|
ensureRegisteredAppShell?: boolean;
|
|
26
26
|
agentTaskId?: number;
|
|
27
27
|
playtestProof?: AgentTaskPlaytestProof;
|
|
28
|
-
mediaCaptureRequired?: boolean;
|
|
29
28
|
playtestSmokeCheckRequired?: boolean;
|
|
30
29
|
captureSession?: TaskCaptureSession | null;
|
|
31
30
|
artifacts?: AppBuildArtifacts;
|
|
32
31
|
};
|
|
33
32
|
export declare function runAppPipeline(client: ApiClient, task: AppTask, options?: AppPipelineOptions): Promise<AppPipelineResult>;
|
|
34
|
-
export { collectAppValidationWarnings, validateAppTask, buildApp, uploadApp, runFormatScript };
|
|
33
|
+
export { collectAppValidationWarnings, validateAppTask, buildApp, prepareAppUploadFiles, uploadApp, runFormatScript };
|
package/dist/apps/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.runFormatScript = exports.uploadApp = exports.buildApp = exports.validateAppTask = exports.collectAppValidationWarnings = void 0;
|
|
3
|
+
exports.runFormatScript = exports.uploadApp = exports.prepareAppUploadFiles = exports.buildApp = exports.validateAppTask = exports.collectAppValidationWarnings = void 0;
|
|
4
4
|
exports.runAppPipeline = runAppPipeline;
|
|
5
5
|
const validate_1 = require("./validate");
|
|
6
6
|
Object.defineProperty(exports, "collectAppValidationWarnings", { enumerable: true, get: function () { return validate_1.collectAppValidationWarnings; } });
|
|
@@ -9,6 +9,7 @@ Object.defineProperty(exports, "runFormatScript", { enumerable: true, get: funct
|
|
|
9
9
|
const build_1 = require("./build");
|
|
10
10
|
Object.defineProperty(exports, "buildApp", { enumerable: true, get: function () { return build_1.buildApp; } });
|
|
11
11
|
const upload_1 = require("./upload");
|
|
12
|
+
Object.defineProperty(exports, "prepareAppUploadFiles", { enumerable: true, get: function () { return upload_1.prepareAppUploadFiles; } });
|
|
12
13
|
Object.defineProperty(exports, "uploadApp", { enumerable: true, get: function () { return upload_1.uploadApp; } });
|
|
13
14
|
const registration_1 = require("./registration");
|
|
14
15
|
const loadCheck_1 = require("./loadCheck");
|
|
@@ -66,7 +67,6 @@ async function runAppPipeline(client, task, options) {
|
|
|
66
67
|
loadCheckTimeoutMs: options?.loadCheckTimeoutMs,
|
|
67
68
|
agentTaskId: options?.agentTaskId,
|
|
68
69
|
playtestProof: options?.playtestProof,
|
|
69
|
-
mediaCaptureRequired: options?.mediaCaptureRequired,
|
|
70
70
|
playtestSmokeCheckRequired: options?.playtestSmokeCheckRequired,
|
|
71
71
|
captureSession: options?.captureSession,
|
|
72
72
|
};
|
package/dist/apps/loadCheck.d.ts
CHANGED
|
@@ -38,7 +38,8 @@ export declare function isSoftwareWebGlRenderer(renderer: string | null | undefi
|
|
|
38
38
|
export declare function dispatchPlaytestTapeToFrame(page: Page, frame: Frame, surface: AppSurface, tape: AppPlaytestTape, hooks?: {
|
|
39
39
|
onBeforeEvent?: (eventIndex: number, event: AppPlaytestTapeEvent) => void;
|
|
40
40
|
}): Promise<void>;
|
|
41
|
-
export declare function
|
|
41
|
+
export declare function verifyHostedEditorPhaseRoundTrip(page: Pick<Page, 'evaluate'>, frame: Pick<Frame, 'evaluate' | 'waitForFunction'>, timeoutMs: number): Promise<void>;
|
|
42
|
+
export declare function formatHostedLoadCheckFailure(taskName: string, result: HostedLoadCheckResult, scope?: 'local' | 'staged' | 'editor' | 'final'): string;
|
|
42
43
|
export declare function redactHostedLoadCheckSecrets(text: string, sourceUrl: string): string;
|
|
43
44
|
export declare function runLocalHostedLoadCheck(input: {
|
|
44
45
|
client: ApiClient;
|
|
@@ -71,6 +72,8 @@ export declare function runUploadedHostedLoadCheck(input: {
|
|
|
71
72
|
gameFrameScreenshotPath?: string | null;
|
|
72
73
|
playtestTape?: AppPlaytestTape;
|
|
73
74
|
postReadyWaitMs?: number;
|
|
75
|
+
phase?: 'play' | 'editor';
|
|
76
|
+
verifyEditorPhaseRoundTrip?: boolean;
|
|
74
77
|
}): Promise<HostedLoadCheckResult>;
|
|
75
78
|
export declare function comparePlaytestFrames(firstPath: string, secondPath: string, thresholds?: {
|
|
76
79
|
meanDeltaMin: number;
|
package/dist/apps/loadCheck.js
CHANGED
|
@@ -10,6 +10,7 @@ exports.resolveLoadCheckSurface = resolveLoadCheckSurface;
|
|
|
10
10
|
exports.cloneLoadCheckContextOptions = cloneLoadCheckContextOptions;
|
|
11
11
|
exports.isSoftwareWebGlRenderer = isSoftwareWebGlRenderer;
|
|
12
12
|
exports.dispatchPlaytestTapeToFrame = dispatchPlaytestTapeToFrame;
|
|
13
|
+
exports.verifyHostedEditorPhaseRoundTrip = verifyHostedEditorPhaseRoundTrip;
|
|
13
14
|
exports.formatHostedLoadCheckFailure = formatHostedLoadCheckFailure;
|
|
14
15
|
exports.redactHostedLoadCheckSecrets = redactHostedLoadCheckSecrets;
|
|
15
16
|
exports.runLocalHostedLoadCheck = runLocalHostedLoadCheck;
|
|
@@ -417,6 +418,48 @@ async function findGameFrame(page, timeoutMs) {
|
|
|
417
418
|
}
|
|
418
419
|
return frame;
|
|
419
420
|
}
|
|
421
|
+
async function verifyHostedEditorPhaseRoundTrip(page, frame, timeoutMs) {
|
|
422
|
+
const armReadySignal = async () => {
|
|
423
|
+
await frame.evaluate(() => {
|
|
424
|
+
const runtimeWindow = window;
|
|
425
|
+
const state = runtimeWindow.__playdropEditorPhaseCheck ?? {
|
|
426
|
+
installed: false,
|
|
427
|
+
signals: [],
|
|
428
|
+
};
|
|
429
|
+
state.signals = [];
|
|
430
|
+
runtimeWindow.__playdropEditorPhaseCheck = state;
|
|
431
|
+
if (state.installed) {
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
state.installed = true;
|
|
435
|
+
window.addEventListener('playdrop:loading-state', (event) => {
|
|
436
|
+
const detail = event.detail;
|
|
437
|
+
if (detail?.status === 'ready') {
|
|
438
|
+
state.signals.push({ editor: detail.editor === true });
|
|
439
|
+
}
|
|
440
|
+
});
|
|
441
|
+
});
|
|
442
|
+
};
|
|
443
|
+
const changePhaseAndWait = async (phase, editor) => {
|
|
444
|
+
await armReadySignal();
|
|
445
|
+
await page.evaluate((nextPhase) => {
|
|
446
|
+
window.postMessage({ type: 'playdrop-studio-runtime-state', active: true, phase: nextPhase }, window.location.origin);
|
|
447
|
+
}, phase);
|
|
448
|
+
await frame.waitForFunction((expectedEditor) => {
|
|
449
|
+
const runtimeWindow = window;
|
|
450
|
+
return runtimeWindow.__playdropEditorPhaseCheck?.signals?.some((signal) => signal.editor === expectedEditor) === true;
|
|
451
|
+
}, editor, { timeout: timeoutMs });
|
|
452
|
+
};
|
|
453
|
+
try {
|
|
454
|
+
await changePhaseAndWait('play', false);
|
|
455
|
+
await changePhaseAndWait('editor', true);
|
|
456
|
+
}
|
|
457
|
+
catch (error) {
|
|
458
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
459
|
+
throw new Error('project_check_editor_phase_switch_failed: The hosted bundle must switch Editor to Playtest and back '
|
|
460
|
+
+ `inside the existing runtime frame. ${detail}`);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
420
463
|
async function readWebGlRenderer(frame) {
|
|
421
464
|
const renderer = await frame.evaluate(() => {
|
|
422
465
|
const canvas = document.createElement('canvas');
|
|
@@ -489,9 +532,11 @@ function normalizeLoadCheckFailure(error, artifactFingerprint) {
|
|
|
489
532
|
function formatHostedLoadCheckFailure(taskName, result, scope = 'local') {
|
|
490
533
|
const scopeLabel = scope === 'final'
|
|
491
534
|
? 'final owner play route'
|
|
492
|
-
: scope === '
|
|
493
|
-
? 'staged
|
|
494
|
-
:
|
|
535
|
+
: scope === 'editor'
|
|
536
|
+
? 'staged Editor phase'
|
|
537
|
+
: scope === 'staged'
|
|
538
|
+
? 'staged upload preview'
|
|
539
|
+
: 'local hosted preview';
|
|
495
540
|
const errorCode = result.errorCode?.trim() || 'startup_failure';
|
|
496
541
|
const message = normalizeLoadCheckMessage(result.message);
|
|
497
542
|
return `hosted_load_check_failed: ${taskName} failed the ${scopeLabel} check (${errorCode}). ${message}`;
|
|
@@ -694,6 +739,9 @@ async function runHostedLoadCheck(options) {
|
|
|
694
739
|
if (isSoftwareWebGlRenderer(webglRenderer)) {
|
|
695
740
|
throw new Error(`project_check_webgl_renderer_software: WebGL renderer is "${webglRenderer}". Real hardware acceleration is required.`);
|
|
696
741
|
}
|
|
742
|
+
if (options.verifyEditorPhaseRoundTrip) {
|
|
743
|
+
await verifyHostedEditorPhaseRoundTrip(page, frame, readinessTimeoutMs);
|
|
744
|
+
}
|
|
697
745
|
await saveGameFrameScreenshot(page, options.readyGameFrameScreenshotPath);
|
|
698
746
|
if (options.playtestTape) {
|
|
699
747
|
if (!options.playtestSurface) {
|
|
@@ -917,6 +965,12 @@ function appendLoadCheckParams(targetUrl, devAuth) {
|
|
|
917
965
|
}
|
|
918
966
|
async function runUploadedHostedLoadCheck(input) {
|
|
919
967
|
const preview = await input.client.fetchAppUploadLaunchCheckPreview(input.creatorUsername, input.task.name, input.sessionId);
|
|
968
|
+
const withRequestedPhase = (rawUrl) => {
|
|
969
|
+
const url = new URL(rawUrl);
|
|
970
|
+
if (input.phase === 'editor')
|
|
971
|
+
url.searchParams.set('hostPhase', 'editor');
|
|
972
|
+
return url.toString();
|
|
973
|
+
};
|
|
920
974
|
const captureSurface = resolveLoadCheckSurface(preview.localAppMetadata.surfaceTargets, input.task.primarySurface);
|
|
921
975
|
const contextOptions = cloneLoadCheckContextOptions(captureSurface);
|
|
922
976
|
const anonymousExpectedState = resolveAnonymousHostedLaunchState({
|
|
@@ -925,7 +979,7 @@ async function runUploadedHostedLoadCheck(input) {
|
|
|
925
979
|
});
|
|
926
980
|
let result;
|
|
927
981
|
if (input.captureSession) {
|
|
928
|
-
const targetUrl = new URL(preview.launchUrl);
|
|
982
|
+
const targetUrl = new URL(withRequestedPhase(preview.launchUrl));
|
|
929
983
|
(0, appUrls_1.appendTaskCaptureSession)(targetUrl, input.captureSession);
|
|
930
984
|
result = await runHostedLoadCheck({
|
|
931
985
|
targetUrl: targetUrl.toString(),
|
|
@@ -939,6 +993,7 @@ async function runUploadedHostedLoadCheck(input) {
|
|
|
939
993
|
playtestTape: input.playtestTape,
|
|
940
994
|
playtestSurface: captureSurface,
|
|
941
995
|
postReadyWaitMs: input.postReadyWaitMs,
|
|
996
|
+
verifyEditorPhaseRoundTrip: input.verifyEditorPhaseRoundTrip,
|
|
942
997
|
});
|
|
943
998
|
}
|
|
944
999
|
else if (anonymousExpectedState === 'login_required') {
|
|
@@ -947,7 +1002,7 @@ async function runUploadedHostedLoadCheck(input) {
|
|
|
947
1002
|
}
|
|
948
1003
|
else {
|
|
949
1004
|
const anonymousGateResult = await runHostedLoadCheck({
|
|
950
|
-
targetUrl: appendLoadCheckParams(preview.launchUrl, 'anonymous'),
|
|
1005
|
+
targetUrl: withRequestedPhase(appendLoadCheckParams(preview.launchUrl, 'anonymous')),
|
|
951
1006
|
artifactFingerprint: preview.session.manifestHash,
|
|
952
1007
|
timeoutMs: input.timeoutMs,
|
|
953
1008
|
expectedHostedLaunchState: anonymousExpectedState,
|
|
@@ -958,7 +1013,7 @@ async function runUploadedHostedLoadCheck(input) {
|
|
|
958
1013
|
}
|
|
959
1014
|
else {
|
|
960
1015
|
result = await runHostedLoadCheck({
|
|
961
|
-
targetUrl: appendLoadCheckParams(preview.launchUrl, 'viewer'),
|
|
1016
|
+
targetUrl: withRequestedPhase(appendLoadCheckParams(preview.launchUrl, 'viewer')),
|
|
962
1017
|
artifactFingerprint: preview.session.manifestHash,
|
|
963
1018
|
timeoutMs: input.timeoutMs,
|
|
964
1019
|
expectedHostedLaunchState: resolvePostAuthHostedLaunchState(preview.localAppMetadata.controllerMode),
|
|
@@ -972,13 +1027,14 @@ async function runUploadedHostedLoadCheck(input) {
|
|
|
972
1027
|
playtestTape: input.playtestTape,
|
|
973
1028
|
playtestSurface: captureSurface,
|
|
974
1029
|
postReadyWaitMs: input.postReadyWaitMs,
|
|
1030
|
+
verifyEditorPhaseRoundTrip: input.verifyEditorPhaseRoundTrip,
|
|
975
1031
|
});
|
|
976
1032
|
}
|
|
977
1033
|
}
|
|
978
1034
|
}
|
|
979
1035
|
else {
|
|
980
1036
|
result = await runHostedLoadCheck({
|
|
981
|
-
targetUrl: appendLoadCheckParams(preview.launchUrl, 'anonymous'),
|
|
1037
|
+
targetUrl: withRequestedPhase(appendLoadCheckParams(preview.launchUrl, 'anonymous')),
|
|
982
1038
|
artifactFingerprint: preview.session.manifestHash,
|
|
983
1039
|
timeoutMs: input.timeoutMs,
|
|
984
1040
|
expectedHostedLaunchState: anonymousExpectedState,
|
|
@@ -989,6 +1045,7 @@ async function runUploadedHostedLoadCheck(input) {
|
|
|
989
1045
|
playtestTape: input.playtestTape,
|
|
990
1046
|
playtestSurface: captureSurface,
|
|
991
1047
|
postReadyWaitMs: input.postReadyWaitMs,
|
|
1048
|
+
verifyEditorPhaseRoundTrip: input.verifyEditorPhaseRoundTrip,
|
|
992
1049
|
});
|
|
993
1050
|
}
|
|
994
1051
|
const recorded = await input.client.recordAppUploadLaunchCheck(input.creatorUsername, input.task.name, input.sessionId, {
|
package/dist/apps/staticHtml.js
CHANGED
|
@@ -257,6 +257,7 @@ function buildStaticAppTask(input) {
|
|
|
257
257
|
hostingMode: 'HOSTED',
|
|
258
258
|
authMode: 'NONE',
|
|
259
259
|
previewable: true,
|
|
260
|
+
editorSupported: false,
|
|
260
261
|
...(buildListing(input.metadata.imagePath, input.primarySurface)
|
|
261
262
|
? { listing: buildListing(input.metadata.imagePath, input.primarySurface) }
|
|
262
263
|
: {}),
|
package/dist/apps/upload.d.ts
CHANGED
|
@@ -22,8 +22,45 @@ export type AppUploadOptions = {
|
|
|
22
22
|
loadCheckTimeoutMs?: number;
|
|
23
23
|
agentTaskId?: number;
|
|
24
24
|
playtestProof?: AgentTaskPlaytestProof;
|
|
25
|
-
mediaCaptureRequired?: boolean;
|
|
26
25
|
playtestSmokeCheckRequired?: boolean;
|
|
27
26
|
captureSession?: TaskCaptureSession | null;
|
|
28
27
|
};
|
|
28
|
+
type PreparedSessionFile = {
|
|
29
|
+
fileKey: string;
|
|
30
|
+
file: File;
|
|
31
|
+
filename: string;
|
|
32
|
+
contentType: string;
|
|
33
|
+
sizeBytes: number;
|
|
34
|
+
sha256: string;
|
|
35
|
+
};
|
|
36
|
+
type PreparedOwnedAssetUpload = {
|
|
37
|
+
uploadKey: string;
|
|
38
|
+
runtimeKey?: string;
|
|
39
|
+
name: string;
|
|
40
|
+
displayName?: string;
|
|
41
|
+
description?: string;
|
|
42
|
+
category: string;
|
|
43
|
+
subcategory: string | null;
|
|
44
|
+
format: string;
|
|
45
|
+
visibility?: string;
|
|
46
|
+
license: string;
|
|
47
|
+
assetSpec?: string;
|
|
48
|
+
tags: string[];
|
|
49
|
+
shopListed?: boolean;
|
|
50
|
+
shopPriceCredits?: number;
|
|
51
|
+
files: Array<{
|
|
52
|
+
role: string;
|
|
53
|
+
file: File;
|
|
54
|
+
filename: string;
|
|
55
|
+
contentType: string;
|
|
56
|
+
sizeBytes: number;
|
|
57
|
+
sha256: string;
|
|
58
|
+
}>;
|
|
59
|
+
};
|
|
60
|
+
type PreparedAppUploadFiles = {
|
|
61
|
+
sessionFiles: PreparedSessionFile[];
|
|
62
|
+
ownedAssets: PreparedOwnedAssetUpload[];
|
|
63
|
+
};
|
|
64
|
+
export declare function prepareAppUploadFiles(task: AppTask, artifacts: AppBuildArtifacts | null): Promise<PreparedAppUploadFiles>;
|
|
29
65
|
export declare function uploadApp(client: ApiClient, task: AppTask, artifacts: AppBuildArtifacts | null, options?: AppUploadOptions): Promise<AppUploadResult>;
|
|
66
|
+
export {};
|
package/dist/apps/upload.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.prepareAppUploadFiles = prepareAppUploadFiles;
|
|
3
4
|
exports.uploadApp = uploadApp;
|
|
4
5
|
const config_1 = require("@playdrop/config");
|
|
5
6
|
const types_1 = require("@playdrop/types");
|
|
@@ -8,7 +9,6 @@ const node_fs_1 = require("node:fs");
|
|
|
8
9
|
const node_path_1 = require("node:path");
|
|
9
10
|
const http_1 = require("../http");
|
|
10
11
|
const loadCheck_1 = require("./loadCheck");
|
|
11
|
-
const listingPreflight_1 = require("../listingPreflight");
|
|
12
12
|
const EXTENSION_TO_MIME = {
|
|
13
13
|
'.png': 'image/png',
|
|
14
14
|
'.jpg': 'image/jpeg',
|
|
@@ -149,37 +149,20 @@ function pushPreparedArtifact(task, currentTotal, file, maxBytes, code, label) {
|
|
|
149
149
|
const bytes = ensurePreparedArtifactSize(file, maxBytes, code, label);
|
|
150
150
|
return accumulateUploadBytes(task.name, currentTotal, bytes);
|
|
151
151
|
}
|
|
152
|
-
async function
|
|
153
|
-
// Version is required - no legacy fallback
|
|
154
|
-
if (!task.version) {
|
|
155
|
-
throw new Error(`Missing "version" field in catalogue.json for app "${task.name}".\n` +
|
|
156
|
-
`Add a version field like "version": "1.0.0" to upload your app.`);
|
|
157
|
-
}
|
|
158
|
-
return uploadAppVersion(client, task, artifacts, options);
|
|
159
|
-
}
|
|
160
|
-
/**
|
|
161
|
-
* Upload an app version using the versioned API.
|
|
162
|
-
*/
|
|
163
|
-
async function uploadAppVersion(client, task, artifacts, options) {
|
|
164
|
-
const creatorUsername = typeof options?.creatorUsername === 'string' ? options.creatorUsername.trim() : '';
|
|
165
|
-
if (!creatorUsername) {
|
|
166
|
-
throw new Error(`Missing creator username for app "${task.name}" upload.`);
|
|
167
|
-
}
|
|
152
|
+
async function prepareAppUploadFiles(task, artifacts) {
|
|
168
153
|
let totalUploadBytes = 0;
|
|
169
|
-
const achievements = task.achievements ?? [];
|
|
170
|
-
const leaderboards = task.leaderboards ?? [];
|
|
171
154
|
const listing = task.listing;
|
|
172
|
-
const
|
|
173
|
-
const
|
|
174
|
-
for (const ownedAsset of
|
|
155
|
+
const sessionFiles = [];
|
|
156
|
+
const ownedAssets = await Promise.all(task.ownedAssets.map((ownedAsset) => prepareOwnedAssetUpload(ownedAsset)));
|
|
157
|
+
for (const ownedAsset of ownedAssets) {
|
|
175
158
|
for (const file of ownedAsset.files) {
|
|
176
159
|
totalUploadBytes = accumulateUploadBytes(task.name, totalUploadBytes, file.sizeBytes);
|
|
177
160
|
}
|
|
178
161
|
}
|
|
179
|
-
for (const definition of achievements) {
|
|
162
|
+
for (const definition of task.achievements ?? []) {
|
|
180
163
|
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);
|
|
181
164
|
totalUploadBytes = pushPreparedFile(task.name, totalUploadBytes, prepared);
|
|
182
|
-
|
|
165
|
+
sessionFiles.push(await buildPreparedSessionFile(`achievementIcon:${definition.key}`, prepared.file));
|
|
183
166
|
}
|
|
184
167
|
if (task.hostingMode === 'EXTERNAL') {
|
|
185
168
|
// no hosted files
|
|
@@ -187,48 +170,70 @@ async function uploadAppVersion(client, task, artifacts, options) {
|
|
|
187
170
|
else if (artifacts) {
|
|
188
171
|
totalUploadBytes = pushPreparedArtifact(task, totalUploadBytes, artifacts.bundleFile, config_1.MAX_VERSION_BUNDLE_BYTES, 'bundle_too_large', 'Bundle');
|
|
189
172
|
totalUploadBytes = pushPreparedArtifact(task, totalUploadBytes, artifacts.sourceFile, config_1.MAX_APP_SOURCE_BYTES, 'source_too_large', 'Source archive');
|
|
190
|
-
|
|
191
|
-
|
|
173
|
+
sessionFiles.push(await buildPreparedSessionFile('bundle', artifacts.bundleFile));
|
|
174
|
+
sessionFiles.push(await buildPreparedSessionFile('source', artifacts.sourceFile));
|
|
192
175
|
}
|
|
193
176
|
if (listing?.iconPath) {
|
|
194
177
|
const prepared = createFileFromPath(listing.iconPath, 'image/png', config_1.MAX_VERSION_ICON_BYTES, 'icon_too_large', 'invalid_icon_content_type', 'Icon', isPngSignature);
|
|
195
178
|
totalUploadBytes = pushPreparedFile(task.name, totalUploadBytes, prepared);
|
|
196
|
-
|
|
179
|
+
sessionFiles.push(await buildPreparedSessionFile('icon', prepared.file));
|
|
197
180
|
}
|
|
198
181
|
if (listing?.heroPortraitPath) {
|
|
199
182
|
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);
|
|
200
183
|
totalUploadBytes = pushPreparedFile(task.name, totalUploadBytes, prepared);
|
|
201
|
-
|
|
184
|
+
sessionFiles.push(await buildPreparedSessionFile('heroPortrait', prepared.file));
|
|
202
185
|
}
|
|
203
186
|
if (listing?.heroLandscapePath) {
|
|
204
187
|
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);
|
|
205
188
|
totalUploadBytes = pushPreparedFile(task.name, totalUploadBytes, prepared);
|
|
206
|
-
|
|
189
|
+
sessionFiles.push(await buildPreparedSessionFile('heroLandscape', prepared.file));
|
|
207
190
|
}
|
|
208
191
|
for (let index = 0; index < (listing?.screenshotPortraitPaths ?? []).length; index += 1) {
|
|
209
192
|
const filePath = listing.screenshotPortraitPaths[index];
|
|
210
193
|
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);
|
|
211
194
|
totalUploadBytes = pushPreparedFile(task.name, totalUploadBytes, prepared);
|
|
212
|
-
|
|
195
|
+
sessionFiles.push(await buildPreparedSessionFile(`screenshotsPortrait:${index}`, prepared.file));
|
|
213
196
|
}
|
|
214
197
|
for (let index = 0; index < (listing?.screenshotLandscapePaths ?? []).length; index += 1) {
|
|
215
198
|
const filePath = listing.screenshotLandscapePaths[index];
|
|
216
199
|
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);
|
|
217
200
|
totalUploadBytes = pushPreparedFile(task.name, totalUploadBytes, prepared);
|
|
218
|
-
|
|
201
|
+
sessionFiles.push(await buildPreparedSessionFile(`screenshotsLandscape:${index}`, prepared.file));
|
|
219
202
|
}
|
|
220
203
|
for (let index = 0; index < (listing?.videoPortraitPaths ?? []).length; index += 1) {
|
|
221
204
|
const filePath = listing.videoPortraitPaths[index];
|
|
222
205
|
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);
|
|
223
206
|
totalUploadBytes = pushPreparedFile(task.name, totalUploadBytes, prepared);
|
|
224
|
-
|
|
207
|
+
sessionFiles.push(await buildPreparedSessionFile(`videosPortrait:${index}`, prepared.file));
|
|
225
208
|
}
|
|
226
209
|
for (let index = 0; index < (listing?.videoLandscapePaths ?? []).length; index += 1) {
|
|
227
210
|
const filePath = listing.videoLandscapePaths[index];
|
|
228
211
|
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);
|
|
229
212
|
totalUploadBytes = pushPreparedFile(task.name, totalUploadBytes, prepared);
|
|
230
|
-
|
|
213
|
+
sessionFiles.push(await buildPreparedSessionFile(`videosLandscape:${index}`, prepared.file));
|
|
231
214
|
}
|
|
215
|
+
return { sessionFiles, ownedAssets };
|
|
216
|
+
}
|
|
217
|
+
async function uploadApp(client, task, artifacts, options) {
|
|
218
|
+
// Version is required - no legacy fallback
|
|
219
|
+
if (!task.version) {
|
|
220
|
+
throw new Error(`Missing "version" field in catalogue.json for app "${task.name}".\n` +
|
|
221
|
+
`Add a version field like "version": "1.0.0" to upload your app.`);
|
|
222
|
+
}
|
|
223
|
+
return uploadAppVersion(client, task, artifacts, options);
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Upload an app version using the versioned API.
|
|
227
|
+
*/
|
|
228
|
+
async function uploadAppVersion(client, task, artifacts, options) {
|
|
229
|
+
const creatorUsername = typeof options?.creatorUsername === 'string' ? options.creatorUsername.trim() : '';
|
|
230
|
+
if (!creatorUsername) {
|
|
231
|
+
throw new Error(`Missing creator username for app "${task.name}" upload.`);
|
|
232
|
+
}
|
|
233
|
+
const achievements = task.achievements ?? [];
|
|
234
|
+
const leaderboards = task.leaderboards ?? [];
|
|
235
|
+
const listing = task.listing;
|
|
236
|
+
const { sessionFiles: preparedSessionFiles, ownedAssets: preparedOwnedAssets, } = await prepareAppUploadFiles(task, artifacts);
|
|
232
237
|
const initializeRequest = {
|
|
233
238
|
version: task.version,
|
|
234
239
|
agentTaskId: options?.agentTaskId,
|
|
@@ -259,6 +264,7 @@ async function uploadAppVersion(client, task, artifacts, options) {
|
|
|
259
264
|
authMode: task.authMode,
|
|
260
265
|
controllerMode: task.controllerMode,
|
|
261
266
|
previewable: task.previewable,
|
|
267
|
+
editorSupported: task.editorSupported,
|
|
262
268
|
skipReview: options?.skipReview,
|
|
263
269
|
displayName: task.displayName,
|
|
264
270
|
subtitle: task.subtitle,
|
|
@@ -346,15 +352,6 @@ async function uploadAppVersion(client, task, artifacts, options) {
|
|
|
346
352
|
})),
|
|
347
353
|
});
|
|
348
354
|
}
|
|
349
|
-
if (options?.mediaCaptureRequired === true
|
|
350
|
-
&& task.hostingMode !== 'EXTERNAL'
|
|
351
|
-
&& !task.externalUrl) {
|
|
352
|
-
const mediaCaptureProof = (0, listingPreflight_1.readMediaCaptureProof)({
|
|
353
|
-
task,
|
|
354
|
-
preparedSessionFiles,
|
|
355
|
-
});
|
|
356
|
-
await client.recordAppUploadMediaCapture(creatorUsername, task.name, sessionId, mediaCaptureProof);
|
|
357
|
-
}
|
|
358
355
|
if (task.hostingMode !== 'EXTERNAL'
|
|
359
356
|
&& !task.externalUrl
|
|
360
357
|
&& options?.playtestSmokeCheckRequired) {
|
|
@@ -363,7 +360,7 @@ async function uploadAppVersion(client, task, artifacts, options) {
|
|
|
363
360
|
creatorUsername,
|
|
364
361
|
task,
|
|
365
362
|
sessionId,
|
|
366
|
-
timeoutMs: options
|
|
363
|
+
timeoutMs: options?.loadCheckTimeoutMs,
|
|
367
364
|
token: options?.token ?? null,
|
|
368
365
|
currentUser: options?.user ?? null,
|
|
369
366
|
captureSession: options?.captureSession,
|
|
@@ -377,7 +374,7 @@ async function uploadAppVersion(client, task, artifacts, options) {
|
|
|
377
374
|
creatorUsername,
|
|
378
375
|
task,
|
|
379
376
|
sessionId,
|
|
380
|
-
timeoutMs: options
|
|
377
|
+
timeoutMs: options?.loadCheckTimeoutMs,
|
|
381
378
|
token: options?.token ?? null,
|
|
382
379
|
currentUser: options?.user ?? null,
|
|
383
380
|
captureSession: options?.captureSession,
|
|
@@ -386,6 +383,23 @@ async function uploadAppVersion(client, task, artifacts, options) {
|
|
|
386
383
|
throw new Error((0, loadCheck_1.formatHostedLoadCheckFailure)(task.name, loadCheck, 'staged'));
|
|
387
384
|
}
|
|
388
385
|
}
|
|
386
|
+
if (task.editorSupported && task.hostingMode !== 'EXTERNAL' && !task.externalUrl) {
|
|
387
|
+
const editorLoadCheck = await (0, loadCheck_1.runUploadedHostedLoadCheck)({
|
|
388
|
+
client,
|
|
389
|
+
creatorUsername,
|
|
390
|
+
task,
|
|
391
|
+
sessionId,
|
|
392
|
+
timeoutMs: options?.loadCheckTimeoutMs,
|
|
393
|
+
token: options?.token ?? null,
|
|
394
|
+
currentUser: options?.user ?? null,
|
|
395
|
+
captureSession: options?.captureSession,
|
|
396
|
+
phase: 'editor',
|
|
397
|
+
verifyEditorPhaseRoundTrip: true,
|
|
398
|
+
});
|
|
399
|
+
if (editorLoadCheck.status !== 'PASSED') {
|
|
400
|
+
throw new Error((0, loadCheck_1.formatHostedLoadCheckFailure)(task.name, editorLoadCheck, 'editor'));
|
|
401
|
+
}
|
|
402
|
+
}
|
|
389
403
|
}
|
|
390
404
|
const finalized = initialized.status === 'finalized'
|
|
391
405
|
? initialized.finalized
|
package/dist/catalogue.d.ts
CHANGED
|
@@ -63,6 +63,7 @@ export type AppCatalogueEntry = {
|
|
|
63
63
|
authMode?: string;
|
|
64
64
|
controllerMode?: string;
|
|
65
65
|
previewable?: boolean;
|
|
66
|
+
editorSupported?: boolean;
|
|
66
67
|
externalUrl?: string;
|
|
67
68
|
listing?: AppListingConfig;
|
|
68
69
|
username?: string;
|
|
@@ -210,6 +211,7 @@ export type AppTask = {
|
|
|
210
211
|
authMode?: AppAuthMode;
|
|
211
212
|
controllerMode?: AppControllerMode;
|
|
212
213
|
previewable?: boolean;
|
|
214
|
+
editorSupported?: boolean;
|
|
213
215
|
externalUrl?: string;
|
|
214
216
|
listing?: ResolvedListingAssets;
|
|
215
217
|
username?: string;
|
|
@@ -368,6 +370,7 @@ export type ValidatedAppMetadata = {
|
|
|
368
370
|
surfaceTargetMap: SurfaceTargetsMap;
|
|
369
371
|
primarySurface?: AppSurface;
|
|
370
372
|
playtestTapes?: AppPlaytestTapes;
|
|
373
|
+
editorSupported?: boolean;
|
|
371
374
|
missingFields: string[];
|
|
372
375
|
warnings: string[];
|
|
373
376
|
errors: string[];
|
package/dist/catalogue.js
CHANGED
|
@@ -948,6 +948,18 @@ function validateAppMetadata(entry) {
|
|
|
948
948
|
warnings.push(`description is ${normalizedDescription.length} characters and ${wordCount} words. Aim for at least ${exports.APP_DESCRIPTION_RECOMMENDED_MIN_CHARACTERS} characters and ${exports.APP_DESCRIPTION_RECOMMENDED_MIN_WORDS} words describing what the player does, the objective, and the game's distinctive hook.`);
|
|
949
949
|
}
|
|
950
950
|
}
|
|
951
|
+
let editorSupported;
|
|
952
|
+
if (Object.prototype.hasOwnProperty.call(entry, 'editorSupported')) {
|
|
953
|
+
if (typeof entry.editorSupported !== 'boolean') {
|
|
954
|
+
errors.push('editorSupported must be true or false');
|
|
955
|
+
}
|
|
956
|
+
else {
|
|
957
|
+
editorSupported = entry.editorSupported;
|
|
958
|
+
if (editorSupported && entry.hostingMode === 'EXTERNAL') {
|
|
959
|
+
errors.push('editorSupported can only be true for HOSTED apps, not external apps');
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
}
|
|
951
963
|
let surfaceTargetsList = [];
|
|
952
964
|
let surfaceTargetsMap = { desktop: false, mobileLandscape: false, mobilePortrait: false };
|
|
953
965
|
try {
|
|
@@ -992,6 +1004,7 @@ function validateAppMetadata(entry) {
|
|
|
992
1004
|
surfaceTargetMap: surfaceTargetsMap,
|
|
993
1005
|
primarySurface,
|
|
994
1006
|
playtestTapes,
|
|
1007
|
+
editorSupported,
|
|
995
1008
|
missingFields,
|
|
996
1009
|
warnings,
|
|
997
1010
|
errors,
|
|
@@ -1204,6 +1217,14 @@ function buildAppTasks(rootDir, catalogues, options) {
|
|
|
1204
1217
|
}
|
|
1205
1218
|
previewable = Boolean(entry.previewable);
|
|
1206
1219
|
}
|
|
1220
|
+
let editorSupported;
|
|
1221
|
+
if (Object.prototype.hasOwnProperty.call(entry, 'editorSupported')) {
|
|
1222
|
+
if (typeof entry.editorSupported !== 'boolean') {
|
|
1223
|
+
errors.push(`[${label}] Skipping ${rawName}: editorSupported must be true or false.`);
|
|
1224
|
+
continue;
|
|
1225
|
+
}
|
|
1226
|
+
editorSupported = Boolean(entry.editorSupported);
|
|
1227
|
+
}
|
|
1207
1228
|
let externalUrl;
|
|
1208
1229
|
const rawExternalUrl = typeof entry.externalUrl === 'string' ? entry.externalUrl.trim() : '';
|
|
1209
1230
|
if (rawExternalUrl) {
|
|
@@ -1226,6 +1247,10 @@ function buildAppTasks(rootDir, catalogues, options) {
|
|
|
1226
1247
|
errors.push(`[${label}] Skipping ${rawName}: previewable can only be true for HOSTED apps.`);
|
|
1227
1248
|
continue;
|
|
1228
1249
|
}
|
|
1250
|
+
if ((hostingMode ?? 'HOSTED') === 'EXTERNAL' && editorSupported) {
|
|
1251
|
+
errors.push(`[${label}] Skipping ${rawName}: editorSupported can only be true for HOSTED apps.`);
|
|
1252
|
+
continue;
|
|
1253
|
+
}
|
|
1229
1254
|
const defaultLicense = (hostingMode ?? 'HOSTED') === 'EXTERNAL' ? 'CLOSED' : types_1.DEFAULT_CONTENT_LICENSE;
|
|
1230
1255
|
const license = normalizeContentLicenseValue(entry.license, errors, `${label} app "${rawName}"`, APP_ALLOWED_LICENSES, defaultLicense);
|
|
1231
1256
|
if (!license) {
|
|
@@ -1600,6 +1625,7 @@ function buildAppTasks(rootDir, catalogues, options) {
|
|
|
1600
1625
|
authMode,
|
|
1601
1626
|
controllerMode,
|
|
1602
1627
|
previewable,
|
|
1628
|
+
editorSupported,
|
|
1603
1629
|
externalUrl,
|
|
1604
1630
|
listing,
|
|
1605
1631
|
// Admin-only field
|