@playdrop/playdrop-cli 0.4.0 → 0.4.1

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 (56) hide show
  1. package/config/client-meta.json +7 -7
  2. package/dist/appUrls.d.ts +9 -0
  3. package/dist/appUrls.js +33 -0
  4. package/dist/browser.d.ts +5 -0
  5. package/dist/browser.js +45 -0
  6. package/dist/captureRuntime.d.ts +31 -0
  7. package/dist/captureRuntime.js +414 -0
  8. package/dist/commands/browse.js +1 -1
  9. package/dist/commands/capture.js +182 -479
  10. package/dist/commands/captureRemote.d.ts +2 -1
  11. package/dist/commands/captureRemote.js +144 -47
  12. package/dist/commands/comments.js +3 -3
  13. package/dist/commands/create.js +6 -6
  14. package/dist/commands/createRemixContent.js +1 -1
  15. package/dist/commands/creations.js +2 -2
  16. package/dist/commands/credits.js +2 -2
  17. package/dist/commands/detail.js +9 -4
  18. package/dist/commands/dev.js +2 -2
  19. package/dist/commands/feedback.js +26 -11
  20. package/dist/commands/generation.js +2 -2
  21. package/dist/commands/gettingStarted.js +1 -1
  22. package/dist/commands/init.js +1 -1
  23. package/dist/commands/login.d.ts +2 -4
  24. package/dist/commands/login.js +16 -51
  25. package/dist/commands/logout.js +1 -1
  26. package/dist/commands/notifications.js +3 -3
  27. package/dist/commands/play.d.ts +5 -0
  28. package/dist/commands/play.js +102 -0
  29. package/dist/commands/upload.js +10 -11
  30. package/dist/commands/versionsBrowse.js +11 -3
  31. package/dist/commands/whoami.js +3 -3
  32. package/dist/index.js +15 -3
  33. package/dist/messages.js +5 -5
  34. package/dist/playwright.d.ts +2 -1
  35. package/dist/playwright.js +18 -1
  36. package/dist/uploadLog.d.ts +2 -0
  37. package/dist/uploadLog.js +14 -0
  38. package/node_modules/@playdrop/ai-client/dist/index.d.ts.map +1 -1
  39. package/node_modules/@playdrop/ai-client/dist/index.js +136 -3
  40. package/node_modules/@playdrop/api-client/dist/client.d.ts +9 -1
  41. package/node_modules/@playdrop/api-client/dist/client.d.ts.map +1 -1
  42. package/node_modules/@playdrop/api-client/dist/client.js +10 -1
  43. package/node_modules/@playdrop/api-client/dist/domains/auth.d.ts +3 -1
  44. package/node_modules/@playdrop/api-client/dist/domains/auth.d.ts.map +1 -1
  45. package/node_modules/@playdrop/api-client/dist/domains/auth.js +21 -0
  46. package/node_modules/@playdrop/api-client/dist/domains/free-credits.d.ts +27 -0
  47. package/node_modules/@playdrop/api-client/dist/domains/free-credits.d.ts.map +1 -0
  48. package/node_modules/@playdrop/api-client/dist/domains/free-credits.js +66 -0
  49. package/node_modules/@playdrop/api-client/dist/index.d.ts +10 -2
  50. package/node_modules/@playdrop/api-client/dist/index.d.ts.map +1 -1
  51. package/node_modules/@playdrop/api-client/dist/index.js +31 -2
  52. package/node_modules/@playdrop/config/client-meta.json +7 -7
  53. package/node_modules/@playdrop/types/dist/api.d.ts +85 -1
  54. package/node_modules/@playdrop/types/dist/api.d.ts.map +1 -1
  55. package/node_modules/@playdrop/types/dist/api.js +15 -0
  56. package/package.json +2 -2
@@ -2,82 +2,16 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.capture = capture;
4
4
  const node_fs_1 = require("node:fs");
5
- const promises_1 = require("node:fs/promises");
6
5
  const node_path_1 = require("node:path");
7
6
  const types_1 = require("@playdrop/types");
8
- const config_1 = require("../config");
9
- const apiClient_1 = require("../apiClient");
10
7
  const http_1 = require("../http");
11
- const environment_1 = require("../environment");
12
8
  const messages_1 = require("../messages");
13
9
  const catalogue_utils_1 = require("../catalogue-utils");
14
10
  const devShared_1 = require("./devShared");
15
11
  const devServer_1 = require("./devServer");
