@playdrop/playdrop-cli 0.18.9 → 0.19.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 (66) hide show
  1. package/README.md +27 -0
  2. package/config/client-meta.json +2 -2
  3. package/dist/apps/upload.d.ts +2 -0
  4. package/dist/apps/upload.js +133 -60
  5. package/dist/catalogue.d.ts +18 -0
  6. package/dist/catalogue.js +171 -56
  7. package/dist/commands/devServer.js +3 -0
  8. package/dist/commands/validate.js +1 -0
  9. package/dist/commands/worker/game-source-git.js +7 -0
  10. package/dist/commands/worker/generation.d.ts +16 -1
  11. package/dist/commands/worker/generation.js +8 -2
  12. package/dist/commands/worker/ugc-processing.d.ts +13 -0
  13. package/dist/commands/worker/ugc-processing.js +146 -0
  14. package/dist/commands/worker.js +86 -20
  15. package/dist/proxyFetch.d.ts +5 -0
  16. package/dist/proxyFetch.js +10 -35
  17. package/dist/uploadDispatchers.d.ts +3 -0
  18. package/dist/uploadDispatchers.js +10 -0
  19. package/node_modules/@playdrop/api-client/dist/acquisition.d.ts +3 -1
  20. package/node_modules/@playdrop/api-client/dist/acquisition.d.ts.map +1 -1
  21. package/node_modules/@playdrop/api-client/dist/acquisition.js +2 -2
  22. package/node_modules/@playdrop/api-client/dist/chat.d.ts +13 -4
  23. package/node_modules/@playdrop/api-client/dist/chat.d.ts.map +1 -1
  24. package/node_modules/@playdrop/api-client/dist/client.d.ts +31 -10
  25. package/node_modules/@playdrop/api-client/dist/client.d.ts.map +1 -1
  26. package/node_modules/@playdrop/api-client/dist/core/request.d.ts.map +1 -1
  27. package/node_modules/@playdrop/api-client/dist/core/request.js +11 -1
  28. package/node_modules/@playdrop/api-client/dist/domains/chat.d.ts +30 -9
  29. package/node_modules/@playdrop/api-client/dist/domains/chat.d.ts.map +1 -1
  30. package/node_modules/@playdrop/api-client/dist/domains/chat.js +24 -19
  31. package/node_modules/@playdrop/api-client/dist/domains/comments.d.ts +3 -1
  32. package/node_modules/@playdrop/api-client/dist/domains/comments.d.ts.map +1 -1
  33. package/node_modules/@playdrop/api-client/dist/domains/comments.js +2 -2
  34. package/node_modules/@playdrop/api-client/dist/index.d.ts +17 -6
  35. package/node_modules/@playdrop/api-client/dist/index.d.ts.map +1 -1
  36. package/node_modules/@playdrop/api-client/dist/index.js +12 -10
  37. package/node_modules/@playdrop/api-client/dist/runtime.d.ts +3 -0
  38. package/node_modules/@playdrop/api-client/dist/runtime.d.ts.map +1 -1
  39. package/node_modules/@playdrop/api-client/dist/runtime.js +10 -1
  40. package/node_modules/@playdrop/config/client-meta.json +2 -2
  41. package/node_modules/@playdrop/game-source-git/dist/src/repository.d.ts +8 -0
  42. package/node_modules/@playdrop/game-source-git/dist/src/repository.d.ts.map +1 -1
  43. package/node_modules/@playdrop/game-source-git/dist/src/repository.js +30 -17
  44. package/node_modules/@playdrop/game-source-git/dist/test/core.test.js +10 -0
  45. package/node_modules/@playdrop/game-source-git/dist/tsconfig.tsbuildinfo +1 -1
  46. package/node_modules/@playdrop/game-source-git/package.json +2 -1
  47. package/node_modules/@playdrop/types/dist/api.d.ts +30 -3
  48. package/node_modules/@playdrop/types/dist/api.d.ts.map +1 -1
  49. package/node_modules/@playdrop/types/dist/api.js +1 -0
  50. package/node_modules/@playdrop/types/dist/app-listing-localization.d.ts +34 -0
  51. package/node_modules/@playdrop/types/dist/app-listing-localization.d.ts.map +1 -0
  52. package/node_modules/@playdrop/types/dist/app-listing-localization.js +146 -0
  53. package/node_modules/@playdrop/types/dist/chat.d.ts +7 -2
  54. package/node_modules/@playdrop/types/dist/chat.d.ts.map +1 -1
  55. package/node_modules/@playdrop/types/dist/index.d.ts +3 -0
  56. package/node_modules/@playdrop/types/dist/index.d.ts.map +1 -1
  57. package/node_modules/@playdrop/types/dist/index.js +6 -0
  58. package/node_modules/@playdrop/types/dist/ugc-processing.d.ts +211 -0
  59. package/node_modules/@playdrop/types/dist/ugc-processing.d.ts.map +1 -0
  60. package/node_modules/@playdrop/types/dist/ugc-processing.js +144 -0
  61. package/node_modules/@playdrop/types/dist/ugc.d.ts +39 -0
  62. package/node_modules/@playdrop/types/dist/ugc.d.ts.map +1 -0
  63. package/node_modules/@playdrop/types/dist/ugc.js +70 -0
  64. package/node_modules/@playdrop/types/dist/version.d.ts +4 -0
  65. package/node_modules/@playdrop/types/dist/version.d.ts.map +1 -1
  66. package/package.json +3 -2
package/dist/catalogue.js CHANGED
@@ -9,7 +9,11 @@ exports.resolveWorkspaceApp = resolveWorkspaceApp;
9
9
  exports.formatMetadataWarnings = formatMetadataWarnings;
10
10
  exports.formatMissingMetadataWarnings = formatMissingMetadataWarnings;
11
11
  exports.findAppTaskByFile = findAppTaskByFile;
12
+ exports.catalogueListingContent = catalogueListingContent;
13
+ exports.calculateCatalogueListingSourceHash = calculateCatalogueListingSourceHash;
14
+ const node_crypto_1 = require("node:crypto");
12
15
  const types_1 = require("@playdrop/types");
16
+ const types_2 = require("@playdrop/types");
13
17
  const node_fs_1 = require("node:fs");
