@playdrop/playdrop-cli 0.3.8-build.3 → 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 (98) hide show
  1. package/config/client-meta.json +5 -5
  2. package/dist/apps/build.js +43 -38
  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 +10 -1
  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 +10 -1
  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 +10 -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 +20 -12
  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 +1 -0
  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 +127 -128
  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 +1 -0
  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 +150 -115
  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/payments.d.ts +1 -0
  50. package/node_modules/@playdrop/api-client/dist/domains/payments.d.ts.map +1 -1
  51. package/node_modules/@playdrop/api-client/dist/domains/payments.js +10 -0
  52. package/node_modules/@playdrop/api-client/dist/index.d.ts +34 -31
  53. package/node_modules/@playdrop/api-client/dist/index.d.ts.map +1 -1
  54. package/node_modules/@playdrop/api-client/dist/index.js +19 -9
  55. package/node_modules/@playdrop/boxel-core/dist/src/entity-cleaner.js +2 -0
  56. package/node_modules/@playdrop/boxel-core/dist/src/entity-cleaner.js.map +1 -1
  57. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r15/textured-builder.js +1 -1
  58. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r15/textured-builder.js.map +1 -1
  59. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r15/voxel-builder.js +1 -1
  60. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r15/voxel-builder.js.map +1 -1
  61. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r6/builder.js +95 -75
  62. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r6/builder.js.map +1 -1
  63. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r6/scanner.d.ts +2 -3
  64. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r6/scanner.js.map +1 -1
  65. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r6/textures/face-map.js +4 -4
  66. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r6/textures/face-map.js.map +1 -1
  67. package/node_modules/@playdrop/boxel-core/dist/src/palette_tools.d.ts +2 -2
  68. package/node_modules/@playdrop/boxel-core/dist/src/transforms/textured-boxes/slice.d.ts +5 -5
  69. package/node_modules/@playdrop/boxel-core/dist/src/transforms/voxels/textured-to-voxel.d.ts +3 -3
  70. package/node_modules/@playdrop/boxel-core/dist/src/types.d.ts +25 -25
  71. package/node_modules/@playdrop/boxel-core/dist/src/validation.js +2 -1
  72. package/node_modules/@playdrop/boxel-core/dist/src/validation.js.map +1 -1
  73. package/node_modules/@playdrop/boxel-three/dist/src/exporters/glb.js +5 -0
  74. package/node_modules/@playdrop/config/client-meta.json +5 -5
  75. package/node_modules/@playdrop/config/dist/src/index.js +6 -6
  76. package/node_modules/@playdrop/config/dist/test/validateClientEnvironment.test.js +2 -2
  77. package/node_modules/@playdrop/config/dist/tsconfig.tsbuildinfo +1 -1
  78. package/node_modules/@playdrop/types/dist/api.d.ts +27 -7
  79. package/node_modules/@playdrop/types/dist/api.d.ts.map +1 -1
  80. package/node_modules/@playdrop/types/dist/api.js +15 -8
  81. package/node_modules/@playdrop/types/dist/asset-pack.d.ts +105 -11
  82. package/node_modules/@playdrop/types/dist/asset-pack.d.ts.map +1 -1
  83. package/node_modules/@playdrop/types/dist/asset-pack.js +2 -0
  84. package/node_modules/@playdrop/types/dist/ecs.d.ts.map +1 -1
  85. package/node_modules/@playdrop/types/dist/ecs.js +10 -6
  86. package/node_modules/@playdrop/types/dist/entity.d.ts +5 -10
  87. package/node_modules/@playdrop/types/dist/entity.d.ts.map +1 -1
  88. package/node_modules/@playdrop/types/dist/entity.js +40 -23
  89. package/node_modules/@playdrop/types/dist/graph.d.ts.map +1 -1
  90. package/node_modules/@playdrop/types/dist/graph.js +13 -5
  91. package/node_modules/@playdrop/types/dist/version.d.ts.map +1 -1
  92. package/node_modules/@playdrop/types/dist/version.js +7 -3
  93. package/node_modules/@playdrop/vox-three/dist/src/vox.d.ts +1 -0
  94. package/node_modules/@playdrop/vox-three/dist/src/vox.js +15 -6
  95. package/node_modules/@playdrop/vox-three/dist/src/vox.js.map +1 -1
  96. package/node_modules/@playdrop/vox-three/dist/test/vox.test.js +16 -0
  97. package/node_modules/@playdrop/vox-three/dist/test/vox.test.js.map +1 -1
  98. 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;
