@playdrop/playdrop-cli 0.12.3 → 0.12.4

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.
Files changed (33) hide show
  1. package/config/client-meta.json +1 -1
  2. package/dist/appUrls.d.ts +9 -0
  3. package/dist/appUrls.js +29 -0
  4. package/dist/apps/index.d.ts +2 -0
  5. package/dist/apps/index.js +2 -0
  6. package/dist/apps/loadCheck.d.ts +4 -0
  7. package/dist/apps/loadCheck.js +48 -13
  8. package/dist/apps/upload.d.ts +2 -0
  9. package/dist/apps/upload.js +1 -0
  10. package/dist/commands/captureListing.d.ts +3 -1
  11. package/dist/commands/captureListing.js +24 -8
  12. package/dist/commands/check.js +40 -26
  13. package/dist/commands/create.js +47 -2
  14. package/dist/commands/devShared.d.ts +5 -0
  15. package/dist/commands/devShared.js +9 -0
  16. package/dist/commands/taskCaptureSession.d.ts +4 -0
  17. package/dist/commands/taskCaptureSession.js +21 -0
  18. package/dist/commands/upload.d.ts +1 -0
  19. package/dist/commands/upload.js +16 -1
  20. package/dist/commands/worker.d.ts +5 -1
  21. package/dist/commands/worker.js +187 -119
  22. package/node_modules/@playdrop/config/client-meta.json +1 -1
  23. package/node_modules/@playdrop/config/dist/tsconfig.tsbuildinfo +1 -1
  24. package/node_modules/@playdrop/types/dist/api.d.ts +9 -10
  25. package/node_modules/@playdrop/types/dist/api.d.ts.map +1 -1
  26. package/node_modules/@playdrop/types/dist/api.js +3 -60
  27. package/package.json +1 -1
  28. package/node_modules/@playdrop/api-client/dist/domains/game-ideas.d.ts +0 -46
  29. package/node_modules/@playdrop/api-client/dist/domains/game-ideas.d.ts.map +0 -1
  30. package/node_modules/@playdrop/api-client/dist/domains/game-ideas.js +0 -177
  31. package/node_modules/@playdrop/api-client/dist/domains/music.d.ts +0 -27
  32. package/node_modules/@playdrop/api-client/dist/domains/music.d.ts.map +0 -1
  33. package/node_modules/@playdrop/api-client/dist/domains/music.js +0 -96
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.12.2",
2
+ "version": "0.12.4",
3
3
  "build": 1,
4
4
  "runtimeSdkVersion": "0.10.0",
5
5
  "clients": {
package/dist/appUrls.d.ts CHANGED
@@ -4,6 +4,14 @@ export type AppUrlInput = {
4
4
  appName: string;
5
5
  appType?: AppType | string | null;
6
6
  };
7
+ export type TaskCaptureSession = {
8
+ appId: number;
9
+ taskId: number;
10
+ taskToken: string;
11
+ };
12
+ export declare function appendTaskCaptureSession(url: URL, session: TaskCaptureSession | null | undefined): void;
13
+ export declare function redactTaskCaptureSession(url: string): string;
14
+ export declare function redactTaskCaptureToken(text: string, sourceUrl: string): string;
7
15
  export declare function normalizePlaydropWebBase(webBase?: string | null): string;
8
16
  export declare function getAppTypeSlug(type: AppType | string | null | undefined): string;
9
17
  export declare function buildPlatformPlayUrl(webBase: string | null | undefined, input: AppUrlInput): string;
@@ -15,6 +23,7 @@ export declare function buildPlatformDevUrl(webBase: string | null | undefined,
15
23
  player?: string | null;
16
24
  launchCheck?: boolean;
17
25
  localDevPort?: number | string | null;
26
+ captureSession?: TaskCaptureSession | null;
18
27
  }): string;