16
- const playwright_1 = require("../playwright");
17
- const MAX_CAPTURE_TIMEOUT_SECONDS = 600;
18
- function validateTimeout(value) {
19
- if (value === undefined || Number.isNaN(value)) {
20
- return 5;
21
- }
22
- if (!Number.isFinite(value) || value <= 0) {
23
- throw new Error('Timeout must be a positive number of seconds.');
24
- }
25
- if (value > MAX_CAPTURE_TIMEOUT_SECONDS) {
26
- throw new Error(`Timeout cannot exceed ${MAX_CAPTURE_TIMEOUT_SECONDS} seconds (10 minutes).`);
27
- }
28
- return value;
29
- }
30
- function formatAppTypeSlug(type) {
31
- switch (type) {
32
- case 'TOOL':
33
- return 'tool';
34
- case 'TEMPLATE':
35
- return 'template';
36
- case 'DEMO':
37
- return 'demo';
38
- case undefined:
39
- return 'game';
40
- default:
41
- return 'game';
42
- }
43
- }
44
- function formatConsoleValue(value) {
45
- if (typeof value === 'string')
46
- return value;
47
- if (typeof value === 'number' || typeof value === 'boolean' || value === null) {
48
- return String(value);
49
- }
50
- if (typeof value === 'undefined') {
51
- return 'undefined';
52
- }
53
- if (typeof value === 'function') {
54
- return '[function]';
55
- }
56
- if (typeof value === 'object') {
57
- try {
58
- return JSON.stringify(value);
59
- }
60
- catch {
61
- return '[object]';
62
- }
63
- }
64
- return String(value);
65
- }
66
- function serializePayload(payload) {
67
- if (payload === undefined) {
68
- return '';
69
- }
70
- if (typeof payload === 'string') {
71
- return payload;
72
- }
73
- try {
74
- return JSON.stringify(payload);
75
- }
76
- catch {
77
- return String(payload);
78
- }
79
- }
80
- const LOG_LEVEL_VALUES = ['debug', 'info', 'warn', 'error'];
12
+ const commandContext_1 = require("../commandContext");
13
+ const appUrls_1 = require("../appUrls");
14
+ const captureRuntime_1 = require("../captureRuntime");
81
15
  const MOBILE_USER_AGENT = 'Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Mobile/15E148 Safari/604.1';