@@ -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() {
@@ -91,7 +91,16 @@ function formatCountValue(value) {
91
91
  }
92
92
  function buildCountsSuffix(item) {
93
93
  const parts = [];
94
- if ((item.kind === 'app' || item.kind === 'asset') && typeof item.item.playCount === 'number') {
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
+ }
95
104
  parts.push(`play ${formatCountValue(item.item.playCount)}`);
96
105
  }
97
106
  if (typeof item.item.likeCount === 'number') {
@@ -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) {
@@ -13,6 +13,31 @@ const http_1 = require("../http");
13
13
  const messages_1 = require("../messages");
14
14
  const init_1 = require("./init");
15
15
  const CATALOGUE_FILENAME = 'catalogue.json';
16
+ const MIME_TYPE_TO_EXTENSION = {
17
+ 'image/png': '.png',
18
+ 'image/jpeg': '.jpg',
19
+ 'image/webp': '.webp',
20
+ 'image/gif': '.gif',
21
+ 'audio/mpeg': '.mp3',
22
+ 'audio/wav': '.wav',
23
+ 'audio/ogg': '.ogg',
24
+ 'video/mp4': '.mp4',
25
+ 'video/webm': '.webm',
26
+ 'model/gltf+json': '.gltf',
27
+ 'model/gltf-binary': '.glb',
28
+ 'application/json': '.json',
29
+ };
30
+ function inferExtensionFromCandidate(candidate) {
31
+ try {
32
+ if (candidate.startsWith('http://') || candidate.startsWith('https://')) {
33
+ return (0, node_path_1.extname)(new URL(candidate).pathname);
34
+ }
35
+ }
36
+ catch {
37
+ return (0, node_path_1.extname)(candidate);
38
+ }
39
+ return (0, node_path_1.extname)(candidate);
40
+ }
16
41
  async function ensureWorkspaceCataloguePath() {
17
42
  const path = (0, node_path_1.resolve)(process.cwd(), CATALOGUE_FILENAME);
18
43
  if ((0, node_fs_1.existsSync)(path) && (0, node_fs_1.statSync)(path).isFile()) {
@@ -100,53 +125,13 @@ function parseAssetManifestFiles(value) {
100
125
  function inferExtensionFromManifestEntry(entry) {
101
126
  const candidates = [entry.key, entry.url].filter((value) => typeof value === 'string' && value.length > 0);
102
127
  for (const candidate of candidates) {
103
- try {
104
- if (candidate.startsWith('http://') || candidate.startsWith('https://')) {
105
- const extension = (0, node_path_1.extname)(new URL(candidate).pathname);
106
- if (extension) {
107
- return extension;
108
- }
109
- }
110
- else {
111
- const extension = (0, node_path_1.extname)(candidate);
112
- if (extension) {
113
- return extension;
114
- }
115
- }
116
- }
117
- catch {
118
- const extension = (0, node_path_1.extname)(candidate);
119
- if (extension) {
120
- return extension;
121
- }
128
+ const extension = inferExtensionFromCandidate(candidate);
129
+ if (extension) {
130
+ return extension;
122
131
  }
123
132
  }
124
133
  const mimeType = typeof entry.contentType === 'string' ? entry.contentType.trim().toLowerCase() : '';
125
- if (mimeType === 'image/png')
126
- return '.png';
127
- if (mimeType === 'image/jpeg')
128
- return '.jpg';
129
- if (mimeType === 'image/webp')
130
- return '.webp';
131
- if (mimeType === 'image/gif')
132
- return '.gif';
133
- if (mimeType === 'audio/mpeg')
134
- return '.mp3';
135
- if (mimeType === 'audio/wav')
136
- return '.wav';
137
- if (mimeType === 'audio/ogg')
138
- return '.ogg';
139
- if (mimeType === 'video/mp4')
140
- return '.mp4';
141
- if (mimeType === 'video/webm')
142
- return '.webm';
143
- if (mimeType === 'model/gltf+json')
144
- return '.gltf';
145
- if (mimeType === 'model/gltf-binary')
146
- return '.glb';
147
- if (mimeType === 'application/json')
148
- return '.json';
149
- return '';
134
+ return MIME_TYPE_TO_EXTENSION[mimeType] ?? '';
150
135
  }
151
136
  function buildLocalFilename(entry, usedNames) {
152
137
  const sourceName = typeof entry.key === 'string' && entry.key.length > 0
@@ -99,7 +99,16 @@ function formatCountValue(value) {
99
99
  }
100
100
  function buildCountsSuffix(item) {
101
101
  const parts = [];
102
- if ('playCount' in item.item && typeof item.item.playCount === 'number') {
102
+ if (item.kind === 'app' && 'playCount' in item.item && typeof item.item.playCount === 'number') {
103
+ if (typeof item.item.viewCount === 'number') {
104
+ parts.push(`view ${formatCountValue(item.item.viewCount)}`);
105
+ }
106
+ parts.push(`launch ${formatCountValue(item.item.playCount)}`);
107
+ }
108
+ if (item.kind === 'asset' && 'playCount' in item.item && typeof item.item.playCount === 'number') {
109
+ if (typeof item.item.viewCount === 'number') {
110
+ parts.push(`view ${formatCountValue(item.item.viewCount)}`);
111
+ }
103
112
  parts.push(`play ${formatCountValue(item.item.playCount)}`);
104
113
  }
105
114
  if (typeof item.item.likeCount === 'number') {
@@ -1,6 +1,6 @@
1
1
  import http from 'node:http';
2
2
  import { type ChildProcess } from 'node:child_process';
3
- import { ProjectInfo } from './devShared';
3
+ import type { ProjectInfo } from './devShared';
4
4
  export interface StartDevServerOptions {
5
5
  appName: string;
6
6
  htmlPath: string;
@@ -63,7 +63,7 @@ async function assertAppRegistered(client, creatorUsername, appName) {
63
63
  }
64
64
  function findNearestCatalogue(startDir) {
65
65
  let current = (0, node_path_1.resolve)(startDir);
66
- while (true) { // eslint-disable-line no-constant-condition
66
+ while (true) {
67
67
  const candidate = (0, node_path_1.join)(current, 'catalogue.json');
68
68
  if ((0, node_fs_1.existsSync)(candidate)) {
69
69
  return candidate;