14
18
  const node_path_1 = require("node:path");
15
19
  const assetSpecs_1 = require("./assetSpecs");
@@ -17,7 +21,7 @@ const catalogue_utils_1 = require("./catalogue-utils");
17
21
  const HEX_COLOR_REGEX = /^#?(?:[0-9a-fA-F]{6})$/;
18
22
  const SEMVER_REGEX = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/;
19
23
  const LEGACY_CATALOGUE_VERSION_KEY = ["schema", "Version"].join("");
20
- const ASSET_CATEGORY_SET = new Set(types_1.ASSET_CATEGORY_VALUES);
24
+ const ASSET_CATEGORY_SET = new Set(types_2.ASSET_CATEGORY_VALUES);
21
25
  const LISTING_IMAGE_EXTENSIONS = new Set([".png"]);
22
26
  const LISTING_VIDEO_EXTENSIONS = new Set([".mp4"]);
23
27
  exports.APP_DESCRIPTION_RECOMMENDED_MIN_CHARACTERS = 120;
@@ -26,7 +30,7 @@ exports.APP_RELEASE_NOTES_MAX_CHARACTERS = 240;
26
30
  const MAX_CONTENT_TAGS_PER_ITEM = 20;
27
31
  const APP_ALLOWED_LICENSES = new Set(["CLOSED", "PLAYDROP", "MIT"]);
28
32
  const ASSET_ALLOWED_LICENSES = new Set(["CLOSED", "PLAYDROP", "MIT", "CC0"]);
