@playdrop/playdrop-cli 0.4.2 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/README.md +4 -4
  2. package/config/client-meta.json +7 -7
  3. package/dist/apps/upload.js +26 -0
  4. package/dist/captureRuntime.js +2 -8
  5. package/dist/catalogue.d.ts +18 -1
  6. package/dist/catalogue.js +198 -0
  7. package/dist/commands/changelog.d.ts +1 -0
  8. package/dist/commands/changelog.js +7 -0
  9. package/dist/commands/create.js +2 -0
  10. package/dist/commands/documentation.js +24 -4
  11. package/dist/commands/publicPages.d.ts +1 -0
  12. package/dist/commands/publicPages.js +32 -0
  13. package/dist/index.js +19 -6
  14. package/dist/sessionCookie.d.ts +18 -0
  15. package/dist/sessionCookie.js +88 -0
  16. package/dist/taskSelection.js +2 -0
  17. package/node_modules/@playdrop/ai-client/package.json +1 -1
  18. package/node_modules/@playdrop/api-client/dist/client.d.ts +43 -1
  19. package/node_modules/@playdrop/api-client/dist/client.d.ts.map +1 -1
  20. package/node_modules/@playdrop/api-client/dist/client.js +6 -0
  21. package/node_modules/@playdrop/api-client/dist/domains/admin.d.ts +7 -1
  22. package/node_modules/@playdrop/api-client/dist/domains/admin.d.ts.map +1 -1
  23. package/node_modules/@playdrop/api-client/dist/domains/admin.js +63 -0
  24. package/node_modules/@playdrop/api-client/dist/domains/apps.d.ts.map +1 -1
  25. package/node_modules/@playdrop/api-client/dist/domains/apps.js +13 -0
  26. package/node_modules/@playdrop/api-client/dist/domains/player-meta.d.ts +42 -0
  27. package/node_modules/@playdrop/api-client/dist/domains/player-meta.d.ts.map +1 -0
  28. package/node_modules/@playdrop/api-client/dist/domains/player-meta.js +123 -0
  29. package/node_modules/@playdrop/api-client/dist/index.d.ts +28 -1
  30. package/node_modules/@playdrop/api-client/dist/index.d.ts.map +1 -1
  31. package/node_modules/@playdrop/api-client/dist/index.js +45 -0
  32. package/node_modules/@playdrop/api-client/package.json +1 -1
  33. package/node_modules/@playdrop/boxel-core/package.json +1 -1
  34. package/node_modules/@playdrop/boxel-three/package.json +1 -1
  35. package/node_modules/@playdrop/config/client-meta.json +7 -7
  36. package/node_modules/@playdrop/config/dist/src/creator-docs.d.ts +24 -0
  37. package/node_modules/@playdrop/config/dist/src/creator-docs.d.ts.map +1 -0
  38. package/node_modules/@playdrop/config/dist/src/creator-docs.js +253 -0
  39. package/node_modules/@playdrop/config/dist/src/creator-faq.d.ts +17 -0
  40. package/node_modules/@playdrop/config/dist/src/creator-faq.d.ts.map +1 -0
  41. package/node_modules/@playdrop/config/dist/src/creator-faq.js +141 -0
  42. package/node_modules/@playdrop/config/dist/test/creator-docs.test.d.ts +2 -0
  43. package/node_modules/@playdrop/config/dist/test/creator-docs.test.d.ts.map +1 -0
  44. package/node_modules/@playdrop/config/dist/test/creator-docs.test.js +36 -0
  45. package/node_modules/@playdrop/config/dist/tsconfig.tsbuildinfo +1 -1
  46. package/node_modules/@playdrop/config/package.json +1 -1
  47. package/node_modules/@playdrop/types/dist/api.d.ts +8 -1
  48. package/node_modules/@playdrop/types/dist/api.d.ts.map +1 -1
  49. package/node_modules/@playdrop/types/dist/index.d.ts +1 -0
  50. package/node_modules/@playdrop/types/dist/index.d.ts.map +1 -1
  51. package/node_modules/@playdrop/types/dist/index.js +1 -0
  52. package/node_modules/@playdrop/types/dist/player-meta.d.ts +212 -0
  53. package/node_modules/@playdrop/types/dist/player-meta.d.ts.map +1 -0
  54. package/node_modules/@playdrop/types/dist/player-meta.js +114 -0
  55. package/node_modules/@playdrop/types/dist/version.d.ts +20 -0
  56. package/node_modules/@playdrop/types/dist/version.d.ts.map +1 -1
  57. package/node_modules/@playdrop/types/dist/version.js +31 -0
  58. package/node_modules/@playdrop/types/package.json +1 -1
  59. package/node_modules/@playdrop/vox-three/package.json +1 -1
  60. package/package.json +1 -1
  61. package/dist/commands/gettingStarted.d.ts +0 -1
  62. package/dist/commands/gettingStarted.js +0 -26
