@playdrop/playdrop-cli 0.5.5 → 0.6.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 (96) hide show
  1. package/README.md +2 -1
  2. package/config/client-meta.json +4 -4
  3. package/dist/apps/upload.js +226 -80
  4. package/dist/assetSpecs.d.ts +1 -0
  5. package/dist/assetSpecs.js +22 -1
  6. package/dist/assets/model-artifacts.d.ts +2 -2
  7. package/dist/assets/model-artifacts.js +1 -1
  8. package/dist/captureRuntime.d.ts +1 -0
  9. package/dist/captureRuntime.js +3 -2
  10. package/dist/catalogue.d.ts +33 -8
  11. package/dist/catalogue.js +364 -46
  12. package/dist/commandContext.d.ts +5 -1
  13. package/dist/commandContext.js +90 -29
  14. package/dist/commands/browse.d.ts +3 -0
  15. package/dist/commands/browse.js +90 -17
  16. package/dist/commands/build.js +1 -1
  17. package/dist/commands/capture.d.ts +3 -0
  18. package/dist/commands/capture.js +121 -9
  19. package/dist/commands/captureListing.d.ts +2 -0
  20. package/dist/commands/captureListing.js +157 -61
  21. package/dist/commands/create.js +6 -28
  22. package/dist/commands/createRemixContent.js +4 -26
  23. package/dist/commands/creations.js +2 -3
  24. package/dist/commands/detail.js +25 -3
  25. package/dist/commands/dev.d.ts +8 -1
  26. package/dist/commands/dev.js +180 -2
  27. package/dist/commands/devRuntimeAssets.d.ts +34 -0
  28. package/dist/commands/devRuntimeAssets.js +308 -0
  29. package/dist/commands/devServer.d.ts +11 -0
  30. package/dist/commands/devServer.js +196 -13
  31. package/dist/commands/init.js +6 -24
  32. package/dist/commands/search.d.ts +4 -0
  33. package/dist/commands/search.js +68 -11
  34. package/dist/commands/upload-content.d.ts +3 -3
  35. package/dist/commands/upload-content.js +19 -38
  36. package/dist/commands/upload.js +67 -77
  37. package/dist/commands/validate.js +13 -20
  38. package/dist/commands/whoami.js +23 -26
  39. package/dist/devAuth.d.ts +16 -0
  40. package/dist/devAuth.js +60 -0
  41. package/dist/index.js +22 -4
  42. package/dist/taskSelection.js +4 -3
  43. package/dist/taskUtils.d.ts +2 -2
  44. package/dist/taskUtils.js +1 -1
  45. package/dist/uploadLog.d.ts +1 -1
  46. package/node_modules/@playdrop/ai-client/package.json +1 -1
  47. package/node_modules/@playdrop/api-client/dist/client.d.ts +46 -115
  48. package/node_modules/@playdrop/api-client/dist/client.d.ts.map +1 -1
  49. package/node_modules/@playdrop/api-client/dist/client.js +22 -0
  50. package/node_modules/@playdrop/api-client/dist/domains/apps.d.ts +14 -20
  51. package/node_modules/@playdrop/api-client/dist/domains/apps.d.ts.map +1 -1
  52. package/node_modules/@playdrop/api-client/dist/domains/apps.js +152 -108
  53. package/node_modules/@playdrop/api-client/dist/domains/assets.d.ts +2 -1
  54. package/node_modules/@playdrop/api-client/dist/domains/assets.d.ts.map +1 -1
  55. package/node_modules/@playdrop/api-client/dist/domains/assets.js +13 -0
  56. package/node_modules/@playdrop/api-client/dist/domains/payments.d.ts +5 -5
  57. package/node_modules/@playdrop/api-client/dist/domains/payments.d.ts.map +1 -1
  58. package/node_modules/@playdrop/api-client/dist/domains/payments.js +8 -8
  59. package/node_modules/@playdrop/api-client/dist/domains/search.d.ts.map +1 -1
  60. package/node_modules/@playdrop/api-client/dist/domains/search.js +24 -2
  61. package/node_modules/@playdrop/api-client/dist/domains/tags.d.ts +13 -1
  62. package/node_modules/@playdrop/api-client/dist/domains/tags.d.ts.map +1 -1
  63. package/node_modules/@playdrop/api-client/dist/domains/tags.js +52 -0
  64. package/node_modules/@playdrop/api-client/dist/index.d.ts +29 -31
  65. package/node_modules/@playdrop/api-client/dist/index.d.ts.map +1 -1
  66. package/node_modules/@playdrop/api-client/dist/index.js +33 -12
  67. package/node_modules/@playdrop/api-client/package.json +1 -1
  68. package/node_modules/@playdrop/boxel-core/package.json +1 -1
  69. package/node_modules/@playdrop/boxel-three/package.json +1 -1
  70. package/node_modules/@playdrop/config/client-meta.json +4 -4
  71. package/node_modules/@playdrop/config/package.json +1 -1
  72. package/node_modules/@playdrop/types/dist/api.d.ts +153 -3
  73. package/node_modules/@playdrop/types/dist/api.d.ts.map +1 -1
  74. package/node_modules/@playdrop/types/dist/api.js +30 -0
  75. package/node_modules/@playdrop/types/dist/app-capability-filters.d.ts +24 -0
  76. package/node_modules/@playdrop/types/dist/app-capability-filters.d.ts.map +1 -0
  77. package/node_modules/@playdrop/types/dist/app-capability-filters.js +72 -0
  78. package/node_modules/@playdrop/types/dist/asset-pack.d.ts +3 -2
  79. package/node_modules/@playdrop/types/dist/asset-pack.d.ts.map +1 -1
  80. package/node_modules/@playdrop/types/dist/asset.d.ts +2 -3
  81. package/node_modules/@playdrop/types/dist/asset.d.ts.map +1 -1
  82. package/node_modules/@playdrop/types/dist/asset.js +1 -1
  83. package/node_modules/@playdrop/types/dist/index.d.ts +2 -0
  84. package/node_modules/@playdrop/types/dist/index.d.ts.map +1 -1
  85. package/node_modules/@playdrop/types/dist/index.js +2 -0
  86. package/node_modules/@playdrop/types/dist/owned-assets.d.ts +21 -0
  87. package/node_modules/@playdrop/types/dist/owned-assets.d.ts.map +1 -0
  88. package/node_modules/@playdrop/types/dist/owned-assets.js +35 -0
  89. package/node_modules/@playdrop/types/dist/player-meta.d.ts +28 -0
  90. package/node_modules/@playdrop/types/dist/player-meta.d.ts.map +1 -1
  91. package/node_modules/@playdrop/types/dist/version.d.ts +111 -1
  92. package/node_modules/@playdrop/types/dist/version.d.ts.map +1 -1
  93. package/node_modules/@playdrop/types/dist/version.js +3 -0
  94. package/node_modules/@playdrop/types/package.json +1 -1
  95. package/node_modules/@playdrop/vox-three/package.json +1 -1
  96. package/package.json +1 -1