19
28
  export declare function buildUploadLaunchCheckUrl(webBase: string | null | undefined, input: AppUrlInput & {
20
29
  sessionId: string;
package/dist/appUrls.js CHANGED
@@ -1,5 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.appendTaskCaptureSession = appendTaskCaptureSession;
4
+ exports.redactTaskCaptureSession = redactTaskCaptureSession;
5
+ exports.redactTaskCaptureToken = redactTaskCaptureToken;
3
6
  exports.normalizePlaydropWebBase = normalizePlaydropWebBase;
4
7
  exports.getAppTypeSlug = getAppTypeSlug;
5
8
  exports.buildPlatformPlayUrl = buildPlatformPlayUrl;
@@ -7,6 +10,31 @@ exports.buildPlatformPrivateDraftPlayUrl = buildPlatformPrivateDraftPlayUrl;
7
10
  exports.buildPlatformDevUrl = buildPlatformDevUrl;
8
11
  exports.buildUploadLaunchCheckUrl = buildUploadLaunchCheckUrl;
9
12
  const DEFAULT_WEB_BASE = 'https://www.playdrop.ai';
13
+ function appendTaskCaptureSession(url, session) {
14
+ if (!session) {
15
+ return;
16
+ }
17
+ if (!Number.isInteger(session.appId) || session.appId <= 0) {
18
+ throw new Error('invalid_capture_app_id');
19
+ }
20
+ if (!Number.isInteger(session.taskId) || session.taskId <= 0 || !session.taskToken.trim()) {
21
+ throw new Error('invalid_capture_task_session');
22
+ }
23
+ url.searchParams.set('captureAppId', String(session.appId));
24
+ url.searchParams.set('captureTaskId', String(session.taskId));
25
+ url.searchParams.set('captureTaskToken', session.taskToken.trim());
26
+ }
27
+ function redactTaskCaptureSession(url) {
28
+ const redacted = new URL(url);
29
+ if (redacted.searchParams.has('captureTaskToken')) {
30
+ redacted.searchParams.set('captureTaskToken', '<redacted>');
31
+ }
32
+ return redacted.toString();
33
+ }
34
+ function redactTaskCaptureToken(text, sourceUrl) {
35
+ const taskToken = new URL(sourceUrl).searchParams.get('captureTaskToken')?.trim() ?? '';
36
+ return taskToken ? text.split(taskToken).join('<redacted>') : text;
37
+ }
10
38
  function normalizePlaydropWebBase(webBase) {
11
39
  const base = typeof webBase === 'string' && webBase.trim().length > 0 ? webBase.trim() : DEFAULT_WEB_BASE;
12
40
  return base.replace(/\/$/, '');
@@ -69,6 +97,7 @@ function buildPlatformDevUrl(webBase, input) {
69
97
  }
70
98
  url.searchParams.set('localDevPort', String(port));
71
99
  }
100
+ appendTaskCaptureSession(url, input.captureSession);
72
101
  return url.toString();
73
102
  }
74
103
  function buildUploadLaunchCheckUrl(webBase, input) {
@@ -1,6 +1,7 @@
1
1
  import type { ApiClient } from '@playdrop/api-client';
2
2
  import type { UserResponse } from '@playdrop/types';
3
3
  import type { AppTask } from '../catalogue';
4
+ import type { TaskCaptureSession } from '../appUrls';
4
5
  import { collectAppValidationWarnings, validateAppTask, runFormatScript } from './validate';
5
6
  import { buildApp, type AppBuildArtifacts } from './build';
6
7
  import { uploadApp, type AppUploadResult } from './upload';
@@ -23,6 +24,7 @@ export type AppPipelineOptions = {
23
24
  ensureRegisteredAppShell?: boolean;
24
25
  agentTaskId?: number;
25
26
  mediaCaptureRequired?: boolean;
27
+ captureSession?: TaskCaptureSession | null;
26
28
  };
27
29
  export declare function runAppPipeline(client: ApiClient, task: AppTask, options?: AppPipelineOptions): Promise<AppPipelineResult>;
28
30
  export { collectAppValidationWarnings, validateAppTask, buildApp, uploadApp, runFormatScript };
@@ -49,6 +49,7 @@ async function runAppPipeline(client, task, options) {
49
49
  timeoutMs: options.loadCheckTimeoutMs,
50
50
  token: options.token ?? null,
51
51
  currentUser: options.user ?? null,
52
+ captureSession: options.captureSession,
52
53
  });
53
54
  if (loadCheck.status !== 'PASSED') {
54
55
  throw new Error((0, loadCheck_1.formatHostedLoadCheckFailure)(task.name, loadCheck, 'local'));
@@ -64,6 +65,7 @@ async function runAppPipeline(client, task, options) {
64
65
  loadCheckTimeoutMs: options?.loadCheckTimeoutMs,
65
66
  agentTaskId: options?.agentTaskId,
66
67
  mediaCaptureRequired: options?.mediaCaptureRequired,
68
+ captureSession: options?.captureSession,
67
69
  };
68
70
  const upload = await (0, upload_1.uploadApp)(client, task, artifacts, uploadOptions);
69
71
  return { artifacts, upload, warnings };
@@ -2,6 +2,7 @@ import type { ApiClient } from '@playdrop/api-client';
2
2
  import type { UserResponse } from '@playdrop/types';
3
3
  import type { Frame, Page } from 'playwright-core';
4
4
  import type { AppTask } from '../catalogue';
5
+ import { type TaskCaptureSession } from '../appUrls';
5
6
  export type HostedLoadCheckResult = {
6
7
  status: 'PASSED' | 'FAILED';
7
8
  errorCode: string | null;
@@ -50,6 +51,7 @@ export declare function focusProjectCheckGameFrame(page: Pick<Page, 'mouse'>, fr
50
51
  }>;
51
52
  export declare function dispatchProjectCheckActionsToFrame(page: Page, frame: Frame, actions: readonly ProjectCheckAction[]): Promise<void>;
52
53
  export declare function formatHostedLoadCheckFailure(taskName: string, result: HostedLoadCheckResult, scope?: 'local' | 'staged' | 'final'): string;
54
+ export declare function redactHostedLoadCheckSecrets(text: string, sourceUrl: string): string;
53
55
  export declare function runLocalHostedLoadCheck(input: {
54
56
  client: ApiClient;
55
57
  apiBase: string;
@@ -60,6 +62,7 @@ export declare function runLocalHostedLoadCheck(input: {
60
62
  timeoutMs?: number;
61
63
  token?: string | null;
62
64
  currentUser?: UserResponse | null;
65
+ captureSession?: TaskCaptureSession | null;
63
66
  allowUnregisteredViewerLaunch?: boolean;
64
67
  screenshotPath?: string | null;
65
68
  actions?: ProjectCheckAction[];
@@ -72,5 +75,6 @@ export declare function runUploadedHostedLoadCheck(input: {
72
75
  timeoutMs?: number;
73
76
  token?: string | null;
74
77
  currentUser?: UserResponse | null;
78
+ captureSession?: TaskCaptureSession | null;
75
79
  screenshotPath?: string | null;
76
80
  }): Promise<HostedLoadCheckResult>;
@@ -5,6 +5,7 @@ exports.normalizeProjectCheckActions = normalizeProjectCheckActions;
5
5
  exports.focusProjectCheckGameFrame = focusProjectCheckGameFrame;
6
6
  exports.dispatchProjectCheckActionsToFrame = dispatchProjectCheckActionsToFrame;
7
7
  exports.formatHostedLoadCheckFailure = formatHostedLoadCheckFailure;
8
+ exports.redactHostedLoadCheckSecrets = redactHostedLoadCheckSecrets;
8
9
  exports.runLocalHostedLoadCheck = runLocalHostedLoadCheck;
9
10
  exports.runUploadedHostedLoadCheck = runUploadedHostedLoadCheck;
10
11
  const promises_1 = require("node:fs/promises");
@@ -427,13 +428,18 @@ function formatHostedLoadCheckFailure(taskName, result, scope = 'local') {
427
428
  const message = normalizeLoadCheckMessage(result.message);
428
429
  return `hosted_load_check_failed: ${taskName} failed the ${scopeLabel} check (${errorCode}). ${message}`;
429
430
  }
431
+ function redactHostedLoadCheckSecrets(text, sourceUrl) {
432
+ return (0, appUrls_1.redactTaskCaptureToken)(text, sourceUrl).replace(/("(?:authToken|appAccessToken|captureTaskToken|reviewToken)"\s*:\s*")[^"]+(")/g, '$1<redacted>$2');
433
+ }
430
434
  async function runHostedLoadCheck(options) {
431
435
  const expectedHostedLaunchState = options.expectedHostedLaunchState ?? 'ready';
432
436
  const artifactFingerprint = options.artifactFingerprint ?? null;
433
437
  const timeoutMs = options.timeoutMs ?? DEFAULT_HOSTED_LOAD_TIMEOUT_MS;
434
438
  const errors = [];
435
439
  const warnings = [];
436
- let finalUrl = options.targetUrl;
440
+ let finalUrl = (0, appUrls_1.redactTaskCaptureSession)(options.targetUrl);
441
+ const reportTargetUrl = (0, appUrls_1.redactTaskCaptureSession)(options.targetUrl);
442
+ const redactReportText = (text) => redactHostedLoadCheckSecrets(text, options.targetUrl);
437
443
  let screenshotPath = null;
438
444
  let webglRenderer = null;
439
445
  try {
@@ -494,7 +500,7 @@ async function runHostedLoadCheck(options) {
494
500
  }
495
501
  }
496
502
  const serialized = serializePayload(payload);
497
- const line = ['[check][custom]', typeof type === 'string' ? type.toLowerCase() : 'info', serialized].filter(Boolean).join(' ');
503
+ const line = redactReportText(['[check][custom]', typeof type === 'string' ? type.toLowerCase() : 'info', serialized].filter(Boolean).join(' '));
498
504
  console.log(line);
499
505
  });
500
506
  await page.addInitScript(() => {
@@ -533,7 +539,7 @@ async function runHostedLoadCheck(options) {
533
539
  const type = message.type();
534
540
  const args = await Promise.all(message.args().map((arg) => arg.jsonValue().catch(() => arg.toString())));
535
541
  const rendered = args.length > 0 ? args.map(formatConsoleValue).join(' ') : message.text();
536
- const line = `[check][console:${type}] ${rendered}`;
542
+ const line = redactReportText(`[check][console:${type}] ${rendered}`);
537
543
  if (type === 'error' || type === 'assert' || type === 'trace') {
538
544
  errors.push(line);
539
545
  console.error(line);
@@ -543,19 +549,19 @@ async function runHostedLoadCheck(options) {
543
549
  };
544
550
  page.on('console', (message) => {
545
551
  void handleConsoleMessage(message).catch((error) => {
546
- const text = error instanceof Error ? error.message : String(error);
552
+ const text = redactReportText(error instanceof Error ? error.message : String(error));
547
553
  errors.push(`[check][console-handler] ${text}`);
548
554
  });
549
555
  });
550
556
  page.on('pageerror', (error) => {
551
- const text = error?.message ?? String(error);
557
+ const text = redactReportText(error?.message ?? String(error));
552
558
  errors.push(`[check][pageerror] ${text}`);
553
559
  console.error(`[check][pageerror] ${text}`);
554
560
  });
555
561
  page.on('requestfailed', (request) => {
556
562
  const failure = request.failure();
557
563
  const errorText = failure?.errorText ?? '';
558
- const text = `${request.method()} ${request.url()} - ${errorText || 'failed'}`;
564
+ const text = redactReportText(`${request.method()} ${request.url()} - ${errorText || 'failed'}`);
559
565
  if (isKnownGoogleTelemetryUrl(request.url())) {
560
566
  warnings.push(`[check][requestfailed] ${text}`);
561
567
  return;
@@ -571,7 +577,7 @@ async function runHostedLoadCheck(options) {
571
577
  if (status < 400) {
572
578
  return;
573
579
  }
574
- const text = `${status} ${response.statusText()} ${response.url()}`;
580
+ const text = redactReportText(`${status} ${response.statusText()} ${response.url()}`);
575
581
  if (isKnownGoogleTelemetryUrl(response.url())) {
576
582
  warnings.push(`[check][response] ${text}`);
577
583
  return;
@@ -579,10 +585,10 @@ async function runHostedLoadCheck(options) {
579
585
  errors.push(`[check][response] ${text}`);
580
586
  console.error(`[check][response] ${text}`);
581
587
  });
582
- console.log(`[check] Opening ${options.targetUrl}`);
588
+ console.log(`[check] Opening ${reportTargetUrl}`);
583
589
  const response = await page.goto(options.targetUrl, { waitUntil: 'domcontentloaded', timeout: 30000 });
584
590
  if (response && !response.ok()) {
585
- errors.push(`[check][navigation] ${response.status()} ${response.statusText()} ${options.targetUrl}`);
591
+ errors.push(`[check][navigation] ${response.status()} ${response.statusText()} ${reportTargetUrl}`);
586
592
  }
587
593
  const readinessTimeoutMs = Math.min(Math.max(timeoutMs, 1000), 30000);
588
594
  await page.waitForFunction(({ frameSelector }) => {
@@ -610,7 +616,7 @@ async function runHostedLoadCheck(options) {
610
616
  if (launchState.state !== expectedHostedLaunchState) {
611
617
  throw new Error(formatHostedLaunchStateMismatch(launchState.state, expectedHostedLaunchState));
612
618
  }
613
- finalUrl = page.url();
619
+ finalUrl = (0, appUrls_1.redactTaskCaptureSession)(page.url());
614
620
  if (expectedHostedLaunchState !== 'ready') {
615
621
  screenshotPath = await saveScreenshot(page, options.screenshotPath);
616
622
  return;
@@ -632,11 +638,11 @@ async function runHostedLoadCheck(options) {
632
638
  });
633
639
  }
634
640
  catch (error) {
635
- const launchFailureMessage = error instanceof Error ? error.message : String(error);
641
+ const launchFailureMessage = redactReportText(error instanceof Error ? error.message : String(error));
636
642
  if (/Failed to launch installed Google Chrome/i.test(launchFailureMessage)) {
637
643
  throw error;
638
644
  }
639
- const normalized = normalizeLoadCheckFailure(error, artifactFingerprint);
645
+ const normalized = normalizeLoadCheckFailure(new Error(launchFailureMessage), artifactFingerprint);
640
646
  return {
641
647
  ...normalized,
642
648
  screenshotPath,
@@ -733,6 +739,23 @@ async function runLocalHostedLoadCheck(input) {
733
739
  ...localAppMetadata,
734
740
  captureSurface,
735
741
  });
742
+ if (input.captureSession) {
743
+ return await runHostedLoadCheck({
744
+ targetUrl: (0, appUrls_1.buildPlatformDevUrl)(input.webBase, {
745
+ creatorUsername: input.creatorUsername,
746
+ appName: input.task.name,
747
+ appType: input.task.type ?? 'GAME',
748
+ launchCheck: true,
749
+ localDevPort,
750
+ captureSession: input.captureSession,
751
+ }),
752
+ timeoutMs: input.timeoutMs,
753
+ expectedHostedLaunchState: resolvePostAuthHostedLaunchState(localAppMetadata.controllerMode),
754
+ contextOptions,
755
+ screenshotPath: input.screenshotPath,
756
+ actions: input.actions,
757
+ });
758
+ }
736
759
  if (anonymousExpectedState === 'login_required') {
737
760
  const registeredApp = await (0, registration_1.fetchRegisteredAppShell)(input.client, input.creatorUsername, input.task.name);
738
761
  const canLaunchUnregisteredViewer = input.allowUnregisteredViewerLaunch === true
@@ -813,7 +836,19 @@ async function runUploadedHostedLoadCheck(input) {
813
836
  captureSurface,
814
837
  });
815
838
  let result;
816
- if (anonymousExpectedState === 'login_required') {
839
+ if (input.captureSession) {
840
+ const targetUrl = new URL(preview.launchUrl);
841
+ (0, appUrls_1.appendTaskCaptureSession)(targetUrl, input.captureSession);
842
+ result = await runHostedLoadCheck({
843
+ targetUrl: targetUrl.toString(),
844
+ artifactFingerprint: preview.session.manifestHash,
845
+ timeoutMs: input.timeoutMs,
846
+ expectedHostedLaunchState: resolvePostAuthHostedLaunchState(preview.localAppMetadata.controllerMode),
847
+ contextOptions,
848
+ screenshotPath: input.screenshotPath,
849
+ });
850
+ }
851
+ else if (anonymousExpectedState === 'login_required') {
817
852
  if (!preview.app?.id) {
818
853
  result = buildFailedLoadCheckResult('app_registration_required_for_auth_validation', `Auth-required hosted app "${input.task.name}" must be registered on PlayDrop before viewer load-check can run.`, preview.session.manifestHash);
819
854
  }
@@ -1,6 +1,7 @@
1
1
  import type { ApiClient } from '@playdrop/api-client';
2
2
  import type { UserResponse } from '@playdrop/types';
3
3
  import type { AppTask } from '../catalogue';
4
+ import type { TaskCaptureSession } from '../appUrls';
4
5
  import type { AppBuildArtifacts } from './build';
5
6
  export type AppUploadResult = {
6
7
  fileUnchanged?: boolean;
@@ -21,5 +22,6 @@ export type AppUploadOptions = {
21
22
  loadCheckTimeoutMs?: number;
22
23
  agentTaskId?: number;
23
24
  mediaCaptureRequired?: boolean;
25
+ captureSession?: TaskCaptureSession | null;
24
26
  };
25
27
  export declare function uploadApp(client: ApiClient, task: AppTask, artifacts: AppBuildArtifacts | null, options?: AppUploadOptions): Promise<AppUploadResult>;
@@ -454,6 +454,7 @@ async function uploadAppVersion(client, task, artifacts, options) {
454
454
  timeoutMs: options.loadCheckTimeoutMs,
455
455
  token: options?.token ?? null,
456
456
  currentUser: options?.user ?? null,
457
+ captureSession: options?.captureSession,
457
458
  });
458
459
  if (loadCheck.status !== 'PASSED') {
459
460
  throw new Error((0, loadCheck_1.formatHostedLoadCheckFailure)(task.name, loadCheck, 'staged'));
@@ -1,5 +1,6 @@
1
1
  import { type AppSurface } from '@playdrop/types';
2
2
  import type { BrowserContextOptions } from 'playwright-core';
3
+ import { type TaskCaptureSession } from '../appUrls';
3
4
  type CaptureListingOptions = {
4
5
  app?: string;
5
6
  duration?: string | number;
@@ -53,13 +54,14 @@ type CropRect = {
53
54
  export declare function resolveListingCaptureRouteSegment(previewable: boolean): 'dev-preview';
54
55
  export declare function resolveCaptureListingDevRouterPort(env?: NodeJS.ProcessEnv): number;
55
56
  export declare function resolveListingCaptureLocalDevPort(devRouterPort: number): number | null;
56
- export declare function buildListingCaptureFrameUrl({ webBase, currentUsername, appTypeSlug, appName, routeSegment, localDevPort, }: {
57
+ export declare function buildListingCaptureFrameUrl({ webBase, currentUsername, appTypeSlug, appName, routeSegment, localDevPort, captureSession, }: {
57
58
  webBase: string;
58
59
  currentUsername: string;
59
60
  appTypeSlug: string;
60
61
  appName: string;
61
62
  routeSegment: 'dev-preview';
62
63
  localDevPort: number | null;
64
+ captureSession?: TaskCaptureSession | null;
63
65
  }): string;
64
66
  export declare function resolveListingCaptureSceneId(width: number, height: number): 'listing-landscape' | 'listing-portrait';
65
67
  export declare function parseCaptureListingOptions(targetArg: string | undefined, options?: CaptureListingOptions): ParsedCaptureListingOptions;
@@ -62,6 +62,7 @@ const dev_1 = require("./dev");
62
62
  const devRuntimeAssets_1 = require("./devRuntimeAssets");
63
63
  const devServer_1 = require("./devServer");
64
64
  const devShared_1 = require("./devShared");
65
+ const taskCaptureSession_1 = require("./taskCaptureSession");
65
66
  const CAPTURE_FRAME_SELECTOR = 'iframe[title="Game"]';
66
67
  const DEFAULT_DURATION_SECONDS = 8;
67
68
  const DEFAULT_WIDTH = 1280;
@@ -100,15 +101,15 @@ function resolveCaptureListingDevRouterPort(env = process.env) {
100
101
  function resolveListingCaptureLocalDevPort(devRouterPort) {
101
102
  return devRouterPort === devServer_1.DEV_ROUTER_PORT ? null : devRouterPort;
102
103
  }
103
- function buildListingCaptureFrameUrl({ webBase, currentUsername, appTypeSlug, appName, routeSegment, localDevPort, }) {
104
+ function buildListingCaptureFrameUrl({ webBase, currentUsername, appTypeSlug, appName, routeSegment, localDevPort, captureSession, }) {
104
105
  const frameUrlObject = new URL(`${(0, appUrls_1.normalizePlaydropWebBase)(webBase)}/creators/${encodeURIComponent(currentUsername)}/apps/${appTypeSlug}/${encodeURIComponent(appName)}/${routeSegment}`);
105
- frameUrlObject.searchParams.set('devAuth', 'anonymous');
106
106
  frameUrlObject.searchParams.set('launchCheck', '1');
107
107
  frameUrlObject.searchParams.set('captureShell', 'listing');
108
108
  frameUrlObject.searchParams.set('mobileFrameHost', 'admin');
109
109
  if (localDevPort !== null) {
110
110
  frameUrlObject.searchParams.set('localDevPort', String(localDevPort));
111
111
  }
112
+ (0, appUrls_1.appendTaskCaptureSession)(frameUrlObject, captureSession);
112
113
  return frameUrlObject.toString();
113
114
  }
114
115
  function resolveListingCaptureSceneId(width, height) {
@@ -812,11 +813,14 @@ async function captureListing(targetArg, options = {}) {
812
813
  const outputPaths = await ensureOutputPaths(appName, parsedOptions.outputDir);
813
814
  const projectInfo = (0, devShared_1.findProjectInfo)(resolvedTarget.htmlPath);
814
815
  const devScriptAvailable = Boolean(projectInfo.projectDir && projectInfo.packageJson && typeof projectInfo.packageJson.scripts?.dev === 'string');
815
- await (0, commandContext_1.withEnvironment)('project capture', 'Capturing game media', async ({ client, env, envConfig }) => {
816
+ await (0, commandContext_1.withEnvironment)('project capture', 'Capturing game media', async ({ client, env, envConfig, account, workspaceAuth }) => {
816
817
  let currentUsername = '';
817
818
  try {
818
- const currentUser = await (0, devShared_1.fetchDevUser)(client);
819
- currentUsername = currentUser.username.trim();
819
+ currentUsername = await (0, devShared_1.resolveDevCreatorUsername)({
820
+ client,
821
+ workspaceOwnerUsername: workspaceAuth?.config.ownerUsername,
822
+ accountUsername: account?.username,
823
+ });
820
824
  }
821
825
  catch (error) {
822
826
  if (error instanceof http_1.CLIUnsupportedClientError) {
@@ -842,8 +846,9 @@ async function captureListing(targetArg, options = {}) {
842
846
  }
843
847
  throw error;
844
848
  }
849
+ let registeredApp;
845
850
  try {
846
- await (0, devShared_1.assertAppRegistered)(client, currentUsername, appName);
851
+ registeredApp = await (0, devShared_1.assertAppRegistered)(client, currentUsername, appName);
847
852
  }
848
853
  catch (error) {
849
854
  if (error instanceof http_1.CLIUnsupportedClientError) {
@@ -866,6 +871,15 @@ async function captureListing(targetArg, options = {}) {
866
871
  }
867
872
  throw error;
868
873
  }
874
+ let captureSession;
875
+ try {
876
+ captureSession = (0, taskCaptureSession_1.resolveTaskCaptureSession)(workspaceAuth, registeredApp);
877
+ }
878
+ catch (error) {
879
+ (0, messages_1.printErrorWithHelp)(error?.message || 'Worker capture session is invalid.', ['Run this command from the active worker task workspace.'], { command: 'project capture' });
880
+ process.exitCode = 1;
881
+ return;
882
+ }
869
883
  const entryLabel = (0, node_path_1.relative)(process.cwd(), resolvedTarget.htmlPath) || resolvedTarget.htmlPath;
870
884
  console.log(`[listing] Preparing ${entryLabel} for ${env} (${appTypeSlug}).`);
871
885
  const taskLookup = (0, catalogue_1.findAppTaskByFile)(resolvedTarget.htmlPath);
@@ -960,9 +974,11 @@ async function captureListing(targetArg, options = {}) {
960
974
  appName,
961
975
  routeSegment,
962
976
  localDevPort,
977
+ captureSession,
963
978
  });
964
979
  const frameUrlObject = new URL(frameUrl);
965
- console.log(`[listing] Opening ${frameUrl}`);
980
+ const reportFrameUrl = (0, appUrls_1.redactTaskCaptureSession)(frameUrl);
981
+ console.log(`[listing] Opening ${reportFrameUrl}`);
966
982
  const shouldExportPreviewAudio = parsedOptions.audio && routeSegment === 'dev-preview';
967
983
  const surfaces = resolvedTarget.surfaceTargets.list;
968
984
  const useSurfacePrefix = surfaces.length > 1;
@@ -1026,7 +1042,7 @@ async function captureListing(targetArg, options = {}) {
1026
1042
  capturedSurfaces.push(surface);
1027
1043
  captures.push({
1028
1044
  surface,
1029
- targetUrl: frameUrl,
1045
+ targetUrl: reportFrameUrl,
1030
1046
  command: {
1031
1047
  durationSeconds: parsedOptions.durationSeconds,
1032
1048
  width: dimensions.width,
@@ -11,6 +11,7 @@ const http_1 = require("../http");
11
11
  const messages_1 = require("../messages");
12
12
  const loadCheck_1 = require("../apps/loadCheck");
13
13
  const devShared_1 = require("./devShared");
14
+ const taskCaptureSession_1 = require("./taskCaptureSession");
14
15
  function resolveProjectCheckTarget(targetArg, appOption) {
15
16
  try {
16
17
  return (0, devShared_1.resolveDevTarget)(targetArg, appOption);
@@ -99,36 +100,39 @@ async function check(targetArg, options = {}) {
99
100
  }
100
101
  const screenshotPath = options.screenshot?.trim() || defaultProjectCheckScreenshotPath(appName);
101
102
  const workspacePath = resolvedTarget.cataloguePath ?? (0, node_path_1.dirname)(filePath);
102
- await (0, commandContext_1.withEnvironment)('project check', 'Checking the Playdrop hosted game shell', async ({ client, env, envConfig, account, token }) => {
103
- let currentUsername = account?.username?.trim() ?? '';
104
- if (!currentUsername) {
105
- try {
106
- currentUsername = await (0, devShared_1.fetchDevUsername)(client);
103
+ await (0, commandContext_1.withEnvironment)('project check', 'Checking the Playdrop hosted game shell', async ({ client, env, envConfig, account, token, workspaceAuth }) => {
104
+ let currentUsername = '';
105
+ try {
106
+ currentUsername = await (0, devShared_1.resolveDevCreatorUsername)({
107
+ client,
108
+ workspaceOwnerUsername: workspaceAuth?.config.ownerUsername,
109
+ accountUsername: account?.username,
110
+ });
111
+ }
112
+ catch (error) {
113
+ if (error instanceof http_1.CLIUnsupportedClientError) {
114
+ return;
115
+ }
116
+ if (error instanceof types_1.UnsupportedClientError) {
117
+ (0, http_1.handleUnsupportedError)(error, 'Authentication');
118
+ process.exitCode = 1;
119
+ return;
120
+ }
121
+ if (error instanceof types_1.ApiError) {
122
+ (0, messages_1.printErrorWithHelp)(`Could not fetch your account (status ${error.status}).`, ['Run "playdrop auth login" to refresh your session.'], { command: 'project check' });
123
+ process.exitCode = 1;
124
+ return;
107
125
  }
108
- catch (error) {
109
- if (error instanceof http_1.CLIUnsupportedClientError) {
110
- return;
111
- }
112
- if (error instanceof types_1.UnsupportedClientError) {
113
- (0, http_1.handleUnsupportedError)(error, 'Authentication');
114
- process.exitCode = 1;
115
- return;
116
- }
117
- if (error instanceof types_1.ApiError) {
118
- (0, messages_1.printErrorWithHelp)(`Could not fetch your account (status ${error.status}).`, ['Run "playdrop auth login" to refresh your session.'], { command: 'project check' });
119
- process.exitCode = 1;
120
- return;
121
- }
122
- if ((0, devShared_1.isNetworkError)(error)) {
123
- (0, messages_1.printNetworkIssue)('Could not reach the Playdrop API to resolve your account.', 'project check');
124
- process.exitCode = 1;
125
- return;
126
- }
127
- throw error;
126
+ if ((0, devShared_1.isNetworkError)(error)) {
127
+ (0, messages_1.printNetworkIssue)('Could not reach the Playdrop API to resolve your account.', 'project check');
128
+ process.exitCode = 1;
129
+ return;
128
130
  }
131
+ throw error;
129
132
  }
133
+ let registeredApp;
130
134
  try {
131
- await (0, devShared_1.assertAppRegistered)(client, currentUsername, appName);
135
+ registeredApp = await (0, devShared_1.assertAppRegistered)(client, currentUsername, appName);
132
136
  }
133
137
  catch (error) {
134
138
  if (error instanceof http_1.CLIUnsupportedClientError) {
@@ -151,6 +155,15 @@ async function check(targetArg, options = {}) {
151
155
  }
152
156
  throw error;
153
157
  }
158
+ let captureSession;
159
+ try {
160
+ captureSession = (0, taskCaptureSession_1.resolveTaskCaptureSession)(workspaceAuth, registeredApp);
161
+ }
162
+ catch (error) {
163
+ (0, messages_1.printErrorWithHelp)(error?.message || 'Worker capture session is invalid.', ['Run this command from the active worker task workspace.'], { command: 'project check' });
164
+ process.exitCode = 1;
165
+ return;
166
+ }
154
167
  const projectInfo = (0, devShared_1.findProjectInfo)(filePath);
155
168
  const devScriptAvailable = Boolean(projectInfo.projectDir
156
169
  && projectInfo.packageJson
@@ -172,6 +185,7 @@ async function check(targetArg, options = {}) {
172
185
  timeoutMs,
173
186
  token,
174
187
  currentUser: null,
188
+ captureSession,
175
189
  screenshotPath,
176
190
  actions,
177
191
  });
@@ -19,7 +19,10 @@ const clientInfo_1 = require("../clientInfo");
19
19
  const commandContext_1 = require("../commandContext");
20
20
  const http_1 = require("../http");
21
21
  const messages_1 = require("../messages");
22
+ const loadCheck_1 = require("../apps/loadCheck");
23
+ const devShared_1 = require("./devShared");
22
24
  const init_1 = require("./init");
25
+ const taskCaptureSession_1 = require("./taskCaptureSession");
23
26
  const CATALOGUE_FILENAME = 'catalogue.json';
24
27
  const LEGACY_CATALOGUE_VERSION_KEY = ['schema', 'Version'].join('');
25
28
  const ALLOWED_CATALOGUE_TOP_LEVEL_KEYS = new Set(['apps', 'assetSpecs', 'assets', 'assetPacks']);
@@ -1420,6 +1423,19 @@ async function create(name, options = {}) {
1420
1423
  }
1421
1424
  registrationTask = selectedTask;
1422
1425
  }
1426
+ let dependenciesInstalled = false;
1427
+ if (!reuseExistingApp
1428
+ && projectDir
1429
+ && packageJsonPath
1430
+ && (ctx.workspaceAuth?.config.taskId || ctx.workspaceAuth?.config.taskToken)) {
1431
+ console.log(`Installing dependencies for worker shell preflight in ${(0, node_path_1.relative)(process.cwd(), projectDir) || projectDir}.`);
1432
+ dependenciesInstalled = runNpmInstall(projectDir);
1433
+ if (!dependenciesInstalled) {
1434
+ (0, messages_1.printErrorWithHelp)('Worker project dependency installation failed.', ['Fix the template dependencies before retrying the task.'], { command: 'project create app' });
1435
+ process.exitCode = 1;
1436
+ return;
1437
+ }
1438
+ }
1423
1439
  if (registrationTask) {
1424
1440
  const metadataWarning = (0, catalogue_1.formatMissingMetadataWarnings)(registrationTask);
1425
1441
  if (metadataWarning) {
@@ -1542,12 +1558,41 @@ async function create(name, options = {}) {
1542
1558
  throw error;
1543
1559
  }
1544
1560
  }
1561
+ if (ctx.workspaceAuth?.config.taskId || ctx.workspaceAuth?.config.taskToken) {
1562
+ try {
1563
+ const registeredApp = await (0, devShared_1.assertAppRegistered)(client, creatorUsername, name);
1564
+ const captureSession = (0, taskCaptureSession_1.resolveTaskCaptureSession)(ctx.workspaceAuth, registeredApp);
1565
+ if (!captureSession) {
1566
+ throw new Error('worker_capture_session_missing');
1567
+ }
1568
+ console.log(`Running worker shell preflight for ${creatorUsername}/${name}.`);
1569
+ const preflight = await (0, loadCheck_1.runLocalHostedLoadCheck)({
1570
+ client,
1571
+ apiBase: ctx.envConfig.apiBase,
1572
+ webBase: ctx.envConfig.webBase,
1573
+ creatorUsername,
1574
+ task: registrationTask,
1575
+ captureSession,
1576
+ timeoutMs: 15000,
1577
+ });
1578
+ if (preflight.status !== 'PASSED') {
1579
+ (0, messages_1.printErrorWithHelp)((0, loadCheck_1.formatHostedLoadCheckFailure)(name, preflight, 'local'), ['Fix the worker shell or capture authorization before implementing the game.'], { command: 'project create app' });
1580
+ process.exitCode = 1;
1581
+ return;
1582
+ }
1583
+ console.log(`Worker shell preflight passed for ${creatorUsername}/${name}.`);
1584
+ }
1585
+ catch (error) {
1586
+ (0, messages_1.printErrorWithHelp)(`Worker shell preflight failed: ${error?.message || 'unknown_error'}`, ['Fix the worker shell or capture authorization before implementing the game.'], { command: 'project create app' });
1587
+ process.exitCode = 1;
1588
+ return;
1589
+ }
1590
+ }
1545
1591
  }
1546
1592
  const projectDirLabel = projectDir ? ((0, node_path_1.relative)(process.cwd(), projectDir) || projectDir) : null;
1547
1593
  const entryPointLabel = (0, node_path_1.relative)(process.cwd(), htmlFilePath) || (0, node_path_1.basename)(htmlFilePath);
1548
1594
  const hasPackageJson = Boolean(packageJsonPath);
1549
- let dependenciesInstalled = false;
1550
- if (!reuseExistingApp && projectDir && packageJsonPath && node_process_1.stdin.isTTY && node_process_1.stdout.isTTY) {
1595
+ if (!dependenciesInstalled && !reuseExistingApp && projectDir && packageJsonPath && node_process_1.stdin.isTTY && node_process_1.stdout.isTTY) {
1551
1596
  const accepted = await promptInstallDependencies(projectDir);
1552
1597
  if (accepted) {
1553
1598
  const success = runNpmInstall(projectDir);
@@ -16,6 +16,11 @@ export declare function createDevError(code: string, message: string): Error;
16
16
  export declare function isNetworkError(error: unknown): boolean;
17
17
  export declare function fetchDevUser(client: ApiClient): Promise<UserResponse>;
18
18
  export declare function fetchDevUsername(client: ApiClient): Promise<string>;
19
+ export declare function resolveDevCreatorUsername(input: {
20
+ client: ApiClient;
21
+ workspaceOwnerUsername?: string | null;
22
+ accountUsername?: string | null;
23
+ }): Promise<string>;
19
24
  export declare function assertAppRegistered(client: ApiClient, creatorUsername: string, appName: string): Promise<AppResponse>;
20
25
  export declare function resolveDevTarget(targetArg: string | undefined, appOption: string | undefined): ResolvedDevTarget;
21
26
  export declare function findProjectInfo(htmlPath: string): ProjectInfo;
@@ -4,6 +4,7 @@ exports.createDevError = createDevError;
4
4
  exports.isNetworkError = isNetworkError;
5
5
  exports.fetchDevUser = fetchDevUser;
6
6
  exports.fetchDevUsername = fetchDevUsername;
7
+ exports.resolveDevCreatorUsername = resolveDevCreatorUsername;
7
8
  exports.assertAppRegistered = assertAppRegistered;
8
9
  exports.resolveDevTarget = resolveDevTarget;
9
10
  exports.findProjectInfo = findProjectInfo;
@@ -54,6 +55,14 @@ async function fetchDevUsername(client) {
54
55
  const user = await fetchDevUser(client);
55
56
  return user.username;
56
57
  }
58
+ async function resolveDevCreatorUsername(input) {
59
+ const workspaceOwnerUsername = input.workspaceOwnerUsername?.trim() ?? '';
60
+ if (workspaceOwnerUsername) {
61
+ return workspaceOwnerUsername;
62
+ }
63
+ const accountUsername = input.accountUsername?.trim() ?? '';
64
+ return accountUsername || await fetchDevUsername(input.client);
65
+ }
57
66
  async function assertAppRegistered(client, creatorUsername, appName) {
58
67
  const response = await client.fetchAppBySlug(creatorUsername, appName);
59
68
  if (!response?.app) {