82
16
  const SURFACE_PRESETS = {
83
17
  desktop: {
@@ -111,42 +45,6 @@ const SURFACE_PRESETS = {
111
45
  },
112
46
  };
113
47
  const SURFACE_ORDER = ['desktop', 'mobileLandscape', 'mobilePortrait'];
114
- function resolveLogLevel(value) {
115
- if (!value)
116
- return 'info';
117
- const normalized = value.trim().toLowerCase();
118
- if (LOG_LEVEL_VALUES.includes(normalized)) {
119
- return normalized;
120
- }
121
- throw new Error(`Unsupported log level "${value}". Choose one of: ${LOG_LEVEL_VALUES.join(', ')}`);
122
- }
123
- function severityOrder(level) {
124
- switch (level) {
125
- case 'debug':
126
- return 0;
127
- case 'info':
128
- return 1;
129
- case 'warn':
130
- return 2;
131
- case 'error':
132
- return 3;
133
- default:
134
- return 1;
135
- }
136
- }
137
- function mapConsoleTypeToLevel(type) {
138
- const normalized = type.toLowerCase();
139
- if (normalized === 'debug')
140
- return 'debug';
141
- if (normalized === 'warning' || normalized === 'warn')
142
- return 'warn';
143
- if (normalized === 'error' || normalized === 'assert' || normalized === 'trace')
144
- return 'error';
145
- return 'info';
146
- }
147
- function shouldEmit(level, threshold) {
148
- return severityOrder(level) >= severityOrder(threshold);
149
- }
150
48
  function normalizeSurfaceOverride(value) {
151
49
  if (!value)
152
50
  return null;
@@ -187,43 +85,38 @@ function formatSurfaceList(targets) {
187
85
  }
188
86
  return enabled.join(', ');
189
87
  }
88
+ function resolveCaptureLoginOverride(options) {
89
+ const username = options.username?.trim() || '';
90
+ const password = options.password || '';
91
+ if ((username && !password) || (!username && password)) {
92
+ throw new Error('invalid_credentials_pair');
93
+ }
94
+ if (!username) {
95
+ return null;
96
+ }
97
+ return { username, password };
98
+ }
99
+ // eslint-disable-next-line max-lines-per-function
190
100
  async function capture(targetArg, options = {}) {
191
- const timeoutSeconds = validateTimeout(options.timeoutSeconds);
101
+ const timeoutSeconds = (0, captureRuntime_1.validateCaptureTimeout)(options.timeoutSeconds);
192
102
  const timeoutMs = Math.round(timeoutSeconds * 1000);
193
103
  let minimumLogLevel;
104
+ let loginOverride;
194
105
  try {
195
- minimumLogLevel = resolveLogLevel(options.logLevel);
106
+ minimumLogLevel = (0, captureRuntime_1.resolveCaptureLogLevel)(options.logLevel);
107
+ loginOverride = resolveCaptureLoginOverride(options);
196
108
  }
197
109
  catch (error) {
198
- (0, messages_1.printErrorWithHelp)(error?.message || 'Invalid log level.', [`Valid values: ${LOG_LEVEL_VALUES.join(', ')}`], { command: 'project capture' });
199
- process.exitCode = 1;
200
- return;
201
- }
202
- const screenshotPath = options.screenshotPath
203
- ? (0, node_path_1.resolve)(process.cwd(), options.screenshotPath)
204
- : null;
205
- const cfg = (0, config_1.loadConfig)();
206
- if (!cfg.env) {
207
- (0, messages_1.printConfigEnvironmentMissing)('project capture');
208
- process.exitCode = 1;
209
- return;
210
- }
211
- if (!cfg.token) {
212
- (0, messages_1.printLoginRequired)('Capturing app logs', 'project capture');
213
- process.exitCode = 1;
214
- return;
215
- }
216
- const envConfig = (0, environment_1.resolveEnvironmentConfig)(cfg.env);
217
- if (!envConfig) {
218
- const choices = (0, environment_1.formatEnvironmentList)();
219
- (0, messages_1.printUnknownEnvironment)(cfg.env, choices, 'project capture');
110
+ if (error instanceof Error && error.message === 'invalid_credentials_pair') {
111
+ (0, messages_1.printErrorWithHelp)('Use --username and --password together.', [], { command: 'project capture' });
112
+ }
113
+ else {
114
+ (0, messages_1.printErrorWithHelp)(error?.message || 'Invalid log level.', ['Valid values: debug, info, warn, error'], { command: 'project capture' });
115
+ }
220
116
  process.exitCode = 1;
221
117
  return;
222
118
  }
223
- if (envConfig.allowInsecureRequests) {
224
- process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
225
- }
226
- const client = (0, apiClient_1.createCliApiClient)({ baseUrl: envConfig.apiBase, token: cfg.token });
119
+ const screenshotPath = options.screenshotPath ? (0, node_path_1.resolve)(process.cwd(), options.screenshotPath) : null;
227
120
  const portToUse = 8888;
228
121
  let resolvedTarget;
229
122
  try {
@@ -293,11 +186,11 @@ async function capture(targetArg, options = {}) {
293
186
  ? ` (${surfaceContextOptions.viewport.width}x${surfaceContextOptions.viewport.height})`
294
187
  : '';
295
188
  console.log(`[capture] Surface: ${surfacePreset.label}${viewportLabel}`);
296
- let appTypeSlug = 'game';
189
+ let appTypeSlug = (0, appUrls_1.getAppTypeSlug)(null);
297
190
  try {
298
191
  const match = (0, catalogue_utils_1.findAppDefinition)(filePath);
299
192
  appName = match.name;
300
- appTypeSlug = formatAppTypeSlug(match.type);
193
+ appTypeSlug = (0, appUrls_1.getAppTypeSlug)(match.type);
301
194
  }
302
195
  catch (error) {
303
196
  if (resolvedTarget.cataloguePath) {
@@ -309,374 +202,184 @@ async function capture(targetArg, options = {}) {
309
202
  return;
310
203
  }
311
204
  }
312
- let currentUser = null;
313
- let currentUsername = '';
314
- try {
315
- currentUser = await (0, devShared_1.fetchDevUser)(client);
316
- currentUsername = currentUser.username.trim();
317
- }
318
- catch (error) {
319
- if (error instanceof http_1.CLIUnsupportedClientError) {
320
- return;
321
- }
322
- if (error instanceof types_1.UnsupportedClientError) {
323
- (0, http_1.handleUnsupportedError)(error, 'Authentication');
324
- process.exitCode = 1;
325
- return;
326
- }
327
- if (error instanceof types_1.ApiError) {
328
- (0, messages_1.printErrorWithHelp)(`Could not fetch your account (status ${error.status}).`, [
329
- 'Run "playdrop login" to refresh your session and ensure the API is reachable.',
330
- 'Use "playdrop whoami" afterwards to confirm your status.',
331
- ], { command: 'project capture' });
332
- process.exitCode = 1;
333
- return;
334
- }
335
- if ((0, devShared_1.isNetworkError)(error)) {
336
- (0, messages_1.printNetworkIssue)('Could not reach the Playdrop API to resolve your account.', 'project capture');
337
- process.exitCode = 1;
338
- return;
339
- }
340
- if (error instanceof Error && error.message === 'DEV_USERNAME_EMPTY') {
341
- (0, messages_1.printErrorWithHelp)('Could not determine your Playdrop creator username.', [
342
- 'Retry "playdrop project capture" in a moment.',
343
- 'If the problem persists, contact the Playdrop team.'
344
- ], { command: 'project capture' });
345
- process.exitCode = 1;
346
- return;
347
- }
348
- throw error;
349
- }
350
- try {
351
- await (0, devShared_1.assertAppRegistered)(client, currentUsername, appName);
352
- }
353
- catch (error) {
354
- if (error instanceof http_1.CLIUnsupportedClientError) {
355
- return;
356
- }
357
- if (error instanceof types_1.UnsupportedClientError) {
358
- (0, http_1.handleUnsupportedError)(error, 'Capture');
359
- process.exitCode = 1;
360
- return;
205
+ const projectInfo = (0, devShared_1.findProjectInfo)(filePath);
206
+ const devScriptAvailable = Boolean(projectInfo.projectDir && projectInfo.packageJson && typeof projectInfo.packageJson.scripts?.dev === 'string');
207
+ await (0, commandContext_1.withEnvironment)('project capture', 'Capturing app logs', async ({ client, env, envConfig, token }) => {
208
+ let currentUser = null;
209
+ let currentUsername = '';
210
+ try {
211
+ currentUser = await (0, devShared_1.fetchDevUser)(client);
212
+ currentUsername = currentUser.username.trim();
361
213
  }
362
- if (error instanceof types_1.ApiError) {
363
- if (error.status === 404) {
364
- (0, messages_1.printErrorWithHelp)(`App ${currentUsername}/${appName} is not registered on ${cfg.env}.`, [
365
- `Run "playdrop project create app ${appName}" to register the app before running capture.`,
366
- 'If you expected it to exist, ensure you are logged into the correct environment.'
367
- ], { command: 'project capture' });
214
+ catch (error) {
215
+ if (error instanceof http_1.CLIUnsupportedClientError) {
216
+ return;
368
217
  }
369
- else {
370
- (0, messages_1.printErrorWithHelp)(`Failed to verify app registration (status ${error.status}).`, [
371
- 'Retry in a moment.',
372
- 'If the issue persists, contact the Playdrop team.'
218
+ if (error instanceof types_1.UnsupportedClientError) {
219
+ (0, http_1.handleUnsupportedError)(error, 'Authentication');
220
+ process.exitCode = 1;
221
+ return;
222
+ }
223
+ if (error instanceof types_1.ApiError) {
224
+ (0, messages_1.printErrorWithHelp)(`Could not fetch your account (status ${error.status}).`, [
225
+ 'Run "playdrop auth login" to refresh your session and ensure the API is reachable.',
226
+ 'Use "playdrop auth whoami" afterwards to confirm your status.',
373
227
  ], { command: 'project capture' });
228
+ process.exitCode = 1;
229
+ return;
374
230
  }
375
- process.exitCode = 1;
376
- return;
377
- }
378
- if ((0, devShared_1.isNetworkError)(error)) {
379
- (0, messages_1.printNetworkIssue)('Could not reach the Playdrop API to verify the app registration.', 'project capture');
380
- process.exitCode = 1;
381
- return;
382
- }
383
- throw error;
384
- }
385
- const projectInfo = (0, devShared_1.findProjectInfo)(filePath);
386
- const devScriptAvailable = Boolean(projectInfo.projectDir && projectInfo.packageJson && typeof projectInfo.packageJson.scripts?.dev === 'string');
387
- const entryLabel = (0, node_path_1.relative)(process.cwd(), filePath) || filePath;
388
- console.log(`[capture] Preparing ${entryLabel} for ${cfg.env} (${appTypeSlug}).`);
389
- const serverAlreadyRunning = await (0, devServer_1.isDevServerAvailable)(appName, portToUse, 750);
390
- const devServerStartedByCapture = !serverAlreadyRunning;
391
- let serverHandle = null;
392
- let signalHandler = null;
393
- let cleanupRequested = false;
394
- const cleanup = async () => {
395
- if (cleanupRequested)
396
- return;
397
- cleanupRequested = true;
398
- if (serverHandle) {
399
- try {
400
- await serverHandle.close();
231
+ if ((0, devShared_1.isNetworkError)(error)) {
232
+ (0, messages_1.printNetworkIssue)('Could not reach the Playdrop API to resolve your account.', 'project capture');
233
+ process.exitCode = 1;
234
+ return;
401
235
  }
402
- catch (error) {
403
- console.error(error instanceof Error ? error.message : String(error));
236
+ if (error instanceof Error && error.message === 'DEV_USERNAME_EMPTY') {
237
+ (0, messages_1.printErrorWithHelp)('Could not determine your Playdrop creator username.', [
238
+ 'Retry "playdrop project capture" in a moment.',
239
+ 'If the problem persists, contact the Playdrop team.',
240
+ ], { command: 'project capture' });
241
+ process.exitCode = 1;
242
+ return;
404
243
  }
244
+ throw error;
405
245
  }
406
- };
407
- if (serverAlreadyRunning) {
408
- console.log(`[capture] Reusing dev server at http://localhost:${portToUse}/apps/${appName}.html`);
409
- }
410
- else {
411
246
  try {
412
- serverHandle = await (0, devServer_1.startDevServer)({
413
- appName,
414
- htmlPath: filePath,
415
- port: portToUse,
416
- projectInfo,
417
- });
418
- signalHandler = () => {
419
- void cleanup().finally(() => process.exit(130));
420
- };
421
- process.on('SIGINT', signalHandler);
422
- process.on('SIGTERM', signalHandler);
247
+ await (0, devShared_1.assertAppRegistered)(client, currentUsername, appName);
423
248
  }
424
249
  catch (error) {
425
- (0, messages_1.printErrorWithHelp)(error?.message || `Failed to start dev server on port ${portToUse}.`, [
426
- 'Check if another process is already using the port.',
427
- 'Ensure the HTML file exists and is readable.',
428
- ], { command: 'project capture' });
429
- process.exitCode = 1;
430
- return;
431
- }
432
- }
433
- if (!serverAlreadyRunning && projectInfo.projectDir && !devScriptAvailable && projectInfo.packageJsonPath) {
434
- const projectLabel = (0, devShared_1.formatProjectLabel)(projectInfo);
435
- if (projectLabel) {
436
- console.log(`[capture] package.json detected at ${projectLabel}, but no "dev" script was found. Run your build/watch scripts manually if needed.`);
437
- }
438
- }
439
- if (devServerStartedByCapture) {
440
- await new Promise(resolve => setTimeout(resolve, 1000));
441
- }
442
- const webBase = envConfig.webBase ?? 'https://www.playdrop.ai';
443
- const frameUrl = `${webBase}/creators/${encodeURIComponent(currentUsername)}/apps/${appTypeSlug}/${encodeURIComponent(appName)}/dev`;
444
- console.log(`[capture] Launching Playwright against ${frameUrl}`);
445
- const errors = [];
446
- const handleConsoleMessage = async (message) => {
447
- const type = message.type();
448
- const level = mapConsoleTypeToLevel(type);
449
- const originPage = message.page();
450
- const originLabel = originPage ? originPage.url() : 'worker';
451
- const location = message.location();
452
- const locationLabel = location && location.url
453
- ? `${location.url}:${location.lineNumber ?? 0}:${location.columnNumber ?? 0}`
454
- : undefined;
455
- const args = await Promise.all(message
456
- .args()
457
- .map(arg => arg.jsonValue().catch(() => arg.toString())));
458
- const rendered = args.length > 0
459
- ? args.map(formatConsoleValue).join(' ')
460
- : message.text();
461
- const prefix = `[capture][console:${type}]`;
462
- const components = [prefix, rendered].filter(Boolean);
463
- const line = components.join(' ');
464
- let effectiveLevel = level;
465
- if (level === 'warn' && line.includes('GL Driver Message')) {
466
- effectiveLevel = 'debug';
467
- }
468
- const allowed = shouldEmit(effectiveLevel, minimumLogLevel);
469
- if (type === 'error' || type === 'assert' || type === 'trace') {
470
- errors.push(line);
471
- if (allowed) {
472
- console.error(line);
250
+ if (error instanceof http_1.CLIUnsupportedClientError) {
251
+ return;
473
252
  }
474
- return;
475
- }
476
- if (!allowed) {
477
- return;
478
- }
479
- if (effectiveLevel === 'warn') {
480
- console.warn(line);
481
- }
482
- else if (effectiveLevel === 'info') {
483
- console.info(line);
484
- }
485
- else if (effectiveLevel === 'debug') {
486
- console.debug(line);
487
- }
488
- else {
489
- console.log(line);
490
- }
491
- };
492
- try {
493
- await (0, playwright_1.withChromiumPage)(async ({ page }) => {
494
- await page.addInitScript(({ token, user }) => {
495
- try {
496
- if (token) {
497
- window.localStorage.setItem('playdrop.accessToken', token);
498
- }
499
- }
500
- catch {
501
- // ignore storage errors
502
- }
503
- try {
504
- if (user) {
505
- window.localStorage.setItem('playdrop.user', JSON.stringify(user));
506
- }
507
- }
508
- catch {
509
- // ignore storage errors
510
- }
511
- try {
512
- window.sessionStorage.removeItem('playdrop.logoutReason');
513
- }
514
- catch {
515
- // ignore storage errors
516
- }
517
- }, { token: cfg.token, user: currentUser });
518
- await page.exposeBinding('__playdropCaptureLog', async ({ frame }, type, payload) => {
519
- const normalized = typeof type === 'string' ? type.toLowerCase() : 'info';
520
- const level = normalized === 'error' || normalized === 'fatal'
521
- ? 'error'
522
- : normalized === 'warn' || normalized === 'warning'
523
- ? 'warn'
524
- : normalized === 'debug'
525
- ? 'debug'
526
- : 'info';
527
- const frameUrl = frame?.url();
528
- const serialized = serializePayload(payload);
529
- const parts = ['[capture][custom]', normalized, serialized].filter(Boolean);
530
- const line = parts.join(' ');
531
- const allowed = shouldEmit(level, minimumLogLevel);
532
- if (level === 'error') {
533
- errors.push(line);
534
- if (allowed) {
535
- console.error(line);
536
- }
537
- return;
538
- }
539
- if (!allowed) {
540
- return;
541
- }
542
- if (level === 'warn') {
543
- console.warn(line);
544
- }
545
- else if (level === 'debug') {
546
- console.debug(line);
253
+ if (error instanceof types_1.UnsupportedClientError) {
254
+ (0, http_1.handleUnsupportedError)(error, 'Capture');
255
+ process.exitCode = 1;
256
+ return;
257
+ }
258
+ if (error instanceof types_1.ApiError) {
259
+ if (error.status === 404) {
260
+ (0, messages_1.printErrorWithHelp)(`App ${currentUsername}/${appName} is not registered on ${env}.`, [
261
+ `Run "playdrop project create app ${appName}" to register the app before running capture.`,
262
+ 'If you expected it to exist, ensure you are logged into the correct environment.',
263
+ ], { command: 'project capture' });
547
264
  }
548
265
  else {
549
- console.log(line);
550
- }
551
- });
552
- await page.addInitScript(() => {
553
- window.addEventListener('unhandledrejection', event => {
554
- try {
555
- console.error('[capture][unhandledrejection]', event.reason);
556
- }
557
- catch {
558
- console.error('[capture][unhandledrejection]');
559
- }
560
- });
561
- const bridgeWindow = window;
562
- bridgeWindow.playdrop = bridgeWindow.playdrop || {};
563
- const bridge = (type, payload) => {
564
- try {
565
- const binding = bridgeWindow.__playdropCaptureLog;
566
- if (typeof binding === 'function') {
567
- binding(type, payload);
568
- }
569
- }
570
- catch (error) {
571
- console.error('[capture][bridge-error]', error);
572
- }
573
- };
574
- Object.defineProperty(bridgeWindow.playdrop, 'capture', {
575
- value: bridge,
576
- configurable: true,
577
- writable: true,
578
- });
579
- });
580
- page.on('console', message => {
581
- void handleConsoleMessage(message);
582
- });
583
- page.on('pageerror', error => {
584
- const text = error?.message ?? String(error);
585
- errors.push(text);
586
- if (shouldEmit('error', minimumLogLevel)) {
587
- console.error(`[capture][pageerror] ${text}`);
266
+ (0, messages_1.printErrorWithHelp)(`Failed to verify app registration (status ${error.status}).`, [
267
+ 'Retry in a moment.',
268
+ 'If the issue persists, contact the Playdrop team.',
269
+ ], { command: 'project capture' });
588
270
  }
589
- });
590
- page.on('requestfailed', request => {
591
- const failure = request.failure();
592
- const errorText = failure?.errorText ?? '';
593
- const text = `${request.method()} ${request.url()} - ${errorText || 'failed'}`;
594
- if (/ERR_ABORTED/i.test(errorText) || /ERR_HTTP2_PROTOCOL_ERROR/i.test(errorText)) {
595
- if (shouldEmit('debug', minimumLogLevel)) {
596
- console.debug(`[capture][requestcancelled] ${text}`);
597
- }
598
- return;
599
- }
600
- errors.push(text);
601
- if (shouldEmit('warn', minimumLogLevel)) {
602
- console.error(`[capture][requestfailed] ${text}`);
603
- }
604
- });
605
- page.on('response', response => {
606
- const status = response.status();
607
- if (status >= 400) {
608
- const text = `${status} ${response.statusText()} ${response.url()}`;
609
- if (status === 404 && /\\?playdrop_channel=/.test(response.url())) {
610
- if (shouldEmit('debug', minimumLogLevel)) {
611
- console.debug(`[capture][response-reload] ${text}`);
612
- }
613
- return;
614
- }
615
- errors.push(text);
616
- if (shouldEmit('warn', minimumLogLevel)) {
617
- console.error(`[capture][response] ${text}`);
618
- }
619
- }
620
- });
621
- const response = await page.goto(frameUrl, { waitUntil: 'domcontentloaded', timeout: 30000 });
622
- if (response && !response.ok()) {
623
- const statusText = `${response.status()} ${response.statusText()}`;
624
- console.warn(`[capture] Navigation succeeded with non-OK status: ${statusText}`);
271
+ process.exitCode = 1;
272
+ return;
625
273
  }
626
- await page.waitForTimeout(timeoutMs);
627
- if (screenshotPath) {
274
+ if ((0, devShared_1.isNetworkError)(error)) {
275
+ (0, messages_1.printNetworkIssue)('Could not reach the Playdrop API to verify the app registration.', 'project capture');
276
+ process.exitCode = 1;
277
+ return;
278
+ }
279
+ throw error;
280
+ }
281
+ const entryLabel = (0, node_path_1.relative)(process.cwd(), filePath) || filePath;
282
+ console.log(`[capture] Preparing ${entryLabel} for ${env} (${appTypeSlug}).`);
283
+ const serverAlreadyRunning = await (0, devServer_1.isDevServerAvailable)(appName, portToUse, 750);
284
+ const devServerStartedByCapture = !serverAlreadyRunning;
285
+ let serverHandle = null;
286
+ let signalHandler = null;
287
+ let cleanupRequested = false;
288
+ const cleanup = async () => {
289
+ if (cleanupRequested)
290
+ return;
291
+ cleanupRequested = true;
292
+ if (serverHandle) {
628
293
  try {
629
- const targetDir = (0, node_path_1.dirname)(screenshotPath);
630
- if (targetDir && targetDir !== '.' && targetDir !== '/') {
631
- await (0, promises_1.mkdir)(targetDir, { recursive: true });
632
- }
633
- await page.screenshot({ path: screenshotPath, fullPage: true });
634
- const relativePath = (0, node_path_1.relative)(process.cwd(), screenshotPath) || screenshotPath;
635
- console.log(`[capture] Saved screenshot to ${relativePath}`);
294
+ await serverHandle.close();
636
295
  }
637
296
  catch (error) {
638
- const reason = error?.message ?? String(error);
639
- errors.push(`screenshot_failed: ${reason}`);
640
- console.error(`[capture] Failed to write screenshot: ${reason}`);
297
+ console.error(error instanceof Error ? error.message : String(error));
641
298
  }
642
299
  }
643
- }, surfaceContextOptions);
644
- }
645
- catch (error) {
646
- if (error instanceof http_1.CLIUnsupportedClientError) {
647
- return;
300
+ };
301
+ if (serverAlreadyRunning) {
302
+ console.log(`[capture] Reusing dev server at http://localhost:${portToUse}/apps/${appName}.html`);
648
303
  }
649
- if (error instanceof types_1.UnsupportedClientError) {
650
- (0, http_1.handleUnsupportedError)(error, 'Capture');
651
- process.exitCode = 1;
652
- return;
304
+ else {
305
+ try {
306
+ serverHandle = await (0, devServer_1.startDevServer)({
307
+ appName,
308
+ htmlPath: filePath,
309
+ port: portToUse,
310
+ projectInfo,
311
+ });
312
+ signalHandler = () => {
313
+ void cleanup().finally(() => process.exit(130));
314
+ };
315
+ process.on('SIGINT', signalHandler);
316
+ process.on('SIGTERM', signalHandler);
317
+ }
318
+ catch (error) {
319
+ (0, messages_1.printErrorWithHelp)(error?.message || `Failed to start dev server on port ${portToUse}.`, [
320
+ 'Check if another process is already using the port.',
321
+ 'Ensure the HTML file exists and is readable.',
322
+ ], { command: 'project capture' });
323
+ process.exitCode = 1;
324
+ return;
325
+ }
326
+ }
327
+ if (!serverAlreadyRunning && projectInfo.projectDir && !devScriptAvailable && projectInfo.packageJsonPath) {
328
+ const projectLabel = (0, devShared_1.formatProjectLabel)(projectInfo);
329
+ if (projectLabel) {
330
+ console.log(`[capture] package.json detected at ${projectLabel}, but no "dev" script was found. Run your build/watch scripts manually if needed.`);
331
+ }
653
332
  }
654
- const message = error instanceof Error ? error.message : String(error);
655
- if (/browserType\.launch/i.test(message) || /MachPortRendezvousServer/i.test(message) || /Chromium/i.test(message)) {
656
- const launchError = (0, playwright_1.createPlaywrightLaunchError)('playdrop project capture', error);
657
- console.error(launchError.message);
333
+ if (devServerStartedByCapture) {
334
+ await new Promise(resolve => setTimeout(resolve, 1000));
658
335
  }
659
- else if (error instanceof Error && error.stack) {
660
- console.error(error.stack);
336
+ const webBase = envConfig.webBase ?? 'https://www.playdrop.ai';
337
+ const frameUrl = `${webBase}/creators/${encodeURIComponent(currentUsername)}/apps/${appTypeSlug}/${encodeURIComponent(appName)}/dev`;
338
+ console.log(`[capture] Launching Playwright against ${frameUrl}`);
339
+ try {
340
+ const result = await (0, captureRuntime_1.runCapture)({
341
+ targetUrl: frameUrl,
342
+ expectedUrl: frameUrl,
343
+ timeoutMs,
344
+ minimumLogLevel,
345
+ screenshotPath,
346
+ contextOptions: surfaceContextOptions,
347
+ token: loginOverride ? undefined : token,
348
+ user: loginOverride ? undefined : currentUser,
349
+ login: loginOverride,
350
+ enableCaptureBridge: true,
351
+ });
352
+ if (result.errorCount > 0) {
353
+ console.error(`[capture] Completed with ${result.errorCount} error(s) after ${timeoutSeconds} seconds.`);
354
+ process.exitCode = 1;
355
+ }
356
+ else {
357
+ console.log(`[capture] Completed without console errors after ${timeoutSeconds} seconds.`);
358
+ }
661
359
  }
662
- else {
663
- console.error(`[capture] ${message}`);
360
+ catch (error) {
361
+ if (error instanceof http_1.CLIUnsupportedClientError) {
362
+ return;
363
+ }
364
+ if (error instanceof types_1.UnsupportedClientError) {
365
+ (0, http_1.handleUnsupportedError)(error, 'Capture');
366
+ process.exitCode = 1;
367
+ return;
368
+ }
369
+ if (error instanceof Error && error.stack) {
370
+ console.error(error.stack);
371
+ }
372
+ else {
373
+ console.error(`[capture] ${error instanceof Error ? error.message : String(error)}`);
374
+ }
375
+ process.exitCode = 1;
664
376
  }
665
- process.exitCode = 1;
666
- return;
667
- }
668
- finally {
669
- if (signalHandler) {
670
- process.off('SIGINT', signalHandler);
671
- process.off('SIGTERM', signalHandler);
377
+ finally {
378
+ if (signalHandler) {
379
+ process.off('SIGINT', signalHandler);
380
+ process.off('SIGTERM', signalHandler);
381
+ }
382
+ await cleanup();
672
383
  }
673
- await cleanup();
674
- }
675
- if (errors.length > 0) {
676
- console.error(`[capture] Completed with ${errors.length} error(s) after ${timeoutSeconds} seconds.`);
677
- process.exitCode = 1;
678
- }
679
- else {
680
- console.log(`[capture] Completed without console errors after ${timeoutSeconds} seconds.`);
681
- }
384
+ });
682
385
  }