29
- const ASSET_SUBCATEGORY_SET_BY_CATEGORY = types_1.ASSET_SUBCATEGORY_DEFINITIONS.reduce((acc, definition) => {
33
+ const ASSET_SUBCATEGORY_SET_BY_CATEGORY = types_2.ASSET_SUBCATEGORY_DEFINITIONS.reduce((acc, definition) => {
30
34
  if (!acc[definition.category]) {
31
35
  acc[definition.category] = new Set();
32
36
  }
@@ -240,7 +244,7 @@ function normalizeCatalogueRemixRef(value, expectedKind, errors, context) {
240
244
  errors.push(`${context} remix must be a canonical ${expectedKind} version ref string.`);
241
245
  return undefined;
242
246
  }
243
- const parsed = (0, types_1.parseContentVersionRef)(trimmed);
247
+ const parsed = (0, types_2.parseContentVersionRef)(trimmed);
244
248
  if (!parsed) {
245
249
  errors.push(`${context} remix must use canonical version ref format.`);
246
250
  return undefined;
@@ -249,7 +253,7 @@ function normalizeCatalogueRemixRef(value, expectedKind, errors, context) {
249
253
  errors.push(`${context} remix must point to a ${expectedKind} version.`);
250
254
  return undefined;
251
255
  }
252
- return (0, types_1.formatContentVersionRef)(parsed);
256
+ return (0, types_2.formatContentVersionRef)(parsed);
253
257
  }
254
258
  function normalizeCatalogueRelations(value, errors, context) {
255
259
  if (!Array.isArray(value)) {
@@ -291,12 +295,12 @@ function normalizeCatalogueAppDependencyAssets(value, errors, context) {
291
295
  errors.push(`${context} uses.assets[${index}] must be a non-empty asset ref string or { ref, runtimeKey } object.`);
292
296
  continue;
293
297
  }
294
- const parsed = (0, types_1.parseContentVersionRef)(ref);
298
+ const parsed = (0, types_2.parseContentVersionRef)(ref);
295
299
  if (!parsed || parsed.kind !== "asset") {
296
300
  errors.push(`${context} uses.assets[${index}] must be a canonical asset version ref.`);
297
301
  continue;
298
302
  }
299
- const normalizedRef = (0, types_1.formatContentVersionRef)(parsed);
303
+ const normalizedRef = (0, types_2.formatContentVersionRef)(parsed);
300
304
  if (seenRefs.has(normalizedRef)) {
301
305
  errors.push(`${context} uses.assets contains duplicate ref "${normalizedRef}".`);
302
306
  continue;
@@ -335,12 +339,12 @@ function normalizeCatalogueAppDependencyPacks(value, errors, context) {
335
339
  errors.push(`${context} uses.packs[${index}] must be a non-empty pack version ref string. Use "pack:playdrop/name@version", not { ref, runtimeKey }.`);
336
340
  continue;
337
341
  }
338
- const parsed = (0, types_1.parseContentVersionRef)(entry.trim());
342
+ const parsed = (0, types_2.parseContentVersionRef)(entry.trim());
339
343
  if (!parsed || parsed.kind !== "pack") {
340
344
  errors.push(`${context} uses.packs[${index}] must be a canonical pack version ref.`);
341
345
  continue;
342
346
  }
343
- const normalizedRef = (0, types_1.formatContentVersionRef)(parsed);
347
+ const normalizedRef = (0, types_2.formatContentVersionRef)(parsed);
344
348
  if (seenRefs.has(normalizedRef)) {
345
349
  errors.push(`${context} uses.packs contains duplicate ref "${normalizedRef}".`);
346
350
  continue;
@@ -375,7 +379,7 @@ function normalizeCatalogueTags(value, errors, context) {
375
379
  }
376
380
  let normalizedRef = "";
377
381
  try {
378
- normalizedRef = (0, types_1.normalizeTagRef)(entry);
382
+ normalizedRef = (0, types_2.normalizeTagRef)(entry);
379
383
  }
380
384
  catch {
381
385
  errors.push(`${context} tags[${index}] must use group-slug/tag-slug format.`);
@@ -406,8 +410,8 @@ function normalizeCatalogueAllowedTargetKinds(value, errors, context) {
406
410
  for (let index = 0; index < value.length; index += 1) {
407
411
  const entry = value[index];
408
412
  const normalizedKind = typeof entry === "string" ? entry.trim().toUpperCase() : "";
409
- if (!types_1.SAVED_ITEM_KIND_VALUES.includes(normalizedKind)) {
410
- errors.push(`${context} allowedTargetKinds[${index}] must be one of ${types_1.SAVED_ITEM_KIND_VALUES.join(", ")}.`);
413
+ if (!types_2.SAVED_ITEM_KIND_VALUES.includes(normalizedKind)) {
414
+ errors.push(`${context} allowedTargetKinds[${index}] must be one of ${types_2.SAVED_ITEM_KIND_VALUES.join(", ")}.`);
411
415
  continue;
412
416
  }
413
417
  const savedItemKind = normalizedKind;
@@ -626,11 +630,11 @@ function normalizeAssetVisibilityValue(raw, errors, label) {
626
630
  }
627
631
  return normalized;
628
632
  }
629
- function normalizeContentLicenseValue(raw, errors, label, allowedValues, defaultValue = types_1.DEFAULT_CONTENT_LICENSE) {
633
+ function normalizeContentLicenseValue(raw, errors, label, allowedValues, defaultValue = types_2.DEFAULT_CONTENT_LICENSE) {
630
634
  if (raw === undefined || raw === null || (typeof raw === "string" && raw.trim().length === 0)) {
631
635
  return defaultValue;
632
636
  }
633
- const parsed = (0, types_1.parseContentLicense)(raw);
637
+ const parsed = (0, types_2.parseContentLicense)(raw);
634
638
  if (!parsed || !allowedValues.has(parsed)) {
635
639
  errors.push(`[${label}] license must be one of ${Array.from(allowedValues).join(", ")}.`);
636
640
  return null;
@@ -670,8 +674,8 @@ function normalizeAssetSubcategoryValue(raw, category, errors, label) {
670
674
  errors.push(`${label} must define subcategory.`);
671
675
  return null;
672
676
  }
673
- const normalized = (0, types_1.normalizeAssetSubcategory)(raw);
674
- if (!(0, types_1.isValidAssetSubcategorySlug)(normalized)) {
677
+ const normalized = (0, types_2.normalizeAssetSubcategory)(raw);
678
+ if (!(0, types_2.isValidAssetSubcategorySlug)(normalized)) {
675
679
  errors.push(`${label} uses invalid subcategory "${raw}".`);
676
680
  return null;
677
681
  }
@@ -740,7 +744,7 @@ function validateAchievementDefinitions(value, catalogueDir, errors) {
740
744
  return;
741
745
  }
742
746
  const key = typeof entry.key === "string" ? entry.key.trim() : "";
743
- if (!(0, types_1.isPlayerMetaKey)(key)) {
747
+ if (!(0, types_2.isPlayerMetaKey)(key)) {
744
748
  errors.push(`achievements[${index}].key must match the player meta key format`);
745
749
  return;
746
750
  }
@@ -759,15 +763,15 @@ function validateAchievementDefinitions(value, catalogueDir, errors) {
759
763
  errors.push(`achievements[${index}].description must be a non-empty string`);
760
764
  return;
761
765
  }
762
- const kind = (0, types_1.parseAppAchievementKind)(entry.kind);
766
+ const kind = (0, types_2.parseAppAchievementKind)(entry.kind);
763
767
  if (!kind) {
764
- errors.push(`achievements[${index}].kind must be one of ${types_1.APP_ACHIEVEMENT_KIND_VALUES.join(", ")}`);
768
+ errors.push(`achievements[${index}].kind must be one of ${types_2.APP_ACHIEVEMENT_KIND_VALUES.join(", ")}`);
765
769
  return;
766
770
  }
767
771
  const hidden = Boolean(entry.hidden);
768
- const status = (0, types_1.parsePlayerMetaDefinitionStatus)(entry.status ?? "ACTIVE");
772
+ const status = (0, types_2.parsePlayerMetaDefinitionStatus)(entry.status ?? "ACTIVE");
769
773
  if (!status) {
770
- errors.push(`achievements[${index}].status must be one of ${types_1.PLAYER_META_DEFINITION_STATUS_VALUES.join(", ")}`);
774
+ errors.push(`achievements[${index}].status must be one of ${types_2.PLAYER_META_DEFINITION_STATUS_VALUES.join(", ")}`);
771
775
  return;
772
776
  }
773
777
  const icon = typeof entry.icon === "string" ? entry.icon.trim() : "";
@@ -828,7 +832,7 @@ function validateLeaderboardDefinitions(value, errors) {
828
832
  return;
829
833
  }
830
834
  const key = typeof entry.key === "string" ? entry.key.trim() : "";
831
- if (!(0, types_1.isPlayerMetaKey)(key)) {
835
+ if (!(0, types_2.isPlayerMetaKey)(key)) {
832
836
  errors.push(`leaderboards[${index}].key must match the player meta key format`);
833
837
  return;
834
838
  }
@@ -847,23 +851,23 @@ function validateLeaderboardDefinitions(value, errors) {
847
851
  errors.push(`leaderboards[${index}].description must be a non-empty string`);
848
852
  return;
849
853
  }
850
- const scoreType = (0, types_1.parseAppLeaderboardScoreType)(entry.scoreType);
854
+ const scoreType = (0, types_2.parseAppLeaderboardScoreType)(entry.scoreType);
851
855
  if (!scoreType) {
852
- errors.push(`leaderboards[${index}].scoreType must be one of ${types_1.APP_LEADERBOARD_SCORE_TYPE_VALUES.join(", ")}`);
856
+ errors.push(`leaderboards[${index}].scoreType must be one of ${types_2.APP_LEADERBOARD_SCORE_TYPE_VALUES.join(", ")}`);
853
857
  return;
854
858
  }
855
- const sort = (0, types_1.parseAppLeaderboardSort)(entry.sort);
859
+ const sort = (0, types_2.parseAppLeaderboardSort)(entry.sort);
856
860
  if (!sort) {
857
- errors.push(`leaderboards[${index}].sort must be one of ${types_1.APP_LEADERBOARD_SORT_VALUES.join(", ")}`);
861
+ errors.push(`leaderboards[${index}].sort must be one of ${types_2.APP_LEADERBOARD_SORT_VALUES.join(", ")}`);
858
862
  return;
859
863
  }
860
- if (!(0, types_1.isValidLeaderboardDefinitionCombination)(scoreType, sort)) {
864
+ if (!(0, types_2.isValidLeaderboardDefinitionCombination)(scoreType, sort)) {
861
865
  errors.push(`leaderboards[${index}] must use INTEGER + DESC or TIME_MS + ASC`);
862
866
  return;
863
867
  }
864
- const status = (0, types_1.parsePlayerMetaDefinitionStatus)(entry.status ?? "ACTIVE");
868
+ const status = (0, types_2.parsePlayerMetaDefinitionStatus)(entry.status ?? "ACTIVE");
865
869
  if (!status) {
866
- errors.push(`leaderboards[${index}].status must be one of ${types_1.PLAYER_META_DEFINITION_STATUS_VALUES.join(", ")}`);
870
+ errors.push(`leaderboards[${index}].status must be one of ${types_2.PLAYER_META_DEFINITION_STATUS_VALUES.join(", ")}`);
867
871
  return;
868
872
  }
869
873
  const unitLabel = typeof entry.unitLabel === "string" && entry.unitLabel.trim().length > 0
@@ -892,9 +896,9 @@ function validateAppMetadata(entry) {
892
896
  const color = validateOptionalColor(entry.color, Object.prototype.hasOwnProperty.call(entry, "color"), errors);
893
897
  let type;
894
898
  if (typeof entry.type === "string" && entry.type.trim()) {
895
- const parsedType = (0, types_1.parseAppType)(entry.type.trim());
899
+ const parsedType = (0, types_2.parseAppType)(entry.type.trim());
896
900
  if (!parsedType) {
897
- errors.push(`type must be one of ${types_1.APP_TYPE_VALUES.join(", ")}`);
901
+ errors.push(`type must be one of ${types_2.APP_TYPE_VALUES.join(", ")}`);
898
902
  }
899
903
  else {
900
904
  type = parsedType;
@@ -909,8 +913,8 @@ function validateAppMetadata(entry) {
909
913
  if (typeof entry.subtitle !== "string" || entry.subtitle.trim().length === 0) {
910
914
  errors.push("subtitle must be a non-empty string when provided");
911
915
  }
912
- else if (entry.subtitle.trim().length > types_1.APP_SUBTITLE_MAX_LENGTH) {
913
- errors.push(`subtitle must be ${types_1.APP_SUBTITLE_MAX_LENGTH} characters or fewer`);
916
+ else if (entry.subtitle.trim().length > types_2.APP_SUBTITLE_MAX_LENGTH) {
917
+ errors.push(`subtitle must be ${types_2.APP_SUBTITLE_MAX_LENGTH} characters or fewer`);
914
918
  }
915
919
  else {
916
920
  subtitle = entry.subtitle.trim();
@@ -951,7 +955,7 @@ function validateAppMetadata(entry) {
951
955
  }
952
956
  let primarySurface;
953
957
  if (Object.prototype.hasOwnProperty.call(entry, "primarySurface")) {
954
- primarySurface = (0, types_1.parseAppSurface)(entry.primarySurface) ?? undefined;
958
+ primarySurface = (0, types_2.parseAppSurface)(entry.primarySurface) ?? undefined;
955
959
  if (!primarySurface) {
956
960
  errors.push("primarySurface must be DESKTOP, MOBILE_LANDSCAPE, or MOBILE_PORTRAIT");
957
961
  }
@@ -961,7 +965,7 @@ function validateAppMetadata(entry) {
961
965
  }
962
966
  let playtestTapes;
963
967
  if (Object.prototype.hasOwnProperty.call(entry, "playtestTapes")) {
964
- const normalized = (0, types_1.normalizeAppPlaytestTapes)(entry.playtestTapes, {
968
+ const normalized = (0, types_2.normalizeAppPlaytestTapes)(entry.playtestTapes, {
965
969
  surfaceTargets: surfaceTargetsList,
966
970
  requireComplete: true,
967
971
  });
@@ -1168,8 +1172,8 @@ function buildAppTasks(rootDir, catalogues, options) {
1168
1172
  let versionVisibility;
1169
1173
  const rawVisibility = typeof entry.visibility === "string" ? entry.visibility.trim().toUpperCase() : "";
1170
1174
  if (rawVisibility) {
1171
- if (!types_1.APP_VERSION_VISIBILITY_VALUES.includes(rawVisibility)) {
1172
- errors.push(`[${label}] Skipping ${rawName}: visibility must be one of ${types_1.APP_VERSION_VISIBILITY_VALUES.join(", ")}.`);
1175
+ if (!types_2.APP_VERSION_VISIBILITY_VALUES.includes(rawVisibility)) {
1176
+ errors.push(`[${label}] Skipping ${rawName}: visibility must be one of ${types_2.APP_VERSION_VISIBILITY_VALUES.join(", ")}.`);
1173
1177
  continue;
1174
1178
  }
1175
1179
  versionVisibility = rawVisibility;
@@ -1177,8 +1181,8 @@ function buildAppTasks(rootDir, catalogues, options) {
1177
1181
  let hostingMode;
1178
1182
  const rawHostingMode = typeof entry.hostingMode === "string" ? entry.hostingMode.trim().toUpperCase() : "";
1179
1183
  if (rawHostingMode) {
1180
- if (!types_1.APP_HOSTING_MODE_VALUES.includes(rawHostingMode)) {
1181
- errors.push(`[${label}] Skipping ${rawName}: hostingMode must be one of ${types_1.APP_HOSTING_MODE_VALUES.join(", ")}.`);
1184
+ if (!types_2.APP_HOSTING_MODE_VALUES.includes(rawHostingMode)) {
1185
+ errors.push(`[${label}] Skipping ${rawName}: hostingMode must be one of ${types_2.APP_HOSTING_MODE_VALUES.join(", ")}.`);
1182
1186
  continue;
1183
1187
  }
1184
1188
  hostingMode = rawHostingMode;
@@ -1186,8 +1190,8 @@ function buildAppTasks(rootDir, catalogues, options) {
1186
1190
  let authMode;
1187
1191
  const rawAuthMode = typeof entry.authMode === "string" ? entry.authMode.trim().toUpperCase() : "";
1188
1192
  if (rawAuthMode) {
1189
- if (!types_1.APP_AUTH_MODE_VALUES.includes(rawAuthMode)) {
1190
- errors.push(`[${label}] Skipping ${rawName}: authMode must be one of ${types_1.APP_AUTH_MODE_VALUES.join(", ")}.`);
1193
+ if (!types_2.APP_AUTH_MODE_VALUES.includes(rawAuthMode)) {
1194
+ errors.push(`[${label}] Skipping ${rawName}: authMode must be one of ${types_2.APP_AUTH_MODE_VALUES.join(", ")}.`);
1191
1195
  continue;
1192
1196
  }
1193
1197
  authMode = rawAuthMode;
@@ -1245,8 +1249,8 @@ function buildAppTasks(rootDir, catalogues, options) {
1245
1249
  let controllerMode;
1246
1250
  const rawControllerMode = typeof entry.controllerMode === "string" ? entry.controllerMode.trim().toUpperCase() : "";
1247
1251
  if (rawControllerMode) {
1248
- if (!types_1.APP_CONTROLLER_MODE_VALUES.includes(rawControllerMode)) {
1249
- errors.push(`[${label}] Skipping ${rawName}: controllerMode must be one of ${types_1.APP_CONTROLLER_MODE_VALUES.join(", ")}.`);
1252
+ if (!types_2.APP_CONTROLLER_MODE_VALUES.includes(rawControllerMode)) {
1253
+ errors.push(`[${label}] Skipping ${rawName}: controllerMode must be one of ${types_2.APP_CONTROLLER_MODE_VALUES.join(", ")}.`);
1250
1254
  continue;
1251
1255
  }
1252
1256
  controllerMode = rawControllerMode;
@@ -1293,7 +1297,7 @@ function buildAppTasks(rootDir, catalogues, options) {
1293
1297
  errors.push(`[${label}] Skipping ${rawName}: editorSupported can only be true for HOSTED apps.`);
1294
1298
  continue;
1295
1299
  }
1296
- const defaultLicense = (hostingMode ?? "HOSTED") === "EXTERNAL" ? "CLOSED" : types_1.DEFAULT_CONTENT_LICENSE;
1300
+ const defaultLicense = (hostingMode ?? "HOSTED") === "EXTERNAL" ? "CLOSED" : types_2.DEFAULT_CONTENT_LICENSE;
1297
1301
  const license = normalizeContentLicenseValue(entry.license, errors, `${label} app "${rawName}"`, APP_ALLOWED_LICENSES, defaultLicense);
1298
1302
  if (!license) {
1299
1303
  continue;
@@ -1382,6 +1386,22 @@ function buildAppTasks(rootDir, catalogues, options) {
1382
1386
  }
1383
1387
  }
1384
1388
  }
1389
+ if (rawListing.socialLandscape !== undefined) {
1390
+ const socialPath = rawListing.socialLandscape;
1391
+ if (typeof socialPath !== "string" || !socialPath.trim()) {
1392
+ errors.push(`[${label}] invalid listing.socialLandscape`);
1393
+ listingValidationFailed = true;
1394
+ }
1395
+ else {
1396
+ const failure = ([".jpg", ".jpeg"].includes((0, node_path_1.extname)(socialPath).toLowerCase()) ? undefined : validateListingAssetExtension(socialPath, "listing.socialLandscape", "image")) ?? validateListingAssetPath(file.directory, socialPath, "listing.socialLandscape");
1397
+ if (failure) {
1398
+ errors.push(failure);
1399
+ listingValidationFailed = true;
1400
+ }
1401
+ else
1402
+ resolvedListing.socialLandscapePath = resolveListingAssetPath(file.directory, socialPath);
1403
+ }
1404
+ }
1385
1405
  for (const mediaField of [
1386
1406
  {
1387
1407
  value: rawListing.screenshotsPortrait,
@@ -1461,7 +1481,7 @@ function buildAppTasks(rootDir, catalogues, options) {
1461
1481
  }
1462
1482
  let design;
1463
1483
  if (Object.prototype.hasOwnProperty.call(entry, "design")) {
1464
- const parsedDesign = (0, types_1.normalizeAppVersionDesign)(entry.design);
1484
+ const parsedDesign = (0, types_2.normalizeAppVersionDesign)(entry.design);
1465
1485
  if (!parsedDesign.ok) {
1466
1486
  errors.push(`[${label}] App "${rawName}" ${parsedDesign.error}`);
1467
1487
  continue;
@@ -1470,7 +1490,7 @@ function buildAppTasks(rootDir, catalogues, options) {
1470
1490
  }
1471
1491
  let players;
1472
1492
  if (Object.prototype.hasOwnProperty.call(entry, "players")) {
1473
- const parsedPlayers = (0, types_1.normalizeAppPlayers)(entry.players);
1493
+ const parsedPlayers = (0, types_2.normalizeAppPlayers)(entry.players);
1474
1494
  if (!parsedPlayers.ok) {
1475
1495
  errors.push(`[${label}] App "${rawName}" ${parsedPlayers.error}`);
1476
1496
  continue;
@@ -1496,7 +1516,7 @@ function buildAppTasks(rootDir, catalogues, options) {
1496
1516
  }
1497
1517
  let creation;
1498
1518
  if (Object.prototype.hasOwnProperty.call(entry, "creation")) {
1499
- const parsedCreation = (0, types_1.normalizeAppCreationMetadata)(entry.creation);
1519
+ const parsedCreation = (0, types_2.normalizeAppCreationMetadata)(entry.creation);
1500
1520
  if (!parsedCreation.ok) {
1501
1521
  errors.push(`[${label}] App "${rawName}" ${parsedCreation.error}`);
1502
1522
  continue;
@@ -1524,7 +1544,7 @@ function buildAppTasks(rootDir, catalogues, options) {
1524
1544
  }
1525
1545
  let tweaks;
1526
1546
  if (Object.prototype.hasOwnProperty.call(entry, "tweaks")) {
1527
- const parsedTweaks = (0, types_1.normalizeAppTweaks)(entry.tweaks);
1547
+ const parsedTweaks = (0, types_2.normalizeAppTweaks)(entry.tweaks);
1528
1548
  if (!parsedTweaks.ok) {
1529
1549
  errors.push(`[${label}] App "${rawName}" ${parsedTweaks.error}`);
1530
1550
  continue;
@@ -1695,8 +1715,89 @@ function buildAppTasks(rootDir, catalogues, options) {
1695
1715
  if (errors.length > runtimeValidationErrorCount) {
1696
1716
  continue;
1697
1717
  }
1718
+ let listingLocalizations = [];
1719
+ let sourceLocale = null;
1720
+ let listingSourceHash;
1721
+ try {
1722
+ sourceLocale = (0, types_1.normalizeAppListingSourceLocale)(entry.sourceLocale);
1723
+ const rawLocalizations = entry.listingLocalizations;
1724
+ if (rawLocalizations !== undefined && (!rawLocalizations || typeof rawLocalizations !== "object" || Array.isArray(rawLocalizations)))
1725
+ throw new Error("invalid_listing_localizations");
1726
+ for (const [locale, value] of Object.entries(rawLocalizations ?? {})) {
1727
+ if (!value || typeof value !== "object" || Array.isArray(value))
1728
+ throw new Error("invalid_listing_localization");
1729
+ const localized = value;
1730
+ const allowedFields = new Set(["displayName", "subtitle", "description", "controls", "makingOf", "releaseNotes", "sourceHash", "listing"]);
1731
+ const unsupported = Object.keys(localized).filter((key) => !allowedFields.has(key));
1732
+ if (unsupported.length)
1733
+ throw new Error(`unsupported_listing_localization_fields:${locale}:${unsupported.join(",")}`);
1734
+ (0, types_1.normalizeAppListingLocalizations)([{ ...localized, locale }], sourceLocale);
1735
+ const media = localized.listing ?? {};
1736
+ if (!media || typeof media !== "object" || Array.isArray(media))
1737
+ throw new Error("invalid_listing_media");
1738
+ const allowedMedia = new Set([...types_1.APP_LISTING_PATH_FIELDS.map((field) => field.replace(/Path$/, "")), ...types_1.APP_LISTING_ARRAY_FIELDS]);
1739
+ const unsupportedMedia = Object.keys(media).filter((key) => !allowedMedia.has(key));
1740
+ if (unsupportedMedia.length)
1741
+ throw new Error(`unsupported_listing_localization_media_fields:${locale}:${unsupportedMedia.join(",")}`);
1742
+ for (const field of types_1.APP_LISTING_ARRAY_FIELDS) {
1743
+ const entries = media[field];
1744
+ if (entries === undefined)
1745
+ continue;
1746
+ if (!Array.isArray(entries))
1747
+ throw new Error(`invalid_listing_${field}`);
1748
+ for (const entry of entries) {
1749
+ if (entry && typeof entry === "object" && !Array.isArray(entry)) {
1750
+ const unsupported = Object.keys(entry).filter((key) => !["path", "slug", "title", "alt", "caption", "description"].includes(key));
1751
+ if (unsupported.length)
1752
+ throw new Error(`unsupported_listing_localization_media_fields:${locale}:${field}:${unsupported.join(",")}`);
1753
+ }
1754
+ }
1755
+ }
1756
+ }
1757
+ if (options.listingAssets !== "ignore") {
1758
+ listingLocalizations = (0, types_1.normalizeAppListingLocalizations)(Object.entries(rawLocalizations ?? {}).map(([locale, value]) => {
1759
+ if (!value || typeof value !== "object" || Array.isArray(value))
1760
+ throw new Error("invalid_listing_localization");
1761
+ const localized = value;
1762
+ const media = localized.listing ?? {};
1763
+ if (!media || typeof media !== "object" || Array.isArray(media))
1764
+ throw new Error("invalid_listing_media");
1765
+ const paths = {};
1766
+ const metadata = [];
1767
+ for (const field of types_1.APP_LISTING_PATH_FIELDS) {
1768
+ const key = field.replace(/Path$/, "");
1769
+ const value = media[key];
1770
+ if (value === undefined || value === null)
1771
+ continue;
1772
+ if (typeof value !== "string" || !value.trim())
1773
+ throw new Error(`invalid_listing_${key}`);
1774
+ const failure = (field === "socialLandscapePath" && [".jpg", ".jpeg"].includes((0, node_path_1.extname)(value).toLowerCase()) ? undefined : validateListingAssetExtension(value, `${locale}.listing.${key}`, "image")) ?? validateListingAssetPath(file.directory, value, `${locale}.listing.${key}`);
1775
+ if (failure)
1776
+ throw new Error(failure);
1777
+ paths[field] = resolveListingAssetPath(file.directory, value);
1778
+ }
1779
+ for (const field of types_1.APP_LISTING_ARRAY_FIELDS) {
1780
+ const resolved = resolveListingMediaEntries({ value: media[field], baseDir: file.directory, fieldName: `${locale}.listing.${field}`, kind: field.startsWith("videos") ? "video" : "image", fileKeyPrefix: field });
1781
+ if (resolved.error)
1782
+ throw new Error(resolved.error);
1783
+ paths[field] = resolved.paths;
1784
+ metadata.push(...resolved.metadata);
1785
+ }
1786
+ return { ...localized, ...paths, listingMedia: metadata, locale };
1787
+ }), sourceLocale);
1788
+ if (sourceLocale)
1789
+ listingSourceHash = calculateCatalogueListingSourceHash({ ...metadata, sourceLocale, listing, controls, makingOf, releaseNotes });
1790
+ }
1791
+ }
1792
+ catch (error) {
1793
+ errors.push(`[${label}] ${rawName}: ${error instanceof Error ? error.message : String(error)}`);
1794
+ continue;
1795
+ }
1698
1796
  tasks.push({
1699
1797
  kind: "app",
1798
+ sourceLocale,
1799
+ listingSourceHash,
1800
+ listingLocalizations,
1700
1801
  name: rawName,
1701
1802
  cataloguePath: label,
1702
1803
  catalogueAbsolutePath: file.absolutePath,
@@ -1820,8 +1921,8 @@ function buildAssetSpecTasks(catalogues) {
1820
1921
  let validationKind = loadedContract.validationKind;
1821
1922
  if (typeof entry.validationKind === "string" && entry.validationKind.trim().length > 0) {
1822
1923
  const normalizedValidationKind = entry.validationKind.trim().toUpperCase();
1823
- if (!types_1.ASSET_SPEC_VALIDATION_KIND_VALUES.includes(normalizedValidationKind)) {
1824
- errors.push(`[${label}] Asset spec "${name}@${version}" validationKind must be one of ${types_1.ASSET_SPEC_VALIDATION_KIND_VALUES.join(", ")}.`);
1924
+ if (!types_2.ASSET_SPEC_VALIDATION_KIND_VALUES.includes(normalizedValidationKind)) {
1925
+ errors.push(`[${label}] Asset spec "${name}@${version}" validationKind must be one of ${types_2.ASSET_SPEC_VALIDATION_KIND_VALUES.join(", ")}.`);
1825
1926
  continue;
1826
1927
  }
1827
1928
  if (normalizedValidationKind !== loadedContract.validationKind) {
@@ -1833,8 +1934,8 @@ function buildAssetSpecTasks(catalogues) {
1833
1934
  let status = "ACTIVE";
1834
1935
  if (typeof entry.status === "string" && entry.status.trim().length > 0) {
1835
1936
  const normalizedStatus = entry.status.trim().toUpperCase();
1836
- if (!types_1.ASSET_SPEC_STATUS_VALUES.includes(normalizedStatus)) {
1837
- errors.push(`[${label}] Asset spec "${name}@${version}" status must be one of ${types_1.ASSET_SPEC_STATUS_VALUES.join(", ")}.`);
1937
+ if (!types_2.ASSET_SPEC_STATUS_VALUES.includes(normalizedStatus)) {
1938
+ errors.push(`[${label}] Asset spec "${name}@${version}" status must be one of ${types_2.ASSET_SPEC_STATUS_VALUES.join(", ")}.`);
1838
1939
  continue;
1839
1940
  }
1840
1941
  status = normalizedStatus;
@@ -2012,7 +2113,7 @@ function buildAssetTasks(catalogues, assetSpecTasks) {
2012
2113
  assetSpecContract = directAssetSpecTask.contract;
2013
2114
  }
2014
2115
  else {
2015
- const parsedAssetSpec = (0, types_1.parseAssetSpecVersionRef)(assetSpec);
2116
+ const parsedAssetSpec = (0, types_2.parseAssetSpecVersionRef)(assetSpec);
2016
2117
  if (parsedAssetSpec) {
2017
2118
  const fallbackAssetSpecTask = uniqueAssetSpecTaskByNameVersion.get(`${parsedAssetSpec.name}@${parsedAssetSpec.version}`);
2018
2119
  if (fallbackAssetSpecTask) {
@@ -2194,8 +2295,8 @@ function buildAssetPackTasks(catalogues) {
2194
2295
  let hostingMode;
2195
2296
  const rawHostingMode = typeof entry.hostingMode === "string" ? entry.hostingMode.trim().toUpperCase() : "";
2196
2297
  if (rawHostingMode) {
2197
- if (!types_1.APP_HOSTING_MODE_VALUES.includes(rawHostingMode)) {
2198
- errors.push(`[${label}] Asset pack "${name}" hostingMode must be one of ${types_1.APP_HOSTING_MODE_VALUES.join(", ")}.`);
2298
+ if (!types_2.APP_HOSTING_MODE_VALUES.includes(rawHostingMode)) {
2299
+ errors.push(`[${label}] Asset pack "${name}" hostingMode must be one of ${types_2.APP_HOSTING_MODE_VALUES.join(", ")}.`);
2199
2300
  continue;
2200
2301
  }
2201
2302
  hostingMode = rawHostingMode;
@@ -2440,7 +2541,7 @@ function resolveCatalogueTagGroups(rootDir) {
2440
2541
  continue;
2441
2542
  }
2442
2543
  const slug = typeof entry.slug === "string" ? entry.slug.trim().toLowerCase() : "";
2443
- if (!slug || !(0, types_1.isValidTagSlug)(slug)) {
2544
+ if (!slug || !(0, types_2.isValidTagSlug)(slug)) {
2444
2545
  errors.push(`[${label}] tagGroups[${index}] slug must use lowercase slug format.`);
2445
2546
  continue;
2446
2547
  }
@@ -2468,7 +2569,7 @@ function resolveCatalogueTagGroups(rootDir) {
2468
2569
  continue;
2469
2570
  }
2470
2571
  const tagSlug = typeof rawTag.slug === "string" ? rawTag.slug.trim().toLowerCase() : "";
2471
- if (!tagSlug || !(0, types_1.isValidTagSlug)(tagSlug)) {
2572
+ if (!tagSlug || !(0, types_2.isValidTagSlug)(tagSlug)) {
2472
2573
  errors.push(`[${label}] Tag group "${slug}" tags[${tagIndex}] slug must use lowercase slug format.`);
2473
2574
  continue;
2474
2575
  }
@@ -2705,3 +2806,17 @@ function findAppTaskByFile(htmlPath, options = {}) {
2705
2806
  }
2706
2807
  return { task: undefined, warnings: [], errors: [] };
2707
2808
  }
2809
+ function catalogueListingContent(task) {
2810
+ const listing = task.listing;
2811
+ return (0, types_1.normalizeAppListingContent)({ ...task,
2812
+ iconPath: listing?.iconPath, heroPortraitPath: listing?.heroPortraitPath, heroLandscapePath: listing?.heroLandscapePath, socialLandscapePath: listing?.socialLandscapePath,
2813
+ screenshotsPortrait: listing?.screenshotPortraitPaths, screenshotsLandscape: listing?.screenshotLandscapePaths,
2814
+ videosPortrait: listing?.videoPortraitPaths, videosLandscape: listing?.videoLandscapePaths, listingMedia: listing?.mediaMetadata,
2815
+ });
2816
+ }
2817
+ function calculateCatalogueListingSourceHash(task) {
2818
+ const content = catalogueListingContent(task);
2819
+ const paths = [...types_1.APP_LISTING_PATH_FIELDS.flatMap((field) => content[field] ? [content[field]] : []), ...types_1.APP_LISTING_ARRAY_FIELDS.flatMap((field) => content[field])];
2820
+ const hashes = new Map(paths.map((file) => [file, (0, node_crypto_1.createHash)("sha256").update((0, node_fs_1.readFileSync)(file)).digest("hex")]));
2821
+ return (0, node_crypto_1.createHash)("sha256").update((0, types_1.canonicalAppListingSource)(content, task.sourceLocale ?? null, hashes)).digest("hex");
2822
+ }
@@ -545,6 +545,9 @@ async function ensureDevRouterRunning(port = exports.DEV_ROUTER_PORT) {
545
545
  }
546
546
  throw new Error(`dev_router_incompatible:port_${port}`);
547
547
  }
548
+ if (process.env.PLAYDROP_WORKER_CONTEXT === "1") {
549
+ throw new Error(`worker_dev_router_missing:port_${port}`);
550
+ }
548
551
  await startOwnedDevRouter(port);
549
552
  }
550
553
  // Managed Mac workers call this with their reserved slots before advertising
@@ -259,6 +259,7 @@ async function validate(pathOrName, options = {}) {
259
259
  entityType: task.kind,
260
260
  entityId,
261
261
  catalogue: task.cataloguePath,
262
+ ...(task.kind === "app" && task.listingSourceHash ? { detail: `listingSourceHash=${task.listingSourceHash}` } : {}),
262
263
  };
263
264
  results.push(entry);
264
265
  console.log((0, uploadLog_1.formatTaskLogLine)(entry));
@@ -366,6 +366,13 @@ async function setupGameSourceWorkspace(input) {
366
366
  repoPath: projectDir,
367
367
  listing: {
368
368
  version: source.listingSync.version,
369
+ sourceLocale: source.listingSync.sourceLocale,
370
+ listingSourceHash: source.listingSync.listingSourceHash,
371
+ listingLocalizations: source.listingSync.listingLocalizations,
372
+ listingMedia: source.listingSync.listingMedia,
373
+ controls: source.listingSync.controls,
374
+ makingOf: source.listingSync.makingOf,
375
+ releaseNotes: source.listingSync.releaseNotes,
369
376
  displayName: source.listingSync.displayName,
370
377
  subtitle: source.listingSync.subtitle,
371
378
  description: source.listingSync.description,
@@ -55,13 +55,28 @@ export declare function triggerGenerationWorkerFailpoint(input: {
55
55
  }): Promise<boolean>;
56
56
  export declare function configureFalCredential(workerHomeDir: string): Promise<void>;
57
57
  export declare function buildSandboxedCodexArgs(input: {
58
- sandbox: "read-only" | "workspace-write";
58
+ sandbox: "read-only" | "workspace-write" | "danger-full-access";
59
59
  outputPath: string;
60
60
  prompt: string;
61
61
  outputSchemaPath?: string;
62
62
  persistSession?: boolean;
63
+ model?: string;
64
+ reasoningEffort?: string;
65
+ imagePaths?: string[];
63
66
  }): string[];
64
67
  export declare function retainScreeningTranscript(codexHomeDir: string, taskId: number): Promise<void>;
68
+ export declare function runCodex(input: {
69
+ cwd: string;
70
+ prompt: string;
71
+ sandbox: "read-only" | "workspace-write" | "danger-full-access";
72
+ outputPath: string;
73
+ outputSchema?: Record<string, unknown>;
74
+ signal: AbortSignal;
75
+ screeningTaskId?: number;
76
+ model?: string;
77
+ reasoningEffort?: string;
78
+ imagePaths?: string[];
79
+ }): Promise<void>;
65
80
  export declare function falInput(request: CreateAiGenerationTaskRequest, resolvedInputs: Map<string, string>): Record<string, unknown>;
66
81
  export declare function selectFalOutputs(kind: CreateAiGenerationTaskRequest["kind"], value: Record<string, unknown>): {
67
82
  primary: FalFile;
@@ -49,6 +49,7 @@ exports.triggerGenerationWorkerFailpoint = triggerGenerationWorkerFailpoint;
49
49
  exports.configureFalCredential = configureFalCredential;
50
50
  exports.buildSandboxedCodexArgs = buildSandboxedCodexArgs;
51
51
  exports.retainScreeningTranscript = retainScreeningTranscript;
52
+ exports.runCodex = runCodex;
52
53
  exports.falInput = falInput;
53
54
  exports.selectFalOutputs = selectFalOutputs;
54
55
  exports.validatePrimary = validatePrimary;
@@ -400,10 +401,12 @@ function buildSandboxedCodexArgs(input) {
400
401
  "--sandbox",
401
402
  input.sandbox,
402
403
  "--model",
403
- CODEX_MODEL,
404
+ input.model ?? CODEX_MODEL,
404
405
  "-c",
405
- 'model_reasoning_effort="high"',
406
+ `model_reasoning_effort="${input.reasoningEffort ?? 'high'}"`,
406
407
  ];
408
+ for (const imagePath of input.imagePaths ?? [])
409
+ args.push('--image', imagePath);
407
410
  if (input.outputSchemaPath)
408
411
  args.push("--output-schema", input.outputSchemaPath);
409
412
  args.push("-o", input.outputPath, input.prompt);
@@ -448,6 +451,9 @@ async function runCodex(input) {
448
451
  prompt: input.prompt,
449
452
  persistSession: input.screeningTaskId !== undefined,
450
453
  ...(outputSchemaPath ? { outputSchemaPath } : {}),
454
+ ...(input.model ? { model: input.model } : {}),
455
+ ...(input.reasoningEffort ? { reasoningEffort: input.reasoningEffort } : {}),
456
+ ...(input.imagePaths ? { imagePaths: input.imagePaths } : {}),
451
457
  });
452
458
  const runtime = await prepareGenerationCodexRuntime();
453
459
  try {
@@ -0,0 +1,13 @@
1
+ import type { ApiClient } from '@playdrop/api-client';
2
+ import { type UgcProcessingResult, type WorkerUgcTaskAssignmentV5 } from '@playdrop/types';
3
+ export declare const UGC_PROCESSING_PROMPT: string;
4
+ export declare function normalizeWorkerUgcAssignment(value: unknown): WorkerUgcTaskAssignmentV5;
5
+ export declare function parseUgcModelOutput(text: string, claudeEnvelope?: boolean): {
6
+ results: UgcProcessingResult[];
7
+ };
8
+ export declare function runWorkerUgcTask(input: {
9
+ client: ApiClient;
10
+ assignment: WorkerUgcTaskAssignmentV5;
11
+ workerKey: string;
12
+ leaseToken: string;
13
+ }): Promise<void>;