@@ -13,6 +13,7 @@ const node_fs_1 = require("node:fs");
13
13
  const node_path_1 = require("node:path");
14
14
  const types_1 = require("@playdrop/types");
15
15
  const model_artifacts_1 = require("../assets/model-artifacts");
16
+ const assetSpecs_1 = require("../assetSpecs");
16
17
  const EXTENSION_TO_FORMAT = {
17
18
  '.png': 'PNG',
18
19
  '.jpg': 'JPEG',
@@ -174,21 +175,6 @@ function parseUnversionedAssetTaskRef(rawRef, fallbackCreator) {
174
175
  };
175
176
  }
176
177
  function buildAssetPackUploadPlans(tasks, defaultCreator, currentUserRole) {
177
- const localAssetTasksByKey = new Map();
178
- for (const task of tasks) {
179
- if (task.kind !== 'asset') {
180
- continue;
181
- }
182
- const creator = getTaskCreatorResult(task, defaultCreator, currentUserRole);
183
- if (creator.creatorTargetError) {
184
- return {
185
- ok: false,
186
- task,
187
- message: creator.creatorTargetError,
188
- };
189
- }
190
- localAssetTasksByKey.set(buildAssetKey(creator.taskCreator, task.name), task);
191
- }
192
178
  const packPlansByKey = new Map();
193
179
  const ownedPackByAssetKey = new Map();
194
180
  for (const task of tasks) {
@@ -211,25 +197,18 @@ function buildAssetPackUploadPlans(tasks, defaultCreator, currentUserRole) {
211
197
  ownedAssetKeys: [],
212
198
  uploadKeyByAssetKey: new Map(),
213
199
  };
214
- for (const assetRef of task.assets) {
215
- const candidate = parseUnversionedAssetTaskRef(assetRef, creator.taskCreator);
216
- if (!candidate || candidate.creatorUsername !== creator.taskCreator) {
217
- continue;
218
- }
219
- const assetKey = buildAssetKey(candidate.creatorUsername, candidate.name);
220
- if (!localAssetTasksByKey.has(assetKey)) {
221
- continue;
222
- }
200
+ for (const ownedAsset of task.ownedAssets) {
201
+ const assetKey = buildAssetKey(creator.taskCreator, ownedAsset.name);
223
202
  const existingOwner = ownedPackByAssetKey.get(assetKey);
224
203
  if (existingOwner && existingOwner.packKey !== packKey) {
225
204
  return {
226
205
  ok: false,
227
206
  task,
228
- message: `Local asset "${candidate.name}" is referenced by multiple local asset packs in this upload run.`,
207
+ message: `Pack owned asset "${ownedAsset.name}" is declared by multiple local asset packs in this upload run.`,
229
208
  };
230
209
  }
231
210
  if (!plan.uploadKeyByAssetKey.has(assetKey)) {
232
- plan.uploadKeyByAssetKey.set(assetKey, candidate.name);
211
+ plan.uploadKeyByAssetKey.set(assetKey, ownedAsset.name);
233
212
  plan.ownedAssetKeys.push(assetKey);
234
213
  }
235
214
  ownedPackByAssetKey.set(assetKey, plan);
@@ -320,7 +299,10 @@ async function prepareAssetTaskUploadData(task) {
320
299
  if ('assetSpec' in task && typeof task.assetSpec === 'string' && task.assetSpec.trim().length > 0) {
321
300
  const uploadFiles = Object.entries(task.filePaths)
322
301
  .sort(([leftRole], [rightRole]) => leftRole.localeCompare(rightRole))
323
- .map(([role, filePath]) => prepareUploadFile(filePath, role));
302
+ .map(([role, filePath]) => {
303
+ const roleContract = task.assetSpecContract?.roles.find((candidate) => candidate.name.trim().toLowerCase() === role.trim().toLowerCase());
304
+ return prepareUploadFile(filePath, role, (0, assetSpecs_1.resolveCustomAssetRoleContentType)(filePath, roleContract));
305
+ });
324
306
  return {
325
307
  assetSpec: task.assetSpec.trim(),
326
308
  uploadFiles,
@@ -372,6 +354,7 @@ async function uploadAssetTask(client, task, sourceAppVersionId, creatorUsername
372
354
  visibility: visibility,
373
355
  sourceKind: sourceAppVersionId ? 'APP_EMBEDDED' : 'UPLOAD',
374
356
  sourceAppVersionId,
357
+ runtimeKey: task.kind === 'owned-asset' ? task.runtimeKey : undefined,
375
358
  tags: task.kind === 'asset' ? task.tags : undefined,
376
359
  clearTags: task.kind === 'asset' ? options?.clearTags : undefined,
377
360
  shopListed: task.shopListed,
@@ -387,6 +370,7 @@ async function uploadAssetTask(client, task, sourceAppVersionId, creatorUsername
387
370
  visibility: visibility,
388
371
  sourceKind: sourceAppVersionId ? 'APP_EMBEDDED' : 'UPLOAD',
389
372
  sourceAppVersionId,
373
+ runtimeKey: task.kind === 'owned-asset' ? task.runtimeKey : undefined,
390
374
  tags: task.kind === 'asset' ? task.tags : undefined,
391
375
  clearTags: task.kind === 'asset' ? options?.clearTags : undefined,
392
376
  shopListed: task.shopListed,
@@ -520,14 +504,10 @@ function buildLocalAssetManifestEntries(preparedLocalAssets) {
520
504
  localAssets.push({
521
505
  uploadKey,
522
506
  name: localAsset.task.name,
523
- displayName: localAsset.task.displayName,
524
- category: (localAsset.task.assetSpec ? 'CUSTOM' : localAsset.task.category),
507
+ category: localAsset.task.category,
525
508
  subcategory: localAsset.prepared.subcategory ?? null,
526
- format: (localAsset.prepared.format ?? 'CUSTOM'),
527
- ...(localAsset.task.assetSpec ? { assetSpec: localAsset.task.assetSpec } : {}),
509
+ format: localAsset.prepared.format,
528
510
  visibility: localAsset.task.visibility,
529
- remixRef: localAsset.task.remix,
530
- tags: localAsset.task.tags,
531
511
  shopListed: localAsset.task.shopListed,
532
512
  shopPriceCredits: localAsset.task.shopPriceCredits,
533
513
  files: localAsset.prepared.manifestFiles,
@@ -538,9 +518,10 @@ function buildLocalAssetManifestEntries(preparedLocalAssets) {
538
518
  async function buildPackMembersAndExistingAssets(client, task, mutationCreatorUsername, uploadedAssets, uploadKeyByAssetKey) {
539
519
  const existingAssetsByRef = new Map();
540
520
  const members = [];
521
+ const ownedAssetNames = new Set(task.ownedAssets.map((asset) => asset.name));
541
522
  for (const rawRef of task.assets) {
542
523
  const localCandidate = parseUnversionedAssetTaskRef(rawRef, mutationCreatorUsername);
543
- if (localCandidate && localCandidate.creatorUsername === mutationCreatorUsername) {
524
+ if (localCandidate && localCandidate.creatorUsername === mutationCreatorUsername && ownedAssetNames.has(localCandidate.name)) {
544
525
  const localAssetKey = buildAssetKey(localCandidate.creatorUsername, localCandidate.name);
545
526
  const localUploadKey = uploadKeyByAssetKey.get(localAssetKey);
546
527
  if (localUploadKey) {
@@ -598,7 +579,7 @@ function buildPackListingPayload(listingUploads) {
598
579
  videosLandscape: listingUploads.filter((entry) => entry.mediaKey.startsWith('videosLandscape:')).map((entry) => entry.entry),
599
580
  };
600
581
  }
601
- function buildPackInitializeRequest(task, localAssets, existingAssets, members, listingUploads, options) {
582
+ function buildPackInitializeRequest(task, ownedAssets, existingAssets, members, listingUploads, options) {
602
583
  return {
603
584
  version: task.version,
604
585
  displayName: task.displayName,
@@ -610,7 +591,7 @@ function buildPackInitializeRequest(task, localAssets, existingAssets, members,
610
591
  externalUrl: task.externalUrl,
611
592
  downloadUrl: task.downloadUrl,
612
593
  releaseNotes: task.releaseNotes,
613
- localAssets,
594
+ ownedAssets,
614
595
  existingAssets,
615
596
  members,
616
597
  listing: buildPackListingPayload(listingUploads),
@@ -655,10 +636,10 @@ function buildUploadedPackInfo(creatorUsername, task, versionNodeId, versionAsse
655
636
  async function uploadAssetPackTask(client, task, creatorUsername, uploadedAssets, localAssetTasks, uploadKeyByAssetKey, targetCreatorUsername, options) {
656
637
  const mutationCreatorUsername = resolveMutationCreatorUsername(task, creatorUsername, targetCreatorUsername);
657
638
  const preparedLocalAssets = await prepareLocalPackAssets(task, mutationCreatorUsername, localAssetTasks, uploadKeyByAssetKey);
658
- const localAssets = buildLocalAssetManifestEntries(preparedLocalAssets);
639
+ const ownedAssets = buildLocalAssetManifestEntries(preparedLocalAssets);
659
640
  const { existingAssets, members } = await buildPackMembersAndExistingAssets(client, task, mutationCreatorUsername, uploadedAssets, uploadKeyByAssetKey);
660
641
  const listingUploads = buildPackListingUploads(task);
661
- const initializeBody = buildPackInitializeRequest(task, localAssets, existingAssets, members, listingUploads, options);
642
+ const initializeBody = buildPackInitializeRequest(task, ownedAssets, existingAssets, members, listingUploads, options);
662
643
  const initializeResponse = await client.initializeAssetPackUpload(mutationCreatorUsername, task.name, initializeBody);
663
644
  if (initializeResponse.status === 'finalized') {
664
645
  if (!initializeResponse.versionNodeId || !initializeResponse.version) {
@@ -208,7 +208,7 @@ function appendOverviewLink(entry, task, portalBase, creator) {
208
208
  else if (task.kind === 'asset-spec') {
209
209
  url = null;
210
210
  }
211
- else if (task.kind === 'asset' || task.kind === 'embedded-asset') {
211
+ else if (task.kind === 'asset' || task.kind === 'owned-asset') {
212
212
  url = buildAssetOverviewUrl(portalBase, creator, task.name);
213
213
  }
214
214
  else if (task.kind === 'asset-pack') {
@@ -220,7 +220,7 @@ function appendOverviewLink(entry, task, portalBase, creator) {
220
220
  entry.detail = entry.detail ? `${entry.detail} | view: ${url}` : `view: ${url}`;
221
221
  }
222
222
  function buildTaskEntityId(task, creatorUsername) {
223
- return task.kind === 'embedded-asset'
223
+ return task.kind === 'owned-asset'
224
224
  ? `${creatorUsername}/${task.appName}:${task.name}`
225
225
  : `${creatorUsername}/${task.name}`;
226
226
  }
@@ -304,12 +304,6 @@ async function uploadAppTask(state, task, taskCreator, options) {
304
304
  state.uploadedAppsByName.set(task.name, uploadedApp);
305
305
  (0, upload_graph_1.registerCanonicalNode)(state.graphState, appRef, uploadedApp.versionNodeId);
306
306
  (0, upload_graph_1.registerLocalRef)(state.graphState.localAppNodeByName, state.graphState.ambiguousAppNames, task.name, uploadedApp.versionNodeId);
307
- task.uses.assets.forEach((toRef, index) => {
308
- (0, upload_graph_1.appendPendingRelation)(state.graphState, uploadedApp.versionNodeId, 'USES', toRef, `[${task.cataloguePath}] app "${task.name}" uses.assets[${index}]`);
309
- });
310
- task.uses.packs.forEach((toRef, index) => {
311
- (0, upload_graph_1.appendPendingRelation)(state.graphState, uploadedApp.versionNodeId, 'USES', toRef, `[${task.cataloguePath}] app "${task.name}" uses.packs[${index}]`);
312
- });
313
307
  appendTaskRelations(state.graphState, uploadedApp.versionNodeId, task.graph.relations, `[${task.cataloguePath}] app "${task.name}"`);
314
308
  const entry = {
315
309
  action: 'upload',
@@ -368,10 +362,10 @@ async function uploadStandaloneAssetTask(state, task, taskCreator, options) {
368
362
  appendOverviewLink(entry, task, state.portalBase, uploaded.creatorUsername);
369
363
  return entry;
370
364
  }
371
- async function uploadEmbeddedAssetTask(state, task, taskCreator) {
365
+ async function uploadOwnedAssetTask(state, task, taskCreator) {
372
366
  const sourceApp = state.uploadedAppsByName.get(task.appName);
373
367
  if (!sourceApp) {
374
- throw new Error(`Embedded asset "${task.name}" references app "${task.appName}" that was not uploaded in this run.`);
368
+ throw new Error(`Owned asset "${task.name}" references app "${task.appName}" that was not uploaded in this run.`);
375
369
  }
376
370
  const uploaded = await (0, upload_content_1.uploadAssetTask)(state.client, task, sourceApp.versionId, sourceApp.creatorUsername);
377
371
  state.uploadedAssetsByKey.set(`${uploaded.creatorUsername}/${uploaded.name}`, uploaded);
@@ -380,7 +374,7 @@ async function uploadEmbeddedAssetTask(state, task, taskCreator) {
380
374
  const entry = {
381
375
  action: 'upload',
382
376
  status: 'success',
383
- entityType: 'embedded-asset',
377
+ entityType: 'owned-asset',
384
378
  entityId: buildTaskEntityId(task, taskCreator),
385
379
  catalogue: task.cataloguePath,
386
380
  detail: `${uploaded.ref} (source app version ${sourceApp.versionId})`,
@@ -394,13 +388,7 @@ function collectPackLocalAssetTasks(state, task, taskCreator) {
394
388
  if (!packPlan) {
395
389
  throw new Error(`Asset pack "${task.name}@${task.version}" is missing an upload plan.`);
396
390
  }
397
- const localAssetTasks = packPlan.ownedAssetKeys.map((assetKey) => {
398
- const ownedTask = state.sortedTasks.find((candidate) => candidate.kind === 'asset' && (0, upload_content_1.buildAssetKey)(taskCreator, candidate.name) === assetKey);
399
- if (!ownedTask || ownedTask.kind !== 'asset') {
400
- throw new Error(`Asset pack "${task.name}@${task.version}" is missing local asset task "${assetKey}".`);
401
- }
402
- return ownedTask;
403
- });
391
+ const localAssetTasks = task.ownedAssets.filter((ownedAsset) => packPlan.ownedAssetKeys.includes((0, upload_content_1.buildAssetKey)(taskCreator, ownedAsset.name)));
404
392
  return { packKey, packPlan, localAssetTasks };
405
393
  }
406
394
  function registerUploadedLocalPackAssets(state, task, taskCreator, packPlan, localAssetTasks, uploadedPack) {
@@ -427,7 +415,6 @@ function registerUploadedLocalPackAssets(state, task, taskCreator, packPlan, loc
427
415
  state.uploadedAssetsByKey.set((0, upload_content_1.buildAssetKey)(uploadedLocalAsset.creatorUsername, uploadedLocalAsset.name), uploadedLocalRef);
428
416
  (0, upload_graph_1.registerCanonicalNode)(state.graphState, uploadedLocalRef.ref, uploadedLocalRef.versionNodeId);
429
417
  (0, upload_graph_1.registerLocalRef)(state.graphState.localAssetNodeByName, state.graphState.ambiguousAssetNames, localTask.name, uploadedLocalRef.versionNodeId);
430
- appendTaskRelations(state.graphState, uploadedLocalRef.versionNodeId, localTask.relations, `[${localTask.cataloguePath}] asset "${localTask.name}"`);
431
418
  }
432
419
  }
433
420
  async function uploadPackTask(state, task, taskCreator, options) {
@@ -461,8 +448,8 @@ async function processSingleUploadTask(state, task, taskCreator, options) {
461
448
  if (task.kind === 'asset') {
462
449
  return await uploadStandaloneAssetTask(state, task, taskCreator, options);
463
450
  }
464
- if (task.kind === 'embedded-asset') {
465
- return await uploadEmbeddedAssetTask(state, task, taskCreator);
451
+ if (task.kind === 'owned-asset') {
452
+ return await uploadOwnedAssetTask(state, task, taskCreator);
466
453
  }
467
454
  if (task.kind === 'asset-pack') {
468
455
  return await uploadPackTask(state, task, taskCreator, options);
@@ -544,63 +531,66 @@ async function processUploadTasks(client, tasks, owner, ownerUsername, currentUs
544
531
  return results;
545
532
  }
546
533
  async function upload(pathOrName, options) {
547
- await (0, commandContext_1.withEnvironment)('project publish', 'Publishing content', async ({ client, env, envConfig }) => {
548
- let userInfo = { username: null, role: null };
549
- try {
550
- userInfo = await fetchCurrentUserInfo(client, envConfig.apiBase);
551
- }
552
- catch (error) {
553
- if (error instanceof http_1.CLIUnsupportedClientError) {
554
- return;
555
- }
556
- throw error;
557
- }
558
- const owner = typeof userInfo.username === 'string' && userInfo.username.trim().length > 0
559
- ? userInfo.username.trim()
560
- : 'unknown';
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
- }
579
- if (selection.errors.length > 0) {
580
- (0, taskSelection_1.reportTaskErrors)(selection.errors, 'project publish');
581
- process.exitCode = 1;
582
- return;
583
- }
584
- const warnings = selection.warnings;
585
- const tasks = selection.tasks;
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;
534
+ const selection = (0, taskSelection_1.selectTasks)(pathOrName);
535
+ const workspaceRoot = resolveUploadWorkspaceRoot(pathOrName, selection.resolution);
536
+ const ctx = await (0, commandContext_1.resolveAuthenticatedEnvironmentContext)('project publish', 'Publishing content', { workspacePath: workspaceRoot });
537
+ if (!ctx) {
538
+ return;
539
+ }
540
+ const { client, env, envConfig } = ctx;
541
+ let userInfo = { username: null, role: null };
542
+ try {
543
+ userInfo = await fetchCurrentUserInfo(client, envConfig.apiBase);
544
+ }
545
+ catch (error) {
546
+ if (error instanceof http_1.CLIUnsupportedClientError) {
592
547
  return;
593
548
  }
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);
549
+ throw error;
550
+ }
551
+ const owner = typeof userInfo.username === 'string' && userInfo.username.trim().length > 0
552
+ ? userInfo.username.trim()
553
+ : 'unknown';
554
+ const tagGroupLoad = (0, catalogue_1.resolveCatalogueTagGroups)(workspaceRoot);
555
+ if (tagGroupLoad.errors.length > 0) {
556
+ (0, taskSelection_1.reportTaskErrors)(tagGroupLoad.errors.map((message) => ({
557
+ type: 'invalid-catalogue',
558
+ message,
559
+ help: ['Update the tagGroups entries noted above, then retry the publish.'],
560
+ })), 'project publish');
561
+ process.exitCode = 1;
562
+ return;
563
+ }
564
+ if (selection.errors.length > 0) {
565
+ const canPublishTagGroupsOnly = selection.errors.every((error) => error.type === 'no-tasks') && tagGroupLoad.groups.length > 0;
566
+ if (canPublishTagGroupsOnly) {
567
+ selection.errors.length = 0;
602
568
  }
603
- (0, uploadLog_1.printTaskSummary)(results, warnings, { action: 'upload', environment: env });
604
- (0, uploadLog_1.printPublishedAppNextSteps)(results);
605
- });
569
+ }
570
+ if (selection.errors.length > 0) {
571
+ (0, taskSelection_1.reportTaskErrors)(selection.errors, 'project publish');
572
+ process.exitCode = 1;
573
+ return;
574
+ }
575
+ const warnings = selection.warnings;
576
+ const tasks = selection.tasks;
577
+ if (tagGroupLoad.groups.length > 0 && userInfo.role !== 'ADMIN') {
578
+ (0, messages_1.printErrorWithHelp)('Publishing tagGroups requires an admin account.', [
579
+ 'Log in as an admin before publishing taxonomy changes.',
580
+ 'Remove tagGroups from catalogue.json if this publish should only upload content.',
581
+ ], { command: 'project publish' });
582
+ process.exitCode = 1;
583
+ return;
584
+ }
585
+ const results = [];
586
+ if (tagGroupLoad.groups.length > 0) {
587
+ const taxonomyEntries = await syncTagGroupsFromCatalogue(client, tagGroupLoad.groups);
588
+ taxonomyEntries.forEach((entry) => pushLoggedEntry(results, entry));
589
+ }
590
+ if (tasks.length > 0) {
591
+ const uploadEntries = await processUploadTasks(client, tasks, owner, userInfo.username, userInfo.role, envConfig.webBase, options);
592
+ results.push(...uploadEntries);
593
+ }
594
+ (0, uploadLog_1.printTaskSummary)(results, warnings, { action: 'upload', environment: env });
595
+ (0, uploadLog_1.printPublishedAppNextSteps)(results);
606
596
  }
@@ -3,9 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.validate = validate;
4
4
  const types_1 = require("@playdrop/types");
5
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");
6
+ const commandContext_1 = require("../commandContext");
9
7
  const taskSelection_1 = require("../taskSelection");
10
8
  const taskUtils_1 = require("../taskUtils");
11
9
  const uploadLog_1 = require("../uploadLog");
@@ -59,21 +57,12 @@ function validateAssetTask(task, assetSpecLookups) {
59
57
  throw new Error(errors.join(' '));
60
58
  }
61
59
  }
62
- async function loadAuthenticatedValidationContext() {
63
- const config = (0, config_1.loadConfig)();
64
- const token = typeof config.token === 'string' ? config.token.trim() : '';
65
- if (!token) {
60
+ async function loadAuthenticatedValidationContext(workspacePath) {
61
+ const ctx = await (0, commandContext_1.resolveOptionalEnvironmentContext)('project validate', { workspacePath });
62
+ if (!ctx) {
66
63
  return null;
67
64
  }
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
- });
65
+ const { client } = ctx;
77
66
  try {
78
67
  const me = await client.me();
79
68
  const username = typeof me?.user?.username === 'string' ? me.user.username.trim() : '';
@@ -90,13 +79,16 @@ async function loadAuthenticatedValidationContext() {
90
79
  return null;
91
80
  }
92
81
  }
82
+ function resolveValidationWorkspacePath(pathOrName, resolution) {
83
+ return resolution === 'name' ? process.cwd() : pathOrName;
84
+ }
93
85
  function formatLiveTagClearWarning(input) {
94
86
  const countLabel = input.liveTagCount === 1 ? '1 existing live tag' : `${input.liveTagCount} existing live tags`;
95
87
  return `${input.entityLabel} "${input.displayName}" would remove ${countLabel} on publish. Add a tags field or pass --clear-tags to confirm.`;
96
88
  }
97
89
  // eslint-disable-next-line complexity
98
- async function collectLiveTagClearWarnings(tasks) {
99
- const context = await loadAuthenticatedValidationContext();
90
+ async function collectLiveTagClearWarnings(tasks, workspacePath) {
91
+ const context = await loadAuthenticatedValidationContext(workspacePath);
100
92
  if (!context) {
101
93
  return [];
102
94
  }
@@ -192,7 +184,7 @@ async function validate(pathOrName) {
192
184
  else if (task.kind === 'asset') {
193
185
  validateAssetTask(task, assetSpecLookups);
194
186
  }
195
- else if (task.kind === 'embedded-asset' || task.kind === 'asset-pack') {
187
+ else if (task.kind === 'owned-asset' || task.kind === 'asset-pack') {
196
188
  // Structural validation already ran during catalogue parsing.
197
189
  }
198
190
  else {
@@ -222,7 +214,8 @@ async function validate(pathOrName) {
222
214
  console.log((0, uploadLog_1.formatTaskLogLine)(entry));
223
215
  }
224
216
  }
225
- const liveTagWarnings = await collectLiveTagClearWarnings(sortedTasks);
217
+ const workspacePath = resolveValidationWorkspacePath(pathOrName, selection.resolution);
218
+ const liveTagWarnings = await collectLiveTagClearWarnings(sortedTasks, workspacePath);
226
219
  liveTagWarnings.forEach((warning) => warnings.add(warning));
227
220
  (0, uploadLog_1.printTaskSummary)(results, warnings, { action: 'validate' });
228
221
  }
@@ -3,33 +3,20 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.whoami = whoami;
4
4
  const types_1 = require("@playdrop/types");
5
5
  const config_1 = require("../config");
6
- const apiClient_1 = require("../apiClient");
6
+ const commandContext_1 = require("../commandContext");
7
7
  const http_1 = require("../http");
8
- const environment_1 = require("../environment");
9
8
  const messages_1 = require("../messages");
10
9
  async function whoami() {
11
- const cfg = (0, config_1.loadConfig)();
12
- const currentAccount = (0, config_1.getCurrentAccountSession)(cfg);
13
- if (!cfg.token || !cfg.env) {
14
- (0, messages_1.printLoginRequired)('Checking your Playdrop account status', 'whoami');
15
- process.exitCode = 1;
16
- return;
17
- }
18
- const envConfig = (0, environment_1.resolveEnvironmentConfig)(cfg.env);
19
- if (!envConfig) {
20
- const choices = (0, environment_1.formatEnvironmentList)();
21
- (0, messages_1.printUnknownEnvironment)(cfg.env, choices, 'whoami');
22
- process.exitCode = 1;
10
+ const ctx = await (0, commandContext_1.resolveAuthenticatedEnvironmentContext)('whoami', 'Checking your Playdrop account status', { workspacePath: process.cwd() });
11
+ if (!ctx) {
23
12
  return;
24
13
  }
25
- if (envConfig.allowInsecureRequests) {
26
- process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
27
- }
28
- const base = envConfig.apiBase;
29
- async function doMe(currentBase) {
30
- const client = (0, apiClient_1.createCliApiClient)({ baseUrl: currentBase, token: cfg.token });
14
+ const resolvedCtx = ctx;
15
+ const globalCurrentAccount = (0, config_1.getCurrentAccountSession)((0, config_1.loadConfig)());
16
+ const hadStoredCurrentAccount = Boolean(globalCurrentAccount);
17
+ async function doMe() {
31
18
  try {
32
- return await client.me();
19
+ return await resolvedCtx.client.me();
33
20
  }
34
21
  catch (unknownError) {
35
22
  if (unknownError instanceof types_1.UnsupportedClientError) {
@@ -48,7 +35,7 @@ async function whoami() {
48
35
  }
49
36
  let data = null;
50
37
  try {
51
- data = await doMe(base);
38
+ data = await doMe();
52
39
  }
53
40
  catch (e) {
54
41
  if (e instanceof http_1.CLIUnsupportedClientError) {
@@ -69,13 +56,23 @@ async function whoami() {
69
56
  process.exitCode = 1;
70
57
  return;
71
58
  }
72
- if (!currentAccount) {
59
+ if (!hadStoredCurrentAccount) {
73
60
  (0, config_1.migrateLegacySession)({
74
61
  username,
75
- env: envConfig.name,
76
- token: cfg.token,
62
+ env: resolvedCtx.env,
63
+ token: resolvedCtx.token,
77
64
  });
78
65
  }
79
- console.log(`${username} (${envConfig.name})`);
66
+ if (resolvedCtx.workspaceAuth) {
67
+ console.log(`Workspace account: ${username} (${resolvedCtx.env})`);
68
+ console.log('Resolved from: .playdrop.json');
69
+ if (globalCurrentAccount
70
+ && (globalCurrentAccount.username !== username || globalCurrentAccount.env !== resolvedCtx.env)) {
71
+ console.log(`Global default: ${globalCurrentAccount.username} (${globalCurrentAccount.env})`);
72
+ }
73
+ }
74
+ else {
75
+ console.log(`${username} (${resolvedCtx.env})`);
76
+ }
80
77
  console.log('Next: run "playdrop getting-started" to see the recommended workflow.');
81
78
  }
@@ -0,0 +1,16 @@
1
+ export type HostedDevAuthMode = 'prompt' | 'anonymous' | 'viewer' | 'player';
2
+ export type HostedCaptureDevAuthMode = Exclude<HostedDevAuthMode, 'prompt'>;
3
+ export type ResetDevPlayerMode = 'before' | 'after' | 'before-and-after' | 'never';
4
+ export type HostedDevAuthSelection = {
5
+ devAuth: HostedDevAuthMode;
6
+ player: 1 | 2 | 3 | 4 | null;
7
+ };
8
+ export declare function parseHostedDevPlayerSlot(value: string | number | undefined): 1 | 2 | 3 | 4 | null;
9
+ export declare function parseHostedDevAuthSelection(input: {
10
+ devAuth?: string;
11
+ player?: string | number;
12
+ defaultMode: HostedDevAuthMode;
13
+ allowPrompt: boolean;
14
+ }): HostedDevAuthSelection;
15
+ export declare function parseResetDevPlayerMode(value: string | undefined): ResetDevPlayerMode;
16
+ export declare function applyHostedDevAuthSelectionToUrl(url: string, selection: HostedDevAuthSelection): string;
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseHostedDevPlayerSlot = parseHostedDevPlayerSlot;
4
+ exports.parseHostedDevAuthSelection = parseHostedDevAuthSelection;
5
+ exports.parseResetDevPlayerMode = parseResetDevPlayerMode;
6
+ exports.applyHostedDevAuthSelectionToUrl = applyHostedDevAuthSelectionToUrl;
7
+ function parseHostedDevPlayerSlot(value) {
8
+ if (value === undefined) {
9
+ return null;
10
+ }
11
+ const normalized = String(value).trim();
12
+ if (normalized === '1' || normalized === '2' || normalized === '3' || normalized === '4') {
13
+ return Number.parseInt(normalized, 10);
14
+ }
15
+ return null;
16
+ }
17
+ function parseHostedDevAuthSelection(input) {
18
+ const normalizedMode = (input.devAuth?.trim().toLowerCase() || input.defaultMode);
19
+ if (!input.allowPrompt && normalizedMode === 'prompt') {
20
+ throw new Error('dev_auth_prompt_not_supported');
21
+ }
22
+ if (normalizedMode !== 'prompt'
23
+ && normalizedMode !== 'anonymous'
24
+ && normalizedMode !== 'viewer'
25
+ && normalizedMode !== 'player') {
26
+ throw new Error('dev_auth_invalid');
27
+ }
28
+ if (normalizedMode !== 'player') {
29
+ return {
30
+ devAuth: normalizedMode,
31
+ player: null,
32
+ };
33
+ }
34
+ const player = parseHostedDevPlayerSlot(input.player);
35
+ if (player === null) {
36
+ throw new Error('dev_auth_player_slot_required');
37
+ }
38
+ return {
39
+ devAuth: normalizedMode,
40
+ player,
41
+ };
42
+ }
43
+ function parseResetDevPlayerMode(value) {
44
+ const normalized = value?.trim().toLowerCase() || 'never';
45
+ if (normalized === 'before' || normalized === 'after' || normalized === 'before-and-after' || normalized === 'never') {
46
+ return normalized;
47
+ }
48
+ throw new Error('reset_dev_player_invalid');
49
+ }
50
+ function applyHostedDevAuthSelectionToUrl(url, selection) {
51
+ const nextUrl = new URL(url);
52
+ nextUrl.searchParams.set('devAuth', selection.devAuth);
53
+ if (selection.devAuth === 'player' && selection.player) {
54
+ nextUrl.searchParams.set('player', String(selection.player));
55
+ }
56
+ else {
57
+ nextUrl.searchParams.delete('player');
58
+ }
59
+ return nextUrl.toString();
60
+ }