package/README.md CHANGED
@@ -32,10 +32,10 @@ playdrop project publish .
32
32
  ## Links
33
33
 
34
34
  - Website: [playdrop.ai](https://www.playdrop.ai/)
35
- - Getting started: [playdrop.ai/docs/getting-started](https://www.playdrop.ai/docs/getting-started)
35
+ - Getting started: [playdrop.ai/getting-started](https://www.playdrop.ai/getting-started)
36
36
  - CLI docs: [playdrop.ai/docs/cli](https://www.playdrop.ai/docs/cli)
37
- - SDK docs: [playdrop.ai/docs/sdk](https://www.playdrop.ai/docs/sdk)
38
- - Templates and demos: [playdrop.ai/docs/samples/templates-and-demos](https://www.playdrop.ai/docs/samples/templates-and-demos)
37
+ - Runtime docs: [playdrop.ai/docs/runtime](https://www.playdrop.ai/docs/runtime)
38
+ - Templates and demos: [playdrop.ai/docs/examples#templates-and-demos](https://www.playdrop.ai/docs/examples#templates-and-demos)
39
39
  - Public Playdrop skill: [skills.sh/playdrop-ai/playdrop-skills/playdrop](https://skills.sh/playdrop-ai/playdrop-skills/playdrop)
40
40
 
41
41
  ## Live Examples
@@ -100,5 +100,5 @@ playdrop project publish .
100
100
 
101
101
  ```bash
102
102
  playdrop documentation browse
103
- playdrop documentation read getting-started.md
103
+ playdrop documentation read runtime
104
104
  ```
@@ -1,6 +1,6 @@
1
1
  {
2
- "version": "0.4.2",
3
- "build": 3,
2
+ "version": "0.5.0",
3
+ "build": 2,
4
4
  "platforms": {
5
5
  "ios": {
6
6
  "minimumVersion": "16.0"
@@ -26,19 +26,19 @@
26
26
  },
27
27
  "clients": {
28
28
  "web": {
29
- "minimumVersion": "0.4.2",
30
- "minimumBuild": 3
29
+ "minimumVersion": "0.5.0",
30
+ "minimumBuild": 2
31
31
  },
32
32
  "admin": {
33
- "minimumVersion": "0.4.2",
34
- "minimumBuild": 3
33
+ "minimumVersion": "0.5.0",
34
+ "minimumBuild": 2
35
35
  },
36
36
  "apple": {
37
37
  "minimumVersion": "0.3.10",
38
38
  "minimumBuild": 1
39
39
  },
40
40
  "cli": {
41
- "minimumVersion": "0.4.2"
41
+ "minimumVersion": "0.5.0"
42
42
  }
43
43
  }
44
44
  }
@@ -58,12 +58,38 @@ async function uploadAppVersion(client, task, artifacts, options) {
58
58
  remixRef: task.remix,
59
59
  surfaceTargets: task.surfaceTargets,
60
60
  entryPoint: artifacts?.metadata?.entryPoint ?? undefined,
61
+ authMode: task.authMode,
62
+ controllerMode: task.controllerMode,
63
+ previewable: task.previewable,
61
64
  // App metadata (used when creating app if it doesn't exist)
62
65
  displayName: task.displayName,
63
66
  description: task.description,
64
67
  type: task.type,
65
68
  emoji: task.emoji ?? undefined,
66
69
  color: task.color ?? undefined,
70
+ achievements: task.achievements.map((definition) => ({
71
+ key: definition.key,
72
+ displayName: definition.displayName,
73
+ description: definition.description,
74
+ kind: definition.kind,
75
+ ...(definition.hidden ? { hidden: true } : {}),
76
+ ...(definition.maxProgress !== undefined ? { maxProgress: definition.maxProgress } : {}),
77
+ icon: definition.icon,
78
+ status: definition.status,
79
+ })),
80
+ leaderboards: task.leaderboards.map((definition) => ({
81
+ key: definition.key,
82
+ displayName: definition.displayName,
83
+ description: definition.description,
84
+ scoreType: definition.scoreType,
85
+ sort: definition.sort,
86
+ ...(definition.unitLabel ? { unitLabel: definition.unitLabel } : {}),
87
+ status: definition.status,
88
+ })),
89
+ achievementIconFiles: task.achievements.map((definition) => ({
90
+ key: definition.key,
91
+ file: createFileFromPath(definition.iconPath, 'image/png', isPngSignature),
92
+ })),
67
93
  };
68
94
  // For EXTERNAL hosting mode, use externalUrl instead of bundle
69
95
  if (task.hostingMode === 'EXTERNAL') {
@@ -7,7 +7,7 @@ exports.runCapture = runCapture;
7
7
  const promises_1 = require("node:fs/promises");
8
8
  const node_path_1 = require("node:path");
9
9
  const playwright_1 = require("./playwright");
10
- const ACCESS_TOKEN_COOKIE_NAME = 'playdrop_access_token';
10
+ const sessionCookie_1 = require("./sessionCookie");
11
11
  const FRAME_SELECTOR = 'iframe[title="Game"]';
12
12
  const LOGIN_BUTTON_NAME = 'Sign in to play';
13
13
  exports.CAPTURE_LOG_LEVEL_VALUES = ['debug', 'info', 'warn', 'error'];
@@ -142,13 +142,7 @@ async function runCapture(options) {
142
142
  await (0, playwright_1.withChromiumPage)(async ({ context, page }) => {
143
143
  const targetOrigin = new URL(options.targetUrl).origin;
144
144
  if (bootstrapToken) {
145
- await context.addCookies([
146
- {
147
- name: ACCESS_TOKEN_COOKIE_NAME,
148
- value: bootstrapToken,
149
- url: targetOrigin,
150
- },
151
- ]);
145
+ await context.addCookies((0, sessionCookie_1.buildCaptureAccessTokenCookies)(options.targetUrl, bootstrapToken));
152
146
  }
153
147
  if (bootstrapToken || bootstrapUser) {
154
148
  await page.addInitScript(({ token, user }) => {
@@ -1,4 +1,4 @@
1
- import { type AppSurface, type AppType, type AppHostingMode, type AppVersionVisibility } from '@playdrop/types';
1
+ import { type AppSurface, type AppType, type AppHostingMode, type AppAuthMode, type AppControllerMode, type AppVersionVisibility, type AppAchievementCatalogueDefinition, type AppLeaderboardCatalogueDefinition, type PlayerMetaDefinitionStatus } from '@playdrop/types';
2
2
  export type CatalogueJson = {
3
3
  apps?: Array<Record<string, unknown>>;
4
4
  assets?: Array<Record<string, unknown>>;
@@ -17,6 +17,13 @@ export type AppListingConfig = {
17
17
  videosPortrait?: string[];
18
18
  videosLandscape?: string[];
19
19
  };
20
+ export type ResolvedAppAchievementDefinition = Omit<AppAchievementCatalogueDefinition, 'status'> & {
21
+ iconPath: string;
22
+ status: PlayerMetaDefinitionStatus;
23
+ };
24
+ export type ResolvedAppLeaderboardDefinition = Omit<AppLeaderboardCatalogueDefinition, 'status'> & {
25
+ status: PlayerMetaDefinitionStatus;
26
+ };
20
27
  export type AppCatalogueEntry = {
21
28
  name: string;
22
29
  displayName?: string;
@@ -35,9 +42,14 @@ export type AppCatalogueEntry = {
35
42
  releaseNotes?: string;
36
43
  visibility?: string;
37
44
  hostingMode?: string;
45
+ authMode?: string;
46
+ controllerMode?: string;
47
+ previewable?: boolean;
38
48
  externalUrl?: string;
39
49
  listing?: AppListingConfig;
40
50
  username?: string;
51
+ achievements?: AppAchievementCatalogueDefinition[];
52
+ leaderboards?: AppLeaderboardCatalogueDefinition[];
41
53
  embeddedAssets?: EmbeddedAssetCatalogueEntry[];
42
54
  uses?: {
43
55
  assets?: string[];
@@ -136,9 +148,14 @@ export type AppTask = {
136
148
  releaseNotes?: string;
137
149
  versionVisibility?: AppVersionVisibility;
138
150
  hostingMode?: AppHostingMode;
151
+ authMode?: AppAuthMode;
152
+ controllerMode?: AppControllerMode;
153
+ previewable?: boolean;
139
154
  externalUrl?: string;
140
155
  listing?: ResolvedListingAssets;
141
156
  username?: string;
157
+ achievements: ResolvedAppAchievementDefinition[];
158
+ leaderboards: ResolvedAppLeaderboardDefinition[];
142
159
  remix?: string;
143
160
  embeddedAssets: EmbeddedAssetTask[];
144
161
  uses: {
package/dist/catalogue.js CHANGED
@@ -442,6 +442,163 @@ function validateOptionalColor(value, hasColorField, errors) {
442
442
  }
443
443
  return color;
444
444
  }
445
+ function validateAchievementDefinitions(value, catalogueDir, errors) {
446
+ if (value === undefined) {
447
+ return [];
448
+ }
449
+ if (!Array.isArray(value)) {
450
+ errors.push('achievements must be an array');
451
+ return [];
452
+ }
453
+ const seenKeys = new Set();
454
+ const definitions = [];
455
+ value.forEach((entry, index) => {
456
+ if (!entry || typeof entry !== 'object' || Array.isArray(entry)) {
457
+ errors.push(`achievements[${index}] must be an object`);
458
+ return;
459
+ }
460
+ const key = typeof entry.key === 'string' ? entry.key.trim() : '';
461
+ if (!(0, types_1.isPlayerMetaKey)(key)) {
462
+ errors.push(`achievements[${index}].key must match the player meta key format`);
463
+ return;
464
+ }
465
+ if (seenKeys.has(key)) {
466
+ errors.push(`achievements contains duplicate key "${key}"`);
467
+ return;
468
+ }
469
+ seenKeys.add(key);
470
+ const displayName = typeof entry.displayName === 'string' ? entry.displayName.trim() : '';
471
+ if (!displayName) {
472
+ errors.push(`achievements[${index}].displayName must be a non-empty string`);
473
+ return;
474
+ }
475
+ const description = typeof entry.description === 'string' ? entry.description.trim() : '';
476
+ if (!description) {
477
+ errors.push(`achievements[${index}].description must be a non-empty string`);
478
+ return;
479
+ }
480
+ const kind = (0, types_1.parseAppAchievementKind)(entry.kind);
481
+ if (!kind) {
482
+ errors.push(`achievements[${index}].kind must be one of ${types_1.APP_ACHIEVEMENT_KIND_VALUES.join(', ')}`);
483
+ return;
484
+ }
485
+ const hidden = Boolean(entry.hidden);
486
+ const status = (0, types_1.parsePlayerMetaDefinitionStatus)(entry.status ?? 'ACTIVE');
487
+ if (!status) {
488
+ errors.push(`achievements[${index}].status must be one of ${types_1.PLAYER_META_DEFINITION_STATUS_VALUES.join(', ')}`);
489
+ return;
490
+ }
491
+ const icon = typeof entry.icon === 'string' ? entry.icon.trim() : '';
492
+ if (!icon) {
493
+ errors.push(`achievements[${index}].icon must be a non-empty PNG path`);
494
+ return;
495
+ }
496
+ const iconExtensionError = validateListingAssetExtension(icon, `achievements[${index}].icon`, 'image');
497
+ if (iconExtensionError) {
498
+ errors.push(iconExtensionError);
499
+ return;
500
+ }
501
+ const iconPathError = validateListingAssetPath(catalogueDir, icon, `achievements[${index}].icon`);
502
+ if (iconPathError) {
503
+ errors.push(iconPathError);
504
+ return;
505
+ }
506
+ const iconPath = resolveListingAssetPath(catalogueDir, icon);
507
+ let maxProgress;
508
+ if (kind === 'INCREMENTAL') {
509
+ if (!Number.isSafeInteger(entry.maxProgress) || Number(entry.maxProgress) <= 0) {
510
+ errors.push(`achievements[${index}].maxProgress must be a positive safe integer`);
511
+ return;
512
+ }
513
+ maxProgress = Number(entry.maxProgress);
514
+ }
515
+ else if (entry.maxProgress !== undefined) {
516
+ errors.push(`achievements[${index}].maxProgress is only valid for INCREMENTAL achievements`);
517
+ return;
518
+ }
519
+ definitions.push({
520
+ key,
521
+ displayName,
522
+ description,
523
+ kind,
524
+ hidden,
525
+ ...(maxProgress !== undefined ? { maxProgress } : {}),
526
+ icon,
527
+ iconPath,
528
+ status,
529
+ });
530
+ });
531
+ return definitions;
532
+ }
533
+ function validateLeaderboardDefinitions(value, errors) {
534
+ if (value === undefined) {
535
+ return [];
536
+ }
537
+ if (!Array.isArray(value)) {
538
+ errors.push('leaderboards must be an array');
539
+ return [];
540
+ }
541
+ const seenKeys = new Set();
542
+ const definitions = [];
543
+ value.forEach((entry, index) => {
544
+ if (!entry || typeof entry !== 'object' || Array.isArray(entry)) {
545
+ errors.push(`leaderboards[${index}] must be an object`);
546
+ return;
547
+ }
548
+ const key = typeof entry.key === 'string' ? entry.key.trim() : '';
549
+ if (!(0, types_1.isPlayerMetaKey)(key)) {
550
+ errors.push(`leaderboards[${index}].key must match the player meta key format`);
551
+ return;
552
+ }
553
+ if (seenKeys.has(key)) {
554
+ errors.push(`leaderboards contains duplicate key "${key}"`);
555
+ return;
556
+ }
557
+ seenKeys.add(key);
558
+ const displayName = typeof entry.displayName === 'string' ? entry.displayName.trim() : '';
559
+ if (!displayName) {
560
+ errors.push(`leaderboards[${index}].displayName must be a non-empty string`);
561
+ return;
562
+ }
563
+ const description = typeof entry.description === 'string' ? entry.description.trim() : '';
564
+ if (!description) {
565
+ errors.push(`leaderboards[${index}].description must be a non-empty string`);
566
+ return;
567
+ }
568
+ const scoreType = (0, types_1.parseAppLeaderboardScoreType)(entry.scoreType);
569
+ if (!scoreType) {
570
+ errors.push(`leaderboards[${index}].scoreType must be one of ${types_1.APP_LEADERBOARD_SCORE_TYPE_VALUES.join(', ')}`);
571
+ return;
572
+ }
573
+ const sort = (0, types_1.parseAppLeaderboardSort)(entry.sort);
574
+ if (!sort) {
575
+ errors.push(`leaderboards[${index}].sort must be one of ${types_1.APP_LEADERBOARD_SORT_VALUES.join(', ')}`);
576
+ return;
577
+ }
578
+ if (!(0, types_1.isValidLeaderboardDefinitionCombination)(scoreType, sort)) {
579
+ errors.push(`leaderboards[${index}] must use INTEGER + DESC or TIME_MS + ASC`);
580
+ return;
581
+ }
582
+ const status = (0, types_1.parsePlayerMetaDefinitionStatus)(entry.status ?? 'ACTIVE');
583
+ if (!status) {
584
+ errors.push(`leaderboards[${index}].status must be one of ${types_1.PLAYER_META_DEFINITION_STATUS_VALUES.join(', ')}`);
585
+ return;
586
+ }
587
+ const unitLabel = typeof entry.unitLabel === 'string' && entry.unitLabel.trim().length > 0
588
+ ? entry.unitLabel.trim()
589
+ : undefined;
590
+ definitions.push({
591
+ key,
592
+ displayName,
593
+ description,
594
+ scoreType,
595
+ sort,
596
+ ...(unitLabel ? { unitLabel } : {}),
597
+ status,
598
+ });
599
+ });
600
+ return definitions;
601
+ }
445
602
  function validateAppMetadata(entry) {
446
603
  const warnings = [];
447
604
  const errors = [];
@@ -592,6 +749,12 @@ function buildAppTasks(rootDir, catalogues, options) {
592
749
  if (metadata.warnings.length > 0) {
593
750
  warnings.push(`${label} (${metadata.warnings.join('; ')})`);
594
751
  }
752
+ const errorCountBeforePlayerMeta = errors.length;
753
+ const achievements = validateAchievementDefinitions(entry.achievements, file.directory, errors);
754
+ const leaderboards = validateLeaderboardDefinitions(entry.leaderboards, errors);
755
+ if (errors.length > errorCountBeforePlayerMeta) {
756
+ continue;
757
+ }
595
758
  if (isTypescriptProject && !hasValidateScript) {
596
759
  warnings.push(`${label} (${rawName}: package.json missing "validate" script)`);
597
760
  }
@@ -667,6 +830,32 @@ function buildAppTasks(rootDir, catalogues, options) {
667
830
  }
668
831
  hostingMode = rawHostingMode;
669
832
  }
833
+ let authMode;
834
+ const rawAuthMode = typeof entry.authMode === 'string' ? entry.authMode.trim().toUpperCase() : '';
835
+ if (rawAuthMode) {
836
+ if (!types_1.APP_AUTH_MODE_VALUES.includes(rawAuthMode)) {
837
+ errors.push(`[${label}] Skipping ${rawName}: authMode must be one of ${types_1.APP_AUTH_MODE_VALUES.join(', ')}.`);
838
+ continue;
839
+ }
840
+ authMode = rawAuthMode;
841
+ }
842
+ let controllerMode;
843
+ const rawControllerMode = typeof entry.controllerMode === 'string' ? entry.controllerMode.trim().toUpperCase() : '';
844
+ if (rawControllerMode) {
845
+ if (!types_1.APP_CONTROLLER_MODE_VALUES.includes(rawControllerMode)) {
846
+ errors.push(`[${label}] Skipping ${rawName}: controllerMode must be one of ${types_1.APP_CONTROLLER_MODE_VALUES.join(', ')}.`);
847
+ continue;
848
+ }
849
+ controllerMode = rawControllerMode;
850
+ }
851
+ let previewable;
852
+ if (Object.prototype.hasOwnProperty.call(entry, 'previewable')) {
853
+ if (typeof entry.previewable !== 'boolean') {
854
+ errors.push(`[${label}] Skipping ${rawName}: previewable must be true or false.`);
855
+ continue;
856
+ }
857
+ previewable = Boolean(entry.previewable);
858
+ }
670
859
  let externalUrl;
671
860
  const rawExternalUrl = typeof entry.externalUrl === 'string' ? entry.externalUrl.trim() : '';
672
861
  if (rawExternalUrl) {
@@ -685,6 +874,10 @@ function buildAppTasks(rootDir, catalogues, options) {
685
874
  // Auto-set hostingMode to EXTERNAL if externalUrl is provided
686
875
  hostingMode = 'EXTERNAL';
687
876
  }
877
+ if ((hostingMode ?? 'HOSTED') === 'EXTERNAL' && previewable) {
878
+ errors.push(`[${label}] Skipping ${rawName}: previewable can only be true for HOSTED apps.`);
879
+ continue;
880
+ }
688
881
  // Parse username field (admin-only)
689
882
  let username;
690
883
  const rawUsername = typeof entry.username === 'string' ? entry.username.trim() : '';
@@ -981,10 +1174,15 @@ function buildAppTasks(rootDir, catalogues, options) {
981
1174
  releaseNotes,
982
1175
  versionVisibility,
983
1176
  hostingMode,
1177
+ authMode,
1178
+ controllerMode,
1179
+ previewable,
984
1180
  externalUrl,
985
1181
  listing,
986
1182
  // Admin-only field
987
1183
  username,
1184
+ achievements,
1185
+ leaderboards,
988
1186
  remix,
989
1187
  embeddedAssets,
990
1188
  uses: {
@@ -0,0 +1 @@
1
+ export declare function showChangelog(): Promise<void>;
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.showChangelog = showChangelog;
4
+ const publicPages_1 = require("./publicPages");
5
+ async function showChangelog() {
6
+ await (0, publicPages_1.showPublicPage)('changelog', 'app-pages/changelog.md');
7
+ }
@@ -537,6 +537,8 @@ function buildPendingExternalRemixTask(name, metadata, sourceInfo, cataloguePath
537
537
  missingMetadata: [],
538
538
  version: entry.version,
539
539
  remix: typeof entry.remix === 'string' ? entry.remix : undefined,
540
+ achievements: [],
541
+ leaderboards: [],
540
542
  embeddedAssets: [],
541
543
  uses: { assets: [], packs: [] },
542
544
  graph: { relations: [] },
@@ -22,6 +22,23 @@ function normalizeDocPath(value) {
22
22
  }
23
23
  return segments.join('/');
24
24
  }
25
+ function toDisplayDocPath(entry) {
26
+ if (entry.endsWith('/README.md')) {
27
+ return entry.slice(0, -'/README.md'.length);
28
+ }
29
+ if (entry.endsWith('/AGENTS.md')) {
30
+ return `${entry.slice(0, -'/AGENTS.md'.length)}/agents`;
31
+ }
32
+ return entry;
33
+ }
34
+ function createDocAliasMap(entries) {
35
+ const aliases = new Map();
36
+ for (const entry of entries) {
37
+ aliases.set(entry, entry);
38
+ aliases.set(toDisplayDocPath(entry), entry);
39
+ }
40
+ return aliases;
41
+ }
25
42
  function encodePathSegments(pathValue) {
26
43
  return pathValue
27
44
  .split('/')
@@ -68,7 +85,8 @@ async function browseDocumentation(options = {}) {
68
85
  if (!entries) {
69
86
  return;
70
87
  }
71
- const items = [...new Set(entries.map((entry) => normalizeDocPath(entry)).filter((entry) => Boolean(entry)))].sort((a, b) => a.localeCompare(b));
88
+ const normalizedEntries = [...new Set(entries.map((entry) => normalizeDocPath(entry)).filter((entry) => Boolean(entry)))].sort((a, b) => a.localeCompare(b));
89
+ const items = [...new Set(normalizedEntries.map((entry) => toDisplayDocPath(entry)))].sort((a, b) => a.localeCompare(b));
72
90
  if (options.json) {
73
91
  (0, output_1.printJson)({ items });
74
92
  return;
@@ -87,7 +105,7 @@ async function browseDocumentation(options = {}) {
87
105
  async function readDocumentation(pathArg) {
88
106
  const normalizedTarget = normalizeDocPath(pathArg);
89
107
  if (!normalizedTarget) {
90
- (0, messages_1.printErrorWithHelp)('A documentation path is required.', ['Use a relative path such as sdk/getting-started.md.', 'Run "playdrop documentation browse" to list available entries.'], { command: 'documentation read' });
108
+ (0, messages_1.printErrorWithHelp)('A documentation path is required.', ['Use a relative path such as runtime.', 'Run "playdrop documentation browse" to list available entries.'], { command: 'documentation read' });
91
109
  process.exitCode = 1;
92
110
  return;
93
111
  }
@@ -102,12 +120,14 @@ async function readDocumentation(pathArg) {
102
120
  return;
103
121
  }
104
122
  const normalizedEntries = [...new Set(entries.map((entry) => normalizeDocPath(entry)).filter((entry) => Boolean(entry)))];
105
- if (!normalizedEntries.includes(normalizedTarget)) {
123
+ const aliases = createDocAliasMap(normalizedEntries);
124
+ const resolvedTarget = aliases.get(normalizedTarget);
125
+ if (!resolvedTarget) {
106
126
  (0, messages_1.printErrorWithHelp)(`Documentation entry "${normalizedTarget}" was not found.`, ['Run "playdrop documentation browse" to list available entries.'], { command: 'documentation read' });
107
127
  process.exitCode = 1;
108
128
  return;
109
129
  }
110
- const fileUrl = buildDocumentationFileUrl(envConfig.cdnBase, normalizedTarget);
130
+ const fileUrl = buildDocumentationFileUrl(envConfig.cdnBase, resolvedTarget);
111
131
  let response;
112
132
  try {
113
133
  response = await fetch(fileUrl);
@@ -0,0 +1 @@
1
+ export declare function showPublicPage(command: string, relativePath: string): Promise<void>;
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.showPublicPage = showPublicPage;
4
+ const commandContext_1 = require("../commandContext");
5
+ const messages_1 = require("../messages");
6
+ function buildPublicPageUrl(base, relativePath) {
7
+ return `${base.replace(/\/$/, '')}/${relativePath}`;
8
+ }
9
+ async function showPublicPage(command, relativePath) {
10
+ await (0, commandContext_1.withPublicEnvironment)(command, async ({ envConfig }) => {
11
+ if (!envConfig.cdnBase) {
12
+ (0, messages_1.printErrorWithHelp)(`Environment "${envConfig.name}" does not include a public content base URL.`, ['Set PLAYDROP_CDN_BASE or use an environment that publishes public content assets.'], { command });
13
+ process.exitCode = 1;
14
+ return;
15
+ }
16
+ let response;
17
+ try {
18
+ response = await fetch(buildPublicPageUrl(envConfig.cdnBase, relativePath));
19
+ }
20
+ catch (error) {
21
+ (0, messages_1.printErrorWithHelp)(`Fetching ${command} failed.`, [error instanceof Error ? error.message : String(error)], { command });
22
+ process.exitCode = 1;
23
+ return;
24
+ }
25
+ if (!response.ok) {
26
+ (0, messages_1.printErrorWithHelp)(`Failed to fetch ${command}.`, [`HTTP ${response.status} ${response.statusText}`], { command });
27
+ process.exitCode = 1;
28
+ return;
29
+ }
30
+ process.stdout.write(await response.text());
31
+ });
32
+ }
package/dist/index.js CHANGED
@@ -30,7 +30,8 @@ const format_1 = require("./commands/format");
30
30
  const upload_1 = require("./commands/upload");
31
31
  const documentation_1 = require("./commands/documentation");
32
32
  const feedback_1 = require("./commands/feedback");
33
- const gettingStarted_1 = require("./commands/gettingStarted");
33
+ const changelog_1 = require("./commands/changelog");
34
+ const publicPages_1 = require("./commands/publicPages");
34
35
  const upgrade_1 = require("./commands/upgrade");
35
36
  const messages_1 = require("./messages");
36
37
  function resolveInitTarget(pathArg) {
@@ -83,7 +84,7 @@ program
83
84
  .addHelpCommand('help [command]', 'Show help for a command');
84
85
  program.showHelpAfterError();
85
86
  program.showSuggestionAfterError();
86
- program.addHelpText('afterAll', '\nStart here: run `playdrop getting-started`.\n');
87
+ program.addHelpText('afterAll', '\nStart here: run `playdrop getting-started`, `playdrop faq`, or `playdrop changelog`.\n');
87
88
  program.on('command:*', (unknownCommands) => {
88
89
  const [unknownCommand] = unknownCommands;
89
90
  if (unknownCommand) {
@@ -648,14 +649,26 @@ feedback
648
649
  program
649
650
  .command('getting-started')
650
651
  .description('Show the recommended first steps')
651
- .action(() => {
652
- (0, gettingStarted_1.printGettingStarted)();
652
+ .action(async () => {
653
+ await (0, publicPages_1.showPublicPage)('getting-started', 'app-pages/getting-started.md');
654
+ });
655
+ program
656
+ .command('faq')
657
+ .description('Show creator-focused platform questions and answers')
658
+ .action(async () => {
659
+ await (0, publicPages_1.showPublicPage)('faq', 'app-pages/faq.md');
660
+ });
661
+ program
662
+ .command('changelog')
663
+ .description('Read the latest public Playdrop changelog')
664
+ .action(async () => {
665
+ await (0, changelog_1.showChangelog)();
653
666
  });
654
667
  program
655
668
  .command('start')
656
669
  .description('Alias of getting-started')
657
- .action(() => {
658
- (0, gettingStarted_1.printGettingStarted)();
670
+ .action(async () => {
671
+ await (0, publicPages_1.showPublicPage)('getting-started', 'app-pages/getting-started.md');
659
672
  });
660
673
  program
661
674
  .command('update')
@@ -0,0 +1,18 @@
1
+ export declare function resolveCaptureSessionCookieDomain(hostname: string): string | null;
2
+ export declare function shouldUseSecureCaptureSessionCookie(hostname: string): boolean;
3
+ export declare function buildCaptureAccessTokenCookies(targetUrl: string, accessToken: string): {
4
+ name: string;
5
+ value: string;
6
+ url: string;
7
+ httpOnly: boolean;
8
+ sameSite: "Lax";
9
+ secure: boolean;
10
+ }[] | {
11
+ name: string;
12
+ value: string;
13
+ domain: string;
14
+ path: string;
15
+ httpOnly: boolean;
16
+ sameSite: "Lax";
17
+ secure: boolean;
18
+ }[];