@playdrop/playdrop-cli 0.5.2 → 0.5.3

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 (103) hide show
  1. package/config/client-meta.json +4 -4
  2. package/dist/apps/build.js +49 -6
  3. package/dist/apps/index.d.ts +2 -0
  4. package/dist/apps/index.js +2 -0
  5. package/dist/apps/upload.d.ts +2 -0
  6. package/dist/apps/upload.js +126 -28
  7. package/dist/assetSpecs.d.ts +16 -0
  8. package/dist/assetSpecs.js +263 -0
  9. package/dist/assets/model-artifacts.js +3 -0
  10. package/dist/catalogue.d.ts +57 -3
  11. package/dist/catalogue.js +342 -16
  12. package/dist/commands/ads.d.ts +8 -0
  13. package/dist/commands/ads.js +124 -0
  14. package/dist/commands/boosts.d.ts +25 -0
  15. package/dist/commands/boosts.js +209 -0
  16. package/dist/commands/browse.d.ts +6 -1
  17. package/dist/commands/browse.js +365 -124
  18. package/dist/commands/captureListing.d.ts +53 -0
  19. package/dist/commands/captureListing.js +804 -0
  20. package/dist/commands/create.d.ts +1 -0
  21. package/dist/commands/create.js +183 -3
  22. package/dist/commands/credits.d.ts +6 -0
  23. package/dist/commands/credits.js +47 -1
  24. package/dist/commands/detail.js +38 -4
  25. package/dist/commands/devServer.js +10 -5
  26. package/dist/commands/search.d.ts +5 -0
  27. package/dist/commands/search.js +139 -17
  28. package/dist/commands/tags.d.ts +7 -0
  29. package/dist/commands/tags.js +63 -0
  30. package/dist/commands/upload-content.d.ts +13 -3
  31. package/dist/commands/upload-content.js +86 -20
  32. package/dist/commands/upload.d.ts +2 -0
  33. package/dist/commands/upload.js +187 -11
  34. package/dist/commands/validate.js +163 -2
  35. package/dist/commands/versionsBrowse.js +128 -91
  36. package/dist/index.js +145 -3
  37. package/dist/refs.d.ts +2 -2
  38. package/dist/refs.js +13 -1
  39. package/dist/taskSelection.js +6 -3
  40. package/dist/taskUtils.d.ts +2 -2
  41. package/dist/taskUtils.js +1 -0
  42. package/dist/uploadLog.d.ts +1 -1
  43. package/dist/uploadLog.js +2 -2
  44. package/node_modules/@playdrop/ai-client/package.json +1 -1
  45. package/node_modules/@playdrop/api-client/dist/client.d.ts +131 -10
  46. package/node_modules/@playdrop/api-client/dist/client.d.ts.map +1 -1
  47. package/node_modules/@playdrop/api-client/dist/client.js +6 -0
  48. package/node_modules/@playdrop/api-client/dist/domains/admin.d.ts +9 -1
  49. package/node_modules/@playdrop/api-client/dist/domains/admin.d.ts.map +1 -1
  50. package/node_modules/@playdrop/api-client/dist/domains/admin.js +45 -0
  51. package/node_modules/@playdrop/api-client/dist/domains/apps.d.ts +3 -0
  52. package/node_modules/@playdrop/api-client/dist/domains/apps.d.ts.map +1 -1
  53. package/node_modules/@playdrop/api-client/dist/domains/apps.js +27 -0
  54. package/node_modules/@playdrop/api-client/dist/domains/asset-packs.d.ts +2 -0
  55. package/node_modules/@playdrop/api-client/dist/domains/asset-packs.d.ts.map +1 -1
  56. package/node_modules/@playdrop/api-client/dist/domains/asset-packs.js +16 -0
  57. package/node_modules/@playdrop/api-client/dist/domains/assets.d.ts +44 -2
  58. package/node_modules/@playdrop/api-client/dist/domains/assets.d.ts.map +1 -1
  59. package/node_modules/@playdrop/api-client/dist/domains/assets.js +260 -3
  60. package/node_modules/@playdrop/api-client/dist/domains/payments.d.ts +17 -1
  61. package/node_modules/@playdrop/api-client/dist/domains/payments.d.ts.map +1 -1
  62. package/node_modules/@playdrop/api-client/dist/domains/payments.js +173 -0
  63. package/node_modules/@playdrop/api-client/dist/domains/search.d.ts.map +1 -1
  64. package/node_modules/@playdrop/api-client/dist/domains/search.js +39 -11
  65. package/node_modules/@playdrop/api-client/dist/domains/tags.d.ts +34 -0
  66. package/node_modules/@playdrop/api-client/dist/domains/tags.d.ts.map +1 -0
  67. package/node_modules/@playdrop/api-client/dist/domains/tags.js +111 -0
  68. package/node_modules/@playdrop/api-client/dist/index.d.ts +61 -1
  69. package/node_modules/@playdrop/api-client/dist/index.d.ts.map +1 -1
  70. package/node_modules/@playdrop/api-client/dist/index.js +50 -0
  71. package/node_modules/@playdrop/api-client/package.json +1 -1
  72. package/node_modules/@playdrop/boxel-core/package.json +1 -1
  73. package/node_modules/@playdrop/boxel-three/package.json +1 -1
  74. package/node_modules/@playdrop/config/client-meta.json +4 -4
  75. package/node_modules/@playdrop/config/dist/src/constants.d.ts +11 -0
  76. package/node_modules/@playdrop/config/dist/src/constants.d.ts.map +1 -1
  77. package/node_modules/@playdrop/config/dist/src/constants.js +12 -1
  78. package/node_modules/@playdrop/config/dist/tsconfig.tsbuildinfo +1 -1
  79. package/node_modules/@playdrop/config/package.json +1 -1
  80. package/node_modules/@playdrop/types/dist/api.d.ts +346 -6
  81. package/node_modules/@playdrop/types/dist/api.d.ts.map +1 -1
  82. package/node_modules/@playdrop/types/dist/api.js +52 -1
  83. package/node_modules/@playdrop/types/dist/asset-pack.d.ts +7 -1
  84. package/node_modules/@playdrop/types/dist/asset-pack.d.ts.map +1 -1
  85. package/node_modules/@playdrop/types/dist/asset-spec-contract-meta-schema.json +86 -0
  86. package/node_modules/@playdrop/types/dist/asset-spec.d.ts +163 -0
  87. package/node_modules/@playdrop/types/dist/asset-spec.d.ts.map +1 -0
  88. package/node_modules/@playdrop/types/dist/asset-spec.js +101 -0
  89. package/node_modules/@playdrop/types/dist/asset.d.ts +23 -6
  90. package/node_modules/@playdrop/types/dist/asset.d.ts.map +1 -1
  91. package/node_modules/@playdrop/types/dist/asset.js +4 -1
  92. package/node_modules/@playdrop/types/dist/graph.d.ts +4 -2
  93. package/node_modules/@playdrop/types/dist/graph.d.ts.map +1 -1
  94. package/node_modules/@playdrop/types/dist/graph.js +9 -2
  95. package/node_modules/@playdrop/types/dist/index.d.ts +1 -0
  96. package/node_modules/@playdrop/types/dist/index.d.ts.map +1 -1
  97. package/node_modules/@playdrop/types/dist/index.js +1 -0
  98. package/node_modules/@playdrop/types/dist/version.d.ts +13 -0
  99. package/node_modules/@playdrop/types/dist/version.d.ts.map +1 -1
  100. package/node_modules/@playdrop/types/dist/version.js +21 -0
  101. package/node_modules/@playdrop/types/package.json +6 -1
  102. package/node_modules/@playdrop/vox-three/package.json +1 -1
  103. package/package.json +3 -1
