@playdrop/playdrop-cli 0.3.8-build.2 → 0.3.9

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 (106) hide show
  1. package/config/client-meta.json +5 -5
  2. package/dist/apps/build.js +45 -39
  3. package/dist/catalogue-utils.js +23 -18
  4. package/dist/catalogue.d.ts +0 -2
  5. package/dist/catalogue.js +54 -41
  6. package/dist/clientInfo.js +16 -2
  7. package/dist/commands/browse.js +66 -23
  8. package/dist/commands/capture.js +3 -2
  9. package/dist/commands/create.js +49 -46
  10. package/dist/commands/createRemixContent.js +29 -44
  11. package/dist/commands/creations.js +51 -13
  12. package/dist/commands/devServer.d.ts +1 -1
  13. package/dist/commands/devShared.js +1 -1
  14. package/dist/commands/generation.js +91 -74
  15. package/dist/commands/gettingStarted.js +1 -1
  16. package/dist/commands/search.js +29 -1
  17. package/dist/commands/upload-content.d.ts +70 -0
  18. package/dist/commands/upload-content.js +627 -0
  19. package/dist/commands/upload-graph.d.ts +23 -0
  20. package/dist/commands/upload-graph.js +108 -0
  21. package/dist/commands/upload.js +264 -543
  22. package/dist/http.d.ts +1 -1
  23. package/dist/playwright.d.ts +12 -4
  24. package/dist/proxyFetch.js +3 -2
  25. package/node_modules/@playdrop/ai-client/dist/index.d.ts.map +1 -1
  26. package/node_modules/@playdrop/ai-client/dist/index.js +74 -54
  27. package/node_modules/@playdrop/api-client/dist/client.d.ts +34 -14
  28. package/node_modules/@playdrop/api-client/dist/client.d.ts.map +1 -1
  29. package/node_modules/@playdrop/api-client/dist/client.js +6 -8
  30. package/node_modules/@playdrop/api-client/dist/core/errors.js +11 -11
  31. package/node_modules/@playdrop/api-client/dist/core/request.d.ts +2 -0
  32. package/node_modules/@playdrop/api-client/dist/core/request.d.ts.map +1 -1
  33. package/node_modules/@playdrop/api-client/dist/core/request.js +10 -3
  34. package/node_modules/@playdrop/api-client/dist/domains/admin.d.ts +12 -10
  35. package/node_modules/@playdrop/api-client/dist/domains/admin.d.ts.map +1 -1
  36. package/node_modules/@playdrop/api-client/dist/domains/admin.js +33 -30
  37. package/node_modules/@playdrop/api-client/dist/domains/apps.d.ts +8 -1
  38. package/node_modules/@playdrop/api-client/dist/domains/apps.d.ts.map +1 -1
  39. package/node_modules/@playdrop/api-client/dist/domains/apps.js +140 -124
  40. package/node_modules/@playdrop/api-client/dist/domains/asset-packs.d.ts +9 -5
  41. package/node_modules/@playdrop/api-client/dist/domains/asset-packs.d.ts.map +1 -1
  42. package/node_modules/@playdrop/api-client/dist/domains/asset-packs.js +151 -88
  43. package/node_modules/@playdrop/api-client/dist/domains/assets.d.ts +4 -1
  44. package/node_modules/@playdrop/api-client/dist/domains/assets.d.ts.map +1 -1
  45. package/node_modules/@playdrop/api-client/dist/domains/assets.js +153 -112
  46. package/node_modules/@playdrop/api-client/dist/domains/auth.d.ts +3 -1
  47. package/node_modules/@playdrop/api-client/dist/domains/auth.d.ts.map +1 -1
  48. package/node_modules/@playdrop/api-client/dist/domains/auth.js +21 -0
  49. package/node_modules/@playdrop/api-client/dist/domains/me.d.ts +6 -2
  50. package/node_modules/@playdrop/api-client/dist/domains/me.d.ts.map +1 -1
  51. package/node_modules/@playdrop/api-client/dist/domains/me.js +13 -2
  52. package/node_modules/@playdrop/api-client/dist/domains/payments.d.ts +1 -0
  53. package/node_modules/@playdrop/api-client/dist/domains/payments.d.ts.map +1 -1
  54. package/node_modules/@playdrop/api-client/dist/domains/payments.js +10 -0
  55. package/node_modules/@playdrop/api-client/dist/index.d.ts +65 -24
  56. package/node_modules/@playdrop/api-client/dist/index.d.ts.map +1 -1
  57. package/node_modules/@playdrop/api-client/dist/index.js +38 -13
  58. package/node_modules/@playdrop/boxel-core/dist/src/entity-cleaner.js +2 -0
  59. package/node_modules/@playdrop/boxel-core/dist/src/entity-cleaner.js.map +1 -1
  60. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r15/textured-builder.js +1 -1
  61. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r15/textured-builder.js.map +1 -1
  62. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r15/voxel-builder.js +1 -1
  63. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r15/voxel-builder.js.map +1 -1
  64. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r6/builder.js +95 -75
  65. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r6/builder.js.map +1 -1
  66. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r6/scanner.d.ts +2 -3
  67. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r6/scanner.js.map +1 -1
  68. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r6/textures/face-map.js +4 -4
  69. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r6/textures/face-map.js.map +1 -1
  70. package/node_modules/@playdrop/boxel-core/dist/src/palette_tools.d.ts +2 -2
  71. package/node_modules/@playdrop/boxel-core/dist/src/transforms/textured-boxes/slice.d.ts +5 -5
  72. package/node_modules/@playdrop/boxel-core/dist/src/transforms/voxels/textured-to-voxel.d.ts +3 -3
  73. package/node_modules/@playdrop/boxel-core/dist/src/types.d.ts +25 -25
  74. package/node_modules/@playdrop/boxel-core/dist/src/validation.js +2 -1
  75. package/node_modules/@playdrop/boxel-core/dist/src/validation.js.map +1 -1
  76. package/node_modules/@playdrop/boxel-three/dist/src/exporters/glb.js +5 -0
  77. package/node_modules/@playdrop/boxel-three/package.json +3 -3
  78. package/node_modules/@playdrop/config/client-meta.json +5 -5
  79. package/node_modules/@playdrop/config/dist/src/index.js +6 -6
  80. package/node_modules/@playdrop/config/dist/test/validateClientEnvironment.test.js +2 -2
  81. package/node_modules/@playdrop/config/dist/tsconfig.tsbuildinfo +1 -1
  82. package/node_modules/@playdrop/types/dist/api.d.ts +33 -8
  83. package/node_modules/@playdrop/types/dist/api.d.ts.map +1 -1
  84. package/node_modules/@playdrop/types/dist/api.js +16 -8
  85. package/node_modules/@playdrop/types/dist/asset-pack.d.ts +107 -11
  86. package/node_modules/@playdrop/types/dist/asset-pack.d.ts.map +1 -1
  87. package/node_modules/@playdrop/types/dist/asset-pack.js +2 -0
  88. package/node_modules/@playdrop/types/dist/asset.d.ts +15 -0
  89. package/node_modules/@playdrop/types/dist/asset.d.ts.map +1 -1
  90. package/node_modules/@playdrop/types/dist/asset.js +1 -0
  91. package/node_modules/@playdrop/types/dist/ecs.d.ts.map +1 -1
  92. package/node_modules/@playdrop/types/dist/ecs.js +10 -6
  93. package/node_modules/@playdrop/types/dist/entity.d.ts +5 -10
  94. package/node_modules/@playdrop/types/dist/entity.d.ts.map +1 -1
  95. package/node_modules/@playdrop/types/dist/entity.js +40 -23
  96. package/node_modules/@playdrop/types/dist/graph.d.ts.map +1 -1
  97. package/node_modules/@playdrop/types/dist/graph.js +13 -5
  98. package/node_modules/@playdrop/types/dist/version.d.ts.map +1 -1
  99. package/node_modules/@playdrop/types/dist/version.js +7 -3
  100. package/node_modules/@playdrop/vox-three/dist/src/vox.d.ts +1 -0
  101. package/node_modules/@playdrop/vox-three/dist/src/vox.js +15 -6
  102. package/node_modules/@playdrop/vox-three/dist/src/vox.js.map +1 -1
  103. package/node_modules/@playdrop/vox-three/dist/test/vox.test.js +16 -0
  104. package/node_modules/@playdrop/vox-three/dist/test/vox.test.js.map +1 -1
  105. package/node_modules/@playdrop/vox-three/package.json +3 -3
  106. package/package.json +1 -1
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.3.8",
2
+ "version": "0.3.9",
3
3
  "build": 1,
4
4
  "platforms": {
5
5
  "ios": {
@@ -26,19 +26,19 @@
26
26
  },
27
27
  "clients": {
28
28
  "web": {
29
- "minimumVersion": "0.3.8",
29
+ "minimumVersion": "0.3.9",
30
30
  "minimumBuild": 1
31
31
  },
32
32
  "admin": {
33
- "minimumVersion": "0.3.8",
33
+ "minimumVersion": "0.3.9",
34
34
  "minimumBuild": 1
35
35
  },
36
36
  "apple": {
37
- "minimumVersion": "0.3.8",
37
+ "minimumVersion": "0.3.9",
38
38
  "minimumBuild": 1
39
39
  },
40
40
  "cli": {
41
- "minimumVersion": "0.3.8",
41
+ "minimumVersion": "0.3.9",
42
42
  "minimumBuild": 1
43
43
  }
44
44
  }
@@ -182,6 +182,47 @@ function isPathWithinRoot(rootDir, targetPath) {
182
182
  const rootPrefix = normalizedRoot.endsWith(node_path_1.sep) ? normalizedRoot : `${normalizedRoot}${node_path_1.sep}`;
183
183
  return normalizedTarget.startsWith(rootPrefix);
184
184
  }
185
+ function readProjectDirEntries(current) {
186
+ try {
187
+ return (0, node_fs_1.readdirSync)(current, { withFileTypes: true });
188
+ }
189
+ catch {
190
+ return [];
191
+ }
192
+ }
193
+ function appendIncludedProjectFile(rootDir, includePath, fileByRelativePath, results, enforceReservedBundleNames) {
194
+ const normalizedIncludePath = (0, node_path_1.normalize)(includePath).split(node_path_1.sep).join('/');
195
+ if (!normalizedIncludePath || normalizedIncludePath === '.') {
196
+ return;
197
+ }
198
+ if (normalizedIncludePath.startsWith('..')) {
199
+ throw new Error(`[apps][build] Invalid include file path "${includePath}".`);
200
+ }
201
+ if (fileByRelativePath.has(normalizedIncludePath)) {
202
+ return;
203
+ }
204
+ const absolutePath = (0, node_path_1.resolve)(rootDir, normalizedIncludePath);
205
+ if (!isPathWithinRoot(rootDir, absolutePath)) {
206
+ throw new Error(`[apps][build] Included file "${includePath}" escapes the project root.`);
207
+ }
208
+ if (!(0, node_fs_1.existsSync)(absolutePath)) {
209
+ return;
210
+ }
211
+ const stats = (0, node_fs_1.statSync)(absolutePath);
212
+ if (!stats.isFile()) {
213
+ return;
214
+ }
215
+ if (enforceReservedBundleNames && isReservedBundlePath(normalizedIncludePath)) {
216
+ throw new Error(`[apps][build] Reserved bundle filename "${normalizedIncludePath}" is not allowed. Use dedicated source/ecs/server upload fields.`);
217
+ }
218
+ const record = {
219
+ absolutePath,
220
+ relativePath: normalizedIncludePath,
221
+ mtime: stats.mtime,
222
+ };
223
+ results.push(record);
224
+ fileByRelativePath.set(record.relativePath, record);
225
+ }
185
226
  function collectProjectFiles(rootDir, rules, options) {
186
227
  const includeRelativeFiles = options?.includeRelativeFiles ?? [];
187
228
  const enforceReservedBundleNames = options?.enforceReservedBundleNames ?? false;
@@ -194,13 +235,7 @@ function collectProjectFiles(rootDir, rules, options) {
194
235
  if (visited.has(current))
195
236
  continue;
196
237
  visited.add(current);
197
- let dirEntries = [];
198
- try {
199
- dirEntries = (0, node_fs_1.readdirSync)(current, { withFileTypes: true });
200
- }
201
- catch {
202
- continue;
203
- }
238
+ const dirEntries = readProjectDirEntries(current);
204
239
  for (const entry of dirEntries) {
205
240
  const absolutePath = (0, node_path_1.join)(current, entry.name);
206
241
  const relativePath = normalizeRelativePath(rootDir, absolutePath);
@@ -238,37 +273,7 @@ function collectProjectFiles(rootDir, rules, options) {
238
273
  }
239
274
  }
240
275
  for (const includePath of includeRelativeFiles) {
241
- const normalizedIncludePath = (0, node_path_1.normalize)(includePath).split(node_path_1.sep).join('/');
242
- if (!normalizedIncludePath || normalizedIncludePath === '.') {
243
- continue;
244
- }
245
- if (normalizedIncludePath.startsWith('..')) {
246
- throw new Error(`[apps][build] Invalid include file path "${includePath}".`);
247
- }
248
- if (fileByRelativePath.has(normalizedIncludePath)) {
249
- continue;
250
- }
251
- const absolutePath = (0, node_path_1.resolve)(rootDir, normalizedIncludePath);
252
- if (!isPathWithinRoot(rootDir, absolutePath)) {
253
- throw new Error(`[apps][build] Included file "${includePath}" escapes the project root.`);
254
- }
255
- if (!(0, node_fs_1.existsSync)(absolutePath)) {
256
- continue;
257
- }
258
- const stats = (0, node_fs_1.statSync)(absolutePath);
259
- if (!stats.isFile()) {
260
- continue;
261
- }
262
- if (enforceReservedBundleNames && isReservedBundlePath(normalizedIncludePath)) {
263
- throw new Error(`[apps][build] Reserved bundle filename "${normalizedIncludePath}" is not allowed. Use dedicated source/ecs/server upload fields.`);
264
- }
265
- const record = {
266
- absolutePath,
267
- relativePath: normalizedIncludePath,
268
- mtime: stats.mtime,
269
- };
270
- results.push(record);
271
- fileByRelativePath.set(record.relativePath, record);
276
+ appendIncludedProjectFile(rootDir, includePath, fileByRelativePath, results, enforceReservedBundleNames);
272
277
  }
273
278
  results.sort((a, b) => a.relativePath.localeCompare(b.relativePath));
274
279
  return results;
@@ -326,6 +331,7 @@ const CRC32_TABLE = (() => {
326
331
  }
327
332
  return table;
328
333
  })();
334
+ const ZIP_REGULAR_FILE_MODE = 0o100644;
329
335
  function crc32(buffer) {
330
336
  let crc = 0 ^ -1;
331
337
  for (let i = 0; i < buffer.length; i += 1) {
@@ -410,7 +416,7 @@ function createZipArchive(entries) {
410
416
  cursor += 2;
411
417
  centralHeader.writeUInt16LE(0, cursor);
412
418
  cursor += 2;
413
- centralHeader.writeUInt32LE(0, cursor);
419
+ centralHeader.writeUInt32LE((ZIP_REGULAR_FILE_MODE << 16) >>> 0, cursor);
414
420
  cursor += 4;
415
421
  centralHeader.writeUInt32LE(offset, cursor);
416
422
  cursor += 4;
@@ -42,6 +42,27 @@ function parseSurfaceTargets(input) {
42
42
  return { map, list };
43
43
  }
44
44
  const DEFAULT_SURFACE_TARGETS_OBJECT = { desktop: true, mobileLandscape: false, mobilePortrait: false };
45
+ function parseCatalogueEntries(cataloguePath) {
46
+ try {
47
+ const raw = (0, node_fs_1.readFileSync)(cataloguePath, 'utf8');
48
+ const data = raw.trim() ? JSON.parse(raw) : {};
49
+ const entries = Array.isArray(data) ? data : data?.apps;
50
+ return Array.isArray(entries)
51
+ ? entries.filter((entry) => Boolean(entry && typeof entry === 'object'))
52
+ : [];
53
+ }
54
+ catch {
55
+ throw new Error(`Failed to parse ${cataloguePath}. Fix the JSON and retry.`);
56
+ }
57
+ }
58
+ function findMatchingCatalogueEntries(apps, relativePath, fileName) {
59
+ return apps.filter((entry) => {
60
+ const entryName = typeof entry.name === 'string' ? entry.name.trim() : '';
61
+ const entryFile = typeof entry.file === 'string' ? entry.file.trim() : `${entryName}.html`;
62
+ const normalized = entryFile.replace(/\\/g, '/');
63
+ return normalized === relativePath || normalized === fileName;
64
+ });
65
+ }
45
66
  function cloneSurfaceTargets(targets) {
46
67
  return {
47
68
  list: [...targets.list],
@@ -54,25 +75,9 @@ function findAppCatalogueInfo(htmlPath) {
54
75
  for (let dir = (0, node_path_1.dirname)(absolute);; dir = (0, node_path_1.dirname)(dir)) {
55
76
  const cataloguePath = (0, node_path_1.join)(dir, 'catalogue.json');
56
77
  if ((0, node_fs_1.existsSync)(cataloguePath) && (0, node_fs_1.statSync)(cataloguePath).isFile()) {
57
- let entries;
58
- try {
59
- const raw = (0, node_fs_1.readFileSync)(cataloguePath, 'utf8');
60
- const data = raw.trim() ? JSON.parse(raw) : {};
61
- entries = Array.isArray(data) ? data : data?.apps;
62
- }
63
- catch {
64
- throw new Error(`Failed to parse ${cataloguePath}. Fix the JSON and retry.`);
65
- }
66
- const apps = Array.isArray(entries) ? entries : [];
67
78
  const rel = (0, node_path_1.relative)(dir, absolute).replace(/\\/g, '/');
68
- const matches = apps.filter((entry) => {
69
- if (!entry || typeof entry !== 'object')
70
- return false;
71
- const entryName = typeof entry.name === 'string' ? entry.name.trim() : '';
72
- const entryFile = typeof entry.file === 'string' ? entry.file.trim() : `${entryName}.html`;
73
- const normalized = entryFile.replace(/\\/g, '/');
74
- return normalized === rel || normalized === fileName;
75
- });
79
+ const apps = parseCatalogueEntries(cataloguePath);
80
+ const matches = findMatchingCatalogueEntries(apps, rel, fileName);
76
81
  if (matches.length > 1) {
77
82
  throw new Error(`Multiple entries in ${cataloguePath} reference ${absolute}.`);
78
83
  }
@@ -85,7 +85,6 @@ export type AssetPackCatalogueEntry = {
85
85
  hostingMode?: string;
86
86
  externalUrl?: string;
87
87
  downloadUrl?: string;
88
- previewApp?: string;
89
88
  visibility?: string;
90
89
  releaseNotes?: string;
91
90
  listing?: AppListingConfig;
@@ -200,7 +199,6 @@ export type AssetPackTask = {
200
199
  hostingMode?: AppHostingMode;
201
200
  externalUrl?: string;
202
201
  downloadUrl?: string;
203
- previewApp?: string;
204
202
  visibility?: string;
205
203
  releaseNotes?: string;
206
204
  listing?: ResolvedListingAssets;
package/dist/catalogue.js CHANGED
@@ -396,6 +396,48 @@ function normalizeAssetSubcategoryValue(raw, category, errors, label) {
396
396
  }
397
397
  return normalized;
398
398
  }
399
+ function validateOptionalEmoji(value, hasEmojiField, errors) {
400
+ const emoji = value;
401
+ if (!hasEmojiField) {
402
+ return emoji;
403
+ }
404
+ if (emoji !== null && typeof emoji === 'string') {
405
+ const trimmed = emoji.trim();
406
+ if (!trimmed) {
407
+ return null;
408
+ }
409
+ if (Array.from(trimmed).length > 4) {
410
+ errors.push('emoji must be a single character');
411
+ return emoji;
412
+ }
413
+ return trimmed;
414
+ }
415
+ if (emoji !== null) {
416
+ errors.push('emoji must be a string or null');
417
+ }
418
+ return emoji;
419
+ }
420
+ function validateOptionalColor(value, hasColorField, errors) {
421
+ const color = value;
422
+ if (!hasColorField) {
423
+ return color;
424
+ }
425
+ if (color !== null && typeof color === 'string') {
426
+ const trimmed = color.trim();
427
+ if (!trimmed) {
428
+ return null;
429
+ }
430
+ if (!HEX_COLOR_REGEX.test(trimmed)) {
431
+ errors.push('color must be a hex RGB value like #FFAA00');
432
+ return color;
433
+ }
434
+ return trimmed.startsWith('#') ? trimmed.toUpperCase() : `#${trimmed.toUpperCase()}`;
435
+ }
436
+ if (color !== null) {
437
+ errors.push('color must be a string or null');
438
+ }
439
+ return color;
440
+ }
399
441
  function validateAppMetadata(entry) {
400
442
  const warnings = [];
401
443
  const errors = [];
@@ -403,42 +445,8 @@ function validateAppMetadata(entry) {
403
445
  if (missingFields.length > 0) {
404
446
  warnings.push(formatMissingMetadata(missingFields));
405
447
  }
406
- let emoji = entry.emoji;
407
- if (Object.prototype.hasOwnProperty.call(entry, 'emoji')) {
408
- if (emoji !== null && typeof emoji === 'string') {
409
- const trimmed = emoji.trim();
410
- if (!trimmed) {
411
- emoji = null;
412
- }
413
- else if (Array.from(trimmed).length > 4) {
414
- errors.push('emoji must be a single character');
415
- }
416
- else {
417
- emoji = trimmed;
418
- }
419
- }
420
- else if (emoji !== null) {
421
- errors.push('emoji must be a string or null');
422
- }
423
- }
424
- let color = entry.color;
425
- if (Object.prototype.hasOwnProperty.call(entry, 'color')) {
426
- if (color !== null && typeof color === 'string') {
427
- const trimmed = color.trim();
428
- if (!trimmed) {
429
- color = null;
430
- }
431
- else if (!HEX_COLOR_REGEX.test(trimmed)) {
432
- errors.push('color must be a hex RGB value like #FFAA00');
433
- }
434
- else {
435
- color = trimmed.startsWith('#') ? trimmed.toUpperCase() : `#${trimmed.toUpperCase()}`;
436
- }
437
- }
438
- else if (color !== null) {
439
- errors.push('color must be a string or null');
440
- }
441
- }
448
+ const emoji = validateOptionalEmoji(entry.emoji, Object.prototype.hasOwnProperty.call(entry, 'emoji'), errors);
449
+ const color = validateOptionalColor(entry.color, Object.prototype.hasOwnProperty.call(entry, 'color'), errors);
442
450
  let type;
443
451
  if (typeof entry.type === 'string' && entry.type.trim()) {
444
452
  const parsedType = (0, types_1.parseAppType)(entry.type.trim());
@@ -626,7 +634,6 @@ function buildAppTasks(rootDir, catalogues, options) {
626
634
  serverPath = absoluteServerPath;
627
635
  }
628
636
  // Parse versioning fields (required in schema v2)
629
- let version;
630
637
  const rawVersion = typeof entry.version === 'string' ? entry.version.trim() : '';
631
638
  if (!rawVersion) {
632
639
  errors.push(`[${label}] Skipping ${rawName}: app version is required in schema v2 (e.g., "1.0.0").`);
@@ -636,7 +643,7 @@ function buildAppTasks(rootDir, catalogues, options) {
636
643
  errors.push(`[${label}] Skipping ${rawName}: version must be in Major.Minor.Patch format (e.g., "1.0.0").`);
637
644
  continue;
638
645
  }
639
- version = rawVersion;
646
+ const version = rawVersion;
640
647
  const releaseNotes = typeof entry.releaseNotes === 'string' ? entry.releaseNotes : undefined;
641
648
  let versionVisibility;
642
649
  const rawVisibility = typeof entry.visibility === 'string' ? entry.visibility.trim().toUpperCase() : '';
@@ -936,7 +943,7 @@ function buildAppTasks(rootDir, catalogues, options) {
936
943
  category,
937
944
  subcategory,
938
945
  format: embeddedFormat,
939
- visibility: normalizeAssetVisibilityValue(embedded?.visibility, errors, `${label} embedded asset \"${embeddedName}\"`),
946
+ visibility: normalizeAssetVisibilityValue(embedded?.visibility, errors, `${label} embedded asset "${embeddedName}"`),
940
947
  shopListed: typeof embedded?.shopListed === 'boolean' ? embedded.shopListed : undefined,
941
948
  shopPriceCredits: Number.isInteger(embedded?.shopPriceCredits) ? Number(embedded.shopPriceCredits) : undefined,
942
949
  files,
@@ -1107,7 +1114,7 @@ function buildAssetTasks(catalogues) {
1107
1114
  username,
1108
1115
  remix,
1109
1116
  format,
1110
- visibility: normalizeAssetVisibilityValue(entry.visibility, errors, `${label} asset \"${name}\"`),
1117
+ visibility: normalizeAssetVisibilityValue(entry.visibility, errors, `${label} asset "${name}"`),
1111
1118
  shopListed: typeof entry.shopListed === 'boolean' ? entry.shopListed : undefined,
1112
1119
  shopPriceCredits: Number.isInteger(entry.shopPriceCredits) ? Number(entry.shopPriceCredits) : undefined,
1113
1120
  files,
@@ -1351,6 +1358,13 @@ function buildAssetPackTasks(catalogues) {
1351
1358
  }
1352
1359
  listing = resolvedListing;
1353
1360
  }
1361
+ const previewApp = typeof entry.previewApp === 'string'
1362
+ ? String(entry.previewApp).trim()
1363
+ : '';
1364
+ if (previewApp.length > 0) {
1365
+ errors.push(`[${label}] Skipping asset pack "${name}": previewApp is no longer supported during publish. Upload the pack first, then set previewAppVersionId explicitly with creations asset-packs update.`);
1366
+ continue;
1367
+ }
1354
1368
  tasks.push({
1355
1369
  kind: 'asset-pack',
1356
1370
  name,
@@ -1363,7 +1377,6 @@ function buildAssetPackTasks(catalogues) {
1363
1377
  hostingMode,
1364
1378
  externalUrl,
1365
1379
  downloadUrl,
1366
- previewApp: typeof entry.previewApp === 'string' && entry.previewApp.trim().length > 0 ? entry.previewApp.trim() : undefined,
1367
1380
  visibility,
1368
1381
  releaseNotes: typeof entry.releaseNotes === 'string' ? entry.releaseNotes : undefined,
1369
1382
  listing,
@@ -12,14 +12,28 @@ const os_1 = __importDefault(require("os"));
12
12
  const config_1 = require("@playdrop/config");
13
13
  function mapPlatform(platform) {
14
14
  switch (platform) {
15
+ case 'aix':
16
+ return 'aix';
17
+ case 'android':
18
+ return 'android';
19
+ case 'cygwin':
20
+ return 'cygwin';
15
21
  case 'darwin':
16
22
  return 'macos';
23
+ case 'freebsd':
24
+ return 'freebsd';
25
+ case 'haiku':
26
+ return 'haiku';
17
27
  case 'win32':
18
28
  return 'windows';
19
29
  case 'linux':
20
30
  return 'linux';
21
- default:
22
- return platform || 'unknown';
31
+ case 'netbsd':
32
+ return 'netbsd';
33
+ case 'openbsd':
34
+ return 'openbsd';
35
+ case 'sunos':
36
+ return 'sunos';
23
37
  }
24
38
  }
25
39
  function detectPlatformVersion() {
@@ -86,6 +86,34 @@ function itemSummary(item) {
86
86
  }
87
87
  return 'asset-pack';
88
88
  }
89
+ function formatCountValue(value) {
90
+ return value.toLocaleString('en-US');
91
+ }
92
+ function buildCountsSuffix(item) {
93
+ const parts = [];
94
+ if (item.kind === 'app' && typeof item.item.playCount === 'number') {
95
+ if (typeof item.item.viewCount === 'number') {
96
+ parts.push(`view ${formatCountValue(item.item.viewCount)}`);
97
+ }
98
+ parts.push(`launch ${formatCountValue(item.item.playCount)}`);
99
+ }
100
+ if (item.kind === 'asset' && typeof item.item.playCount === 'number') {
101
+ if (typeof item.item.viewCount === 'number') {
102
+ parts.push(`view ${formatCountValue(item.item.viewCount)}`);
103
+ }
104
+ parts.push(`play ${formatCountValue(item.item.playCount)}`);
105
+ }
106
+ if (typeof item.item.likeCount === 'number') {
107
+ parts.push(`like ${formatCountValue(item.item.likeCount)}`);
108
+ }
109
+ if (typeof item.item.remixCount === 'number') {
110
+ parts.push(`remix ${formatCountValue(item.item.remixCount)}`);
111
+ }
112
+ if (typeof item.item.commentCount === 'number') {
113
+ parts.push(`comment ${formatCountValue(item.item.commentCount)}`);
114
+ }
115
+ return parts.length > 0 ? ` | ${parts.join(' | ')}` : '';
116
+ }
89
117
  async function collectPaginatedItems(fetchPage) {
90
118
  const items = [];
91
119
  let offset = 0;
@@ -119,6 +147,12 @@ function sliceMergedItems(items, limit, offset) {
119
147
  },
120
148
  };
121
149
  }
150
+ function requirePagination(pagination, context) {
151
+ if (!pagination) {
152
+ throw new Error(`missing_pagination:${context}`);
153
+ }
154
+ return pagination;
155
+ }
122
156
  async function resolveCreatorUsername(creator, command, resolveMe) {
123
157
  if (creator === 'all') {
124
158
  return null;
@@ -151,12 +185,13 @@ async function browse(options = {}) {
151
185
  process.exitCode = 1;
152
186
  return;
153
187
  }
154
- const appType = options.appType ? (0, types_1.parseAppType)(options.appType.trim().toUpperCase()) : undefined;
155
- if (options.appType !== undefined && !appType) {
188
+ const parsedAppType = options.appType ? (0, types_1.parseAppType)(options.appType.trim().toUpperCase()) : undefined;
189
+ if (options.appType !== undefined && !parsedAppType) {
156
190
  (0, messages_1.printErrorWithHelp)(`App type "${options.appType}" is not supported.`, ['Use one of: game, demo, tool, template.'], { command: 'browse' });
157
191
  process.exitCode = 1;
158
192
  return;
159
193
  }
194
+ const appType = parsedAppType ?? undefined;
160
195
  const assetCategory = parseAssetCategory(options.assetCategory);
161
196
  if (assetCategory === null) {
162
197
  (0, messages_1.printErrorWithHelp)(`Asset category "${options.assetCategory}" is not supported.`, ['Use a canonical asset category like IMAGE, VIDEO, AUDIO, SPRITESHEET, or MODEL_3D.'], { command: 'browse' });
@@ -197,29 +232,23 @@ async function browse(options = {}) {
197
232
  const items = [];
198
233
  let pagination;
199
234
  if (kind === 'app') {
200
- let apps = [];
201
235
  if (creatorUsername) {
202
- if (creator === 'me') {
203
- const response = await client.fetchMyApps();
204
- apps = response.apps ?? [];
205
- }
206
- else {
207
- const response = await client.fetchAppsByCreator(creatorUsername);
208
- apps = response.apps ?? [];
209
- }
236
+ const response = creator === 'me'
237
+ ? await client.fetchMyApps({ limit, offset, type: appType })
238
+ : await client.fetchAppsByCreator(creatorUsername, { limit, offset, type: appType });
239
+ items.push(...(response.apps ?? []).map((item) => toBrowseItem('app', item)));
240
+ pagination = requirePagination(response.pagination, 'browse_app_creator');
210
241
  }
211
242
  else if (appType) {
212
- const response = await client.fetchAppsByType(appType);
213
- apps = response.apps ?? [];
243
+ const response = await client.fetchAppsByType(appType, { limit, offset });
244
+ items.push(...(response.apps ?? []).map((item) => toBrowseItem('app', item)));
245
+ pagination = requirePagination(response.pagination, 'browse_app_type');
214
246
  }
215
247
  else {
216
- const response = await client.fetchApps();
217
- apps = response.apps ?? [];
248
+ const response = await client.fetchApps({ limit, offset });
249
+ items.push(...(response.apps ?? []).map((item) => toBrowseItem('app', item)));
250
+ pagination = requirePagination(response.pagination, 'browse_app_all');
218
251
  }
219
- const filtered = appType ? apps.filter((app) => app.type === appType) : apps;
220
- const sliced = sliceMergedItems(filtered, limit, offset);
221
- items.push(...sliced.items.map((item) => toBrowseItem('app', item)));
222
- pagination = sliced.pagination;
223
252
  }
224
253
  else if (kind === 'asset') {
225
254
  const response = creatorUsername
@@ -238,8 +267,22 @@ async function browse(options = {}) {
238
267
  else {
239
268
  const [apps, assets, packs] = await Promise.all([
240
269
  creatorUsername
241
- ? (creator === 'me' ? client.fetchMyApps().then((response) => response.apps ?? []) : client.fetchAppsByCreator(creatorUsername).then((response) => response.apps ?? []))
242
- : (appType ? client.fetchAppsByType(appType).then((response) => response.apps ?? []) : client.fetchApps().then((response) => response.apps ?? [])),
270
+ ? collectPaginatedItems((pageLimit, pageOffset) => (creator === 'me'
271
+ ? client.fetchMyApps({ limit: pageLimit, offset: pageOffset, type: appType })
272
+ : client.fetchAppsByCreator(creatorUsername, { limit: pageLimit, offset: pageOffset, type: appType }))
273
+ .then((response) => ({
274
+ items: response.apps ?? [],
275
+ pagination: requirePagination(response.pagination, 'browse_all_creator_apps'),
276
+ })))
277
+ : (appType
278
+ ? collectPaginatedItems((pageLimit, pageOffset) => client.fetchAppsByType(appType, { limit: pageLimit, offset: pageOffset }).then((response) => ({
279
+ items: response.apps ?? [],
280
+ pagination: requirePagination(response.pagination, 'browse_all_type_apps'),
281
+ })))
282
+ : collectPaginatedItems((pageLimit, pageOffset) => client.fetchApps({ limit: pageLimit, offset: pageOffset }).then((response) => ({
283
+ items: response.apps ?? [],
284
+ pagination: requirePagination(response.pagination, 'browse_all_apps'),
285
+ })))),
243
286
  creatorUsername
244
287
  ? collectPaginatedItems((pageLimit, pageOffset) => client.listAssetsForCreator(creatorUsername, {
245
288
  limit: pageLimit,
@@ -280,7 +323,7 @@ async function browse(options = {}) {
280
323
  }))),
281
324
  ]);
282
325
  const combined = [
283
- ...(appType ? apps.filter((app) => app.type === appType) : apps).map((item) => toBrowseItem('app', item)),
326
+ ...apps.items.map((item) => toBrowseItem('app', item)),
284
327
  ...assets.items.map((item) => toBrowseItem('asset', item)),
285
328
  ...packs.items.map((item) => toBrowseItem('asset-pack', item)),
286
329
  ];
@@ -309,7 +352,7 @@ async function browse(options = {}) {
309
352
  }
310
353
  for (const [index, item] of items.entries()) {
311
354
  const displayName = item.item.displayName || item.item.name;
312
- console.log(`${index + 1}. [${item.kind}] ${item.ref} | ${displayName} | @${item.item.creatorUsername} | ${itemSummary(item)}`);
355
+ console.log(`${index + 1}. [${item.kind}] ${item.ref} | ${displayName} | @${item.item.creatorUsername} | ${itemSummary(item)}${buildCountsSuffix(item)}`);
313
356
  }
314
357
  console.log('\nNext: run "playdrop detail <creator>/<kind>/<name>" to inspect one item.');
315
358
  }
@@ -35,6 +35,8 @@ function formatAppTypeSlug(type) {
35
35
  return 'template';
36
36
  case 'DEMO':
37
37
  return 'demo';
38
+ case undefined:
39
+ return 'game';
38
40
  default:
39
41
  return 'game';
40
42
  }
@@ -414,8 +416,7 @@ async function capture(targetArg, options = {}) {
414
416
  projectInfo,
415
417
  });
416
418
  signalHandler = () => {
417
- cleanup()
418
- .finally(() => process.exit(130));
419
+ void cleanup().finally(() => process.exit(130));
419
420
  };
420
421
  process.on('SIGINT', signalHandler);
421
422
  process.on('SIGTERM', signalHandler);
@@ -602,55 +602,58 @@ function parseRemixRef(value) {
602
602
  ref: (0, types_1.formatContentVersionRef)(parsed),
603
603
  };
604
604
  }
605
+ function parseRemixScaffoldResponse(response, version) {
606
+ const remixMode = typeof response?.remixMode === 'string' ? response.remixMode : '';
607
+ const archiveUrl = typeof response?.archiveUrl === 'string' && response.archiveUrl.trim().length > 0
608
+ ? response.archiveUrl.trim()
609
+ : null;
610
+ const htmlContent = typeof response?.html === 'string' ? response.html : null;
611
+ const externalListingUrl = typeof response?.externalListingUrl === 'string' && response.externalListingUrl.trim().length > 0
612
+ ? response.externalListingUrl.trim()
613
+ : null;
614
+ const metadata = response.metadata;
615
+ const entryPoint = extractEntryPointFromMetadata(metadata);
616
+ if (remixMode === 'archive') {
617
+ if (!archiveUrl) {
618
+ throw new Error('Remix response missing archiveUrl');
619
+ }
620
+ return {
621
+ html: null,
622
+ metadata,
623
+ archiveUrl,
624
+ entryPoint,
625
+ sourceVersion: version,
626
+ };
627
+ }
628
+ if (remixMode === 'inline-html') {
629
+ if (!htmlContent) {
630
+ throw new Error('Remix response missing html');
631
+ }
632
+ return {
633
+ html: htmlContent,
634
+ metadata,
635
+ entryPoint,
636
+ sourceVersion: null,
637
+ };
638
+ }
639
+ if (remixMode === 'external-metadata-only') {
640
+ if (!externalListingUrl) {
641
+ throw new Error('Remix response missing externalListingUrl');
642
+ }
643
+ return {
644
+ html: null,
645
+ metadata,
646
+ externalListingUrl,
647
+ entryPoint,
648
+ sourceVersion: null,
649
+ };
650
+ }
651
+ throw new Error(`Unsupported remix mode: ${remixMode || 'missing'}`);
652
+ }
605
653
  async function fetchRemixScaffold(client, creator, app, version) {
606
654
  try {
607
655
  const response = await client.fetchRemixScaffold(creator, app, version);
608
- const remixMode = typeof response?.remixMode === 'string' ? response.remixMode : '';
609
- const archiveUrl = typeof response?.archiveUrl === 'string' && response.archiveUrl.trim().length > 0
610
- ? response.archiveUrl.trim()
611
- : null;
612
- const htmlContent = typeof response?.html === 'string' ? response.html : null;
613
- const externalListingUrl = typeof response?.externalListingUrl === 'string' && response.externalListingUrl.trim().length > 0
614
- ? response.externalListingUrl.trim()
615
- : null;
616
- const metadata = response.metadata;
617
- const entryPoint = extractEntryPointFromMetadata(metadata);
618
- if (remixMode === 'archive') {
619
- if (!archiveUrl) {
620
- throw new Error('Remix response missing archiveUrl');
621
- }
622
- return {
623
- html: null,
624
- metadata,
625
- archiveUrl,
626
- entryPoint,
627
- sourceVersion: version,
628
- };
629
- }
630
- if (remixMode === 'inline-html') {
631
- if (!htmlContent) {
632
- throw new Error('Remix response missing html');
633
- }
634
- return {
635
- html: htmlContent,
636
- metadata,
637
- entryPoint,
638
- sourceVersion: null,
639
- };
640
- }
641
- if (remixMode === 'external-metadata-only') {
642
- if (!externalListingUrl) {
643
- throw new Error('Remix response missing externalListingUrl');
644
- }
645
- return {
646
- html: null,
647
- metadata,
648
- externalListingUrl,
649
- entryPoint,
650
- sourceVersion: null,
651
- };
652
- }
653
- throw new Error(`Unsupported remix mode: ${remixMode || 'missing'}`);
656
+ return parseRemixScaffoldResponse(response, version);
654
657
  }
655
658
  catch (unknownError) {
656
659
  if (unknownError instanceof types_1.UnsupportedClientError) {