@@ -1,16 +1,135 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.upload = upload;
4
+ const node_fs_1 = require("node:fs");
5
+ const node_path_1 = require("node:path");
4
6
  const types_1 = require("@playdrop/types");
5
7
  const apps_1 = require("../apps");
6
8
  const commandContext_1 = require("../commandContext");
7
9
  const http_1 = require("../http");
10
+ const catalogue_1 = require("../catalogue");
11
+ const messages_1 = require("../messages");
8
12
  const taskSelection_1 = require("../taskSelection");
9
13
  const taskUtils_1 = require("../taskUtils");
10
14
  const uploadLog_1 = require("../uploadLog");
11
15
  const upload_content_1 = require("./upload-content");
12
16
  const appUrls_1 = require("../appUrls");
13
17
  const upload_graph_1 = require("./upload-graph");
18
+ function normalizeSavedItemKinds(kinds) {
19
+ return [...new Set(Array.isArray(kinds) ? kinds : [])].sort();
20
+ }
21
+ function sameSavedItemKinds(left, right) {
22
+ const normalizedLeft = normalizeSavedItemKinds(left);
23
+ const normalizedRight = normalizeSavedItemKinds(right);
24
+ if (normalizedLeft.length !== normalizedRight.length) {
25
+ return false;
26
+ }
27
+ return normalizedLeft.every((value, index) => value === normalizedRight[index]);
28
+ }
29
+ function resolveCatalogueWorkspaceRoot(startPath) {
30
+ const absoluteStart = (0, node_path_1.resolve)(startPath);
31
+ const currentStats = (0, node_fs_1.existsSync)(absoluteStart) ? (0, node_fs_1.statSync)(absoluteStart) : null;
32
+ let currentDir = currentStats?.isFile() ? (0, node_path_1.dirname)(absoluteStart) : absoluteStart;
33
+ while (true) {
34
+ const parentDir = (0, node_path_1.dirname)(currentDir);
35
+ if (parentDir === currentDir) {
36
+ return currentDir;
37
+ }
38
+ const parentCataloguePath = (0, node_path_1.join)(parentDir, 'catalogue.json');
39
+ if (!(0, node_fs_1.existsSync)(parentCataloguePath)) {
40
+ return currentDir;
41
+ }
42
+ currentDir = parentDir;
43
+ }
44
+ }
45
+ function resolveUploadWorkspaceRoot(pathOrName, resolution) {
46
+ if (resolution === 'name') {
47
+ return resolveCatalogueWorkspaceRoot(process.cwd());
48
+ }
49
+ return resolveCatalogueWorkspaceRoot(pathOrName);
50
+ }
51
+ function buildTagGroupLogEntry(status, entityId, detail) {
52
+ return {
53
+ action: 'upload',
54
+ status,
55
+ entityType: 'tag-group',
56
+ entityId,
57
+ catalogue: 'catalogue:tag-groups',
58
+ detail,
59
+ };
60
+ }
61
+ function buildTagLogEntry(status, entityId, detail) {
62
+ return {
63
+ action: 'upload',
64
+ status,
65
+ entityType: 'tag',
66
+ entityId,
67
+ catalogue: 'catalogue:tag-groups',
68
+ detail,
69
+ };
70
+ }
71
+ async function syncTagGroupsFromCatalogue(client, groups) {
72
+ const entries = [];
73
+ if (groups.length === 0) {
74
+ return entries;
75
+ }
76
+ const live = await client.fetchAdminTagGroups();
77
+ const liveGroupsBySlug = new Map(live.groups.map((group) => [group.slug, group]));
78
+ for (const groupDefinition of groups) {
79
+ let liveGroup = liveGroupsBySlug.get(groupDefinition.slug) ?? null;
80
+ if (!liveGroup) {
81
+ const created = await client.createAdminTagGroup({
82
+ slug: groupDefinition.slug,
83
+ displayName: groupDefinition.displayName,
84
+ ...(groupDefinition.allowedTargetKinds ? { allowedTargetKinds: groupDefinition.allowedTargetKinds } : {}),
85
+ });
86
+ liveGroup = {
87
+ ...created.group,
88
+ tags: [],
89
+ };
90
+ liveGroupsBySlug.set(liveGroup.slug, liveGroup);
91
+ entries.push(buildTagGroupLogEntry('success', liveGroup.slug, `created "${liveGroup.displayName}"`));
92
+ }
93
+ else if (liveGroup.displayName !== groupDefinition.displayName
94
+ || !sameSavedItemKinds(liveGroup.allowedTargetKinds, groupDefinition.allowedTargetKinds)) {
95
+ const updated = await client.updateAdminTagGroup(groupDefinition.slug, {
96
+ displayName: groupDefinition.displayName,
97
+ allowedTargetKinds: normalizeSavedItemKinds(groupDefinition.allowedTargetKinds),
98
+ });
99
+ liveGroup = {
100
+ ...updated.group,
101
+ tags: liveGroup.tags,
102
+ };
103
+ liveGroupsBySlug.set(liveGroup.slug, liveGroup);
104
+ entries.push(buildTagGroupLogEntry('success', liveGroup.slug, `updated "${liveGroup.displayName}"`));
105
+ }
106
+ if (!liveGroup) {
107
+ throw new Error(`tag_group_sync_failed:${groupDefinition.slug}`);
108
+ }
109
+ const liveTagsBySlug = new Map(liveGroup.tags.map((tag) => [tag.slug, tag]));
110
+ for (const tagDefinition of groupDefinition.tags) {
111
+ const liveTag = liveTagsBySlug.get(tagDefinition.slug) ?? null;
112
+ const tagRef = `${groupDefinition.slug}/${tagDefinition.slug}`;
113
+ if (!liveTag) {
114
+ const created = await client.createAdminTag(groupDefinition.slug, {
115
+ slug: tagDefinition.slug,
116
+ displayName: tagDefinition.displayName,
117
+ });
118
+ liveTagsBySlug.set(created.tag.slug, created.tag);
119
+ entries.push(buildTagLogEntry('success', tagRef, `created "${created.tag.displayName}"`));
120
+ continue;
121
+ }
122
+ if (liveTag.displayName !== tagDefinition.displayName) {
123
+ const updated = await client.updateAdminTag(groupDefinition.slug, tagDefinition.slug, {
124
+ displayName: tagDefinition.displayName,
125
+ });
126
+ liveTagsBySlug.set(updated.tag.slug, updated.tag);
127
+ entries.push(buildTagLogEntry('success', tagRef, `updated "${updated.tag.displayName}"`));
128
+ }
129
+ }
130
+ }
131
+ return entries;
132
+ }
14
133
  function buildApiUnavailableMessage(apiBase) {
15
134
  if (apiBase && typeof apiBase === 'string') {
16
135
  return `Could not reach the Playdrop API at ${apiBase}. Check your internet connection, then ensure the server is running before retrying.`;
@@ -86,6 +205,9 @@ function appendOverviewLink(entry, task, portalBase, creator) {
86
205
  if (task.kind === 'app') {
87
206
  url = buildAppOverviewUrl(portalBase, creator, task);
88
207
  }
208
+ else if (task.kind === 'asset-spec') {
209
+ url = null;
210
+ }
89
211
  else if (task.kind === 'asset' || task.kind === 'embedded-asset') {
90
212
  url = buildAssetOverviewUrl(portalBase, creator, task.name);
91
213
  }
@@ -107,11 +229,18 @@ function pushLoggedEntry(results, entry) {
107
229
  console.log((0, uploadLog_1.formatTaskLogLine)(entry));
108
230
  }
109
231
  function buildUploadErrorDetail(error) {
110
- const message = typeof error?.message === 'string' && error.message.trim()
232
+ if (error instanceof types_1.ApiError && error.code === 'tag_clear_confirmation_required') {
233
+ return 'tag_clear_confirmation_required: This publish would remove existing live tags. Re-run with --clear-tags to confirm.';
234
+ }
235
+ const rawMessage = typeof error?.message === 'string' && error.message.trim()
111
236
  ? error.message.trim()
112
237
  : 'upload failed';
238
+ const message = rawMessage.replace(/^Upload failed for [^:]+:\s*/i, '');
239
+ if (/^[a-z0-9_]+:\s+/i.test(message)) {
240
+ return message;
241
+ }
113
242
  if (/status\s+413/i.test(message)) {
114
- return 'upload failed: asset bundle too large (HTTP 413). Reduce the size of your app files and try again.';
243
+ return 'upload_too_large: Upload exceeded the Playdrop HTTP 413 limit. Reduce the size of your bundle, source archive, or listing media and try again.';
115
244
  }
116
245
  if (!message.startsWith('upload failed')) {
117
246
  return `upload failed: ${message}`;
@@ -150,6 +279,8 @@ function appendTaskRelations(graphState, fromNodeId, relations, contextLabel) {
150
279
  async function uploadAppTask(state, task, taskCreator, options) {
151
280
  const { upload } = await (0, apps_1.runAppPipeline)(state.client, task, {
152
281
  skipEcs: options?.skipEcs,
282
+ skipReview: options?.skipReview,
283
+ clearTags: options?.clearTags,
153
284
  creatorUsername: taskCreator,
154
285
  });
155
286
  if (!upload.versionCreated || !upload.version) {
@@ -196,7 +327,18 @@ async function uploadAppTask(state, task, taskCreator, options) {
196
327
  appendOverviewLink(entry, task, state.portalBase, taskCreator);
197
328
  return entry;
198
329
  }
199
- async function uploadStandaloneAssetTask(state, task, taskCreator) {
330
+ async function uploadAssetSpecDefinitionTask(state, task, taskCreator) {
331
+ const uploaded = await (0, upload_content_1.uploadAssetSpecTask)(state.client, task, taskCreator);
332
+ return {
333
+ action: 'upload',
334
+ status: 'success',
335
+ entityType: 'asset-spec',
336
+ entityId: buildTaskEntityId(task, taskCreator),
337
+ catalogue: task.cataloguePath,
338
+ detail: `asset-spec:${uploaded.creatorUsername}/${uploaded.name}@${uploaded.version}`,
339
+ };
340
+ }
341
+ async function uploadStandaloneAssetTask(state, task, taskCreator, options) {
200
342
  const ownedPack = state.packPlanning.ownedPackByAssetKey.get((0, upload_content_1.buildAssetKey)(taskCreator, task.name));
201
343
  if (ownedPack) {
202
344
  return {
@@ -208,7 +350,9 @@ async function uploadStandaloneAssetTask(state, task, taskCreator) {
208
350
  detail: `staged with asset pack ${ownedPack.packTask.name}@${ownedPack.packTask.version}`,
209
351
  };
210
352
  }
211
- const uploaded = await (0, upload_content_1.uploadAssetTask)(state.client, task, undefined, taskCreator);
353
+ const uploaded = await (0, upload_content_1.uploadAssetTask)(state.client, task, undefined, taskCreator, {
354
+ clearTags: options?.clearTags,
355
+ });
212
356
  state.uploadedAssetsByKey.set(`${uploaded.creatorUsername}/${uploaded.name}`, uploaded);
213
357
  (0, upload_graph_1.registerCanonicalNode)(state.graphState, uploaded.ref, uploaded.versionNodeId);
214
358
  (0, upload_graph_1.registerLocalRef)(state.graphState.localAssetNodeByName, state.graphState.ambiguousAssetNames, task.name, uploaded.versionNodeId);
@@ -286,12 +430,12 @@ function registerUploadedLocalPackAssets(state, task, taskCreator, packPlan, loc
286
430
  appendTaskRelations(state.graphState, uploadedLocalRef.versionNodeId, localTask.relations, `[${localTask.cataloguePath}] asset "${localTask.name}"`);
287
431
  }
288
432
  }
289
- async function uploadPackTask(state, task, taskCreator) {
433
+ async function uploadPackTask(state, task, taskCreator, options) {
290
434
  const { packPlan, localAssetTasks } = collectPackLocalAssetTasks(state, task, taskCreator);
291
435
  const mutationTargetCreator = typeof task.username === 'string' && task.username.trim().length > 0
292
436
  ? taskCreator
293
437
  : undefined;
294
- const uploadedPack = await (0, upload_content_1.uploadAssetPackTask)(state.client, task, taskCreator, state.uploadedAssetsByKey, localAssetTasks, packPlan.uploadKeyByAssetKey, mutationTargetCreator);
438
+ const uploadedPack = await (0, upload_content_1.uploadAssetPackTask)(state.client, task, taskCreator, state.uploadedAssetsByKey, localAssetTasks, packPlan.uploadKeyByAssetKey, mutationTargetCreator, { clearTags: options?.clearTags });
295
439
  (0, upload_graph_1.registerCanonicalNode)(state.graphState, uploadedPack.ref, uploadedPack.versionNodeId);
296
440
  (0, upload_graph_1.registerLocalRef)(state.graphState.localPackNodeByNameVersion, state.graphState.ambiguousPackNameVersions, `${task.name}@${task.version}`, uploadedPack.versionNodeId);
297
441
  registerUploadedLocalPackAssets(state, task, taskCreator, packPlan, localAssetTasks, uploadedPack);
@@ -311,14 +455,17 @@ async function processSingleUploadTask(state, task, taskCreator, options) {
311
455
  if (task.kind === 'app') {
312
456
  return await uploadAppTask(state, task, taskCreator, options);
313
457
  }
458
+ if (task.kind === 'asset-spec') {
459
+ return await uploadAssetSpecDefinitionTask(state, task, taskCreator);
460
+ }
314
461
  if (task.kind === 'asset') {
315
- return await uploadStandaloneAssetTask(state, task, taskCreator);
462
+ return await uploadStandaloneAssetTask(state, task, taskCreator, options);
316
463
  }
317
464
  if (task.kind === 'embedded-asset') {
318
465
  return await uploadEmbeddedAssetTask(state, task, taskCreator);
319
466
  }
320
467
  if (task.kind === 'asset-pack') {
321
- return await uploadPackTask(state, task, taskCreator);
468
+ return await uploadPackTask(state, task, taskCreator, options);
322
469
  }
323
470
  throw new Error(`Unsupported task kind "${task.kind}".`);
324
471
  }
@@ -412,6 +559,23 @@ async function upload(pathOrName, options) {
412
559
  ? userInfo.username.trim()
413
560
  : 'unknown';
414
561
  const selection = (0, taskSelection_1.selectTasks)(pathOrName);
562
+ const workspaceRoot = resolveUploadWorkspaceRoot(pathOrName, selection.resolution);
563
+ const tagGroupLoad = (0, catalogue_1.resolveCatalogueTagGroups)(workspaceRoot);
564
+ if (tagGroupLoad.errors.length > 0) {
565
+ (0, taskSelection_1.reportTaskErrors)(tagGroupLoad.errors.map((message) => ({
566
+ type: 'invalid-catalogue',
567
+ message,
568
+ help: ['Update the tagGroups entries noted above, then retry the publish.'],
569
+ })), 'project publish');
570
+ process.exitCode = 1;
571
+ return;
572
+ }
573
+ if (selection.errors.length > 0) {
574
+ const canPublishTagGroupsOnly = selection.errors.every((error) => error.type === 'no-tasks') && tagGroupLoad.groups.length > 0;
575
+ if (canPublishTagGroupsOnly) {
576
+ selection.errors.length = 0;
577
+ }
578
+ }
415
579
  if (selection.errors.length > 0) {
416
580
  (0, taskSelection_1.reportTaskErrors)(selection.errors, 'project publish');
417
581
  process.exitCode = 1;
@@ -419,11 +583,23 @@ async function upload(pathOrName, options) {
419
583
  }
420
584
  const warnings = selection.warnings;
421
585
  const tasks = selection.tasks;
422
- if (tasks.length === 0) {
423
- (0, uploadLog_1.printTaskSummary)([], warnings, { action: 'upload', environment: env });
586
+ if (tagGroupLoad.groups.length > 0 && userInfo.role !== 'ADMIN') {
587
+ (0, messages_1.printErrorWithHelp)('Publishing tagGroups requires an admin account.', [
588
+ 'Log in as an admin before publishing taxonomy changes.',
589
+ 'Remove tagGroups from catalogue.json if this publish should only upload content.',
590
+ ], { command: 'project publish' });
591
+ process.exitCode = 1;
424
592
  return;
425
593
  }
426
- const results = await processUploadTasks(client, tasks, owner, userInfo.username, userInfo.role, envConfig.webBase, options);
594
+ const results = [];
595
+ if (tagGroupLoad.groups.length > 0) {
596
+ const taxonomyEntries = await syncTagGroupsFromCatalogue(client, tagGroupLoad.groups);
597
+ taxonomyEntries.forEach((entry) => pushLoggedEntry(results, entry));
598
+ }
599
+ if (tasks.length > 0) {
600
+ const uploadEntries = await processUploadTasks(client, tasks, owner, userInfo.username, userInfo.role, envConfig.webBase, options);
601
+ results.push(...uploadEntries);
602
+ }
427
603
  (0, uploadLog_1.printTaskSummary)(results, warnings, { action: 'upload', environment: env });
428
604
  (0, uploadLog_1.printPublishedAppNextSteps)(results);
429
605
  });
@@ -1,11 +1,158 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.validate = validate;
4
+ const types_1 = require("@playdrop/types");
5
+ const types_2 = require("@playdrop/types");
6
+ const apiClient_1 = require("../apiClient");
7
+ const config_1 = require("../config");
8
+ const environment_1 = require("../environment");
4
9
  const taskSelection_1 = require("../taskSelection");
5
10
  const taskUtils_1 = require("../taskUtils");
6
11
  const uploadLog_1 = require("../uploadLog");
7
12
  const apps_1 = require("../apps");
13
+ const assetSpecs_1 = require("../assetSpecs");
8
14
  const externalAssetPackValidation_1 = require("../externalAssetPackValidation");
15
+ const upload_content_1 = require("./upload-content");
16
+ function buildLocalAssetSpecLookups(tasks) {
17
+ const exactByRef = new Map();
18
+ const uniqueByNameVersion = new Map();
19
+ for (const task of tasks) {
20
+ if (task.username) {
21
+ exactByRef.set(`asset-spec:${task.username}/${task.name}@${task.version}`, task);
22
+ }
23
+ const nameVersionKey = `${task.name}@${task.version}`;
24
+ if (!uniqueByNameVersion.has(nameVersionKey)) {
25
+ uniqueByNameVersion.set(nameVersionKey, task);
26
+ }
27
+ else {
28
+ uniqueByNameVersion.set(nameVersionKey, null);
29
+ }
30
+ }
31
+ return { exactByRef, uniqueByNameVersion };
32
+ }
33
+ function resolveLocalAssetSpecTask(task, lookups) {
34
+ if (!task.assetSpec) {
35
+ return null;
36
+ }
37
+ const direct = lookups.exactByRef.get(task.assetSpec);
38
+ if (direct) {
39
+ return direct;
40
+ }
41
+ const parsed = (0, types_2.parseAssetSpecVersionRef)(task.assetSpec);
42
+ if (!parsed) {
43
+ return null;
44
+ }
45
+ const fallback = lookups.uniqueByNameVersion.get(`${parsed.name}@${parsed.version}`);
46
+ return fallback ?? null;
47
+ }
48
+ function validateAssetTask(task, assetSpecLookups) {
49
+ if (!task.assetSpec) {
50
+ return;
51
+ }
52
+ const localAssetSpecTask = resolveLocalAssetSpecTask(task, assetSpecLookups);
53
+ if (!localAssetSpecTask) {
54
+ return;
55
+ }
56
+ const errors = [];
57
+ (0, assetSpecs_1.validateCustomAssetFilesAgainstContract)(localAssetSpecTask.contract, task.filePaths, errors, `Asset "${task.name}"`);
58
+ if (errors.length > 0) {
59
+ throw new Error(errors.join(' '));
60
+ }
61
+ }
62
+ async function loadAuthenticatedValidationContext() {
63
+ const config = (0, config_1.loadConfig)();
64
+ const token = typeof config.token === 'string' ? config.token.trim() : '';
65
+ if (!token) {
66
+ return null;
67
+ }
68
+ const envName = typeof config.env === 'string' && config.env.trim().length > 0 ? config.env.trim() : 'prod';
69
+ const envConfig = (0, environment_1.resolveEnvironmentConfig)(envName);
70
+ if (!envConfig) {
71
+ return null;
72
+ }
73
+ const client = (0, apiClient_1.createCliApiClient)({
74
+ baseUrl: envConfig.apiBase,
75
+ token,
76
+ });
77
+ try {
78
+ const me = await client.me();
79
+ const username = typeof me?.user?.username === 'string' ? me.user.username.trim() : '';
80
+ if (!username) {
81
+ return null;
82
+ }
83
+ return {
84
+ client,
85
+ currentUserRole: me.user.role ?? null,
86
+ username,
87
+ };
88
+ }
89
+ catch {
90
+ return null;
91
+ }
92
+ }
93
+ function formatLiveTagClearWarning(input) {
94
+ const countLabel = input.liveTagCount === 1 ? '1 existing live tag' : `${input.liveTagCount} existing live tags`;
95
+ return `${input.entityLabel} "${input.displayName}" would remove ${countLabel} on publish. Add a tags field or pass --clear-tags to confirm.`;
96
+ }
97
+ // eslint-disable-next-line complexity
98
+ async function collectLiveTagClearWarnings(tasks) {
99
+ const context = await loadAuthenticatedValidationContext();
100
+ if (!context) {
101
+ return [];
102
+ }
103
+ const warnings = [];
104
+ for (const task of tasks) {
105
+ if ((task.kind !== 'app' && task.kind !== 'asset' && task.kind !== 'asset-pack')
106
+ || task.tags.length > 0) {
107
+ continue;
108
+ }
109
+ const creatorResult = (0, upload_content_1.getTaskCreatorResult)(task, context.username, context.currentUserRole);
110
+ if (creatorResult.creatorTargetError) {
111
+ continue;
112
+ }
113
+ try {
114
+ if (task.kind === 'app') {
115
+ const response = await context.client.fetchAppBySlug(creatorResult.taskCreator, task.name);
116
+ const liveTagCount = Array.isArray(response.app.tags) ? response.app.tags.length : 0;
117
+ if (liveTagCount > 0) {
118
+ warnings.push(formatLiveTagClearWarning({
119
+ entityLabel: 'App',
120
+ displayName: `${creatorResult.taskCreator}/${task.name}`,
121
+ liveTagCount,
122
+ }));
123
+ }
124
+ continue;
125
+ }
126
+ if (task.kind === 'asset') {
127
+ const response = await context.client.fetchAssetBySlug(creatorResult.taskCreator, task.name);
128
+ const liveTagCount = Array.isArray(response.asset.tags) ? response.asset.tags.length : 0;
129
+ if (liveTagCount > 0) {
130
+ warnings.push(formatLiveTagClearWarning({
131
+ entityLabel: 'Asset',
132
+ displayName: `${creatorResult.taskCreator}/${task.name}`,
133
+ liveTagCount,
134
+ }));
135
+ }
136
+ continue;
137
+ }
138
+ const response = await context.client.fetchAssetPackBySlug(creatorResult.taskCreator, task.name);
139
+ const liveTagCount = Array.isArray(response.pack.tags) ? response.pack.tags.length : 0;
140
+ if (liveTagCount > 0) {
141
+ warnings.push(formatLiveTagClearWarning({
142
+ entityLabel: 'Asset pack',
143
+ displayName: `${creatorResult.taskCreator}/${task.name}`,
144
+ liveTagCount,
145
+ }));
146
+ }
147
+ }
148
+ catch (error) {
149
+ if (error instanceof types_1.ApiError && (error.status === 404 || error.status === 401 || error.status === 403)) {
150
+ continue;
151
+ }
152
+ }
153
+ }
154
+ return warnings;
155
+ }
9
156
  async function validate(pathOrName) {
10
157
  const selection = (0, taskSelection_1.selectTasks)(pathOrName);
11
158
  if (selection.errors.length > 0) {
@@ -30,14 +177,26 @@ async function validate(pathOrName) {
30
177
  return;
31
178
  }
32
179
  const results = [];
33
- for (const task of (0, taskUtils_1.sortTasks)(tasks)) {
180
+ const assetSpecTasks = tasks.filter((task) => task.kind === 'asset-spec');
181
+ const assetSpecLookups = buildLocalAssetSpecLookups(assetSpecTasks);
182
+ const sortedTasks = (0, taskUtils_1.sortTasks)(tasks);
183
+ for (const task of sortedTasks) {
34
184
  const entityId = task.name;
35
185
  try {
36
186
  if (task.kind === 'app') {
37
187
  await (0, apps_1.validateAppTask)(task);
38
188
  }
189
+ else if (task.kind === 'asset-spec') {
190
+ // Parsing already validates the contract, symbol, and documentation files.
191
+ }
192
+ else if (task.kind === 'asset') {
193
+ validateAssetTask(task, assetSpecLookups);
194
+ }
195
+ else if (task.kind === 'embedded-asset' || task.kind === 'asset-pack') {
196
+ // Structural validation already ran during catalogue parsing.
197
+ }
39
198
  else {
40
- throw new Error(`project validate does not support ${task.kind} "${entityId}".`);
199
+ throw new Error(`project validate does not support "${entityId}".`);
41
200
  }
42
201
  const entry = {
43
202
  action: 'validate',
@@ -63,5 +222,7 @@ async function validate(pathOrName) {
63
222
  console.log((0, uploadLog_1.formatTaskLogLine)(entry));
64
223
  }
65
224
  }
225
+ const liveTagWarnings = await collectLiveTagClearWarnings(sortedTasks);
226
+ liveTagWarnings.forEach((warning) => warnings.add(warning));
66
227
  (0, uploadLog_1.printTaskSummary)(results, warnings, { action: 'validate' });
67
228
  }