@playdrop/playdrop-cli 0.7.2 → 0.7.4
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.
- package/README.md +6 -4
- package/config/client-meta.json +9 -9
- package/dist/apps/launchCheck.js +11 -0
- package/dist/apps/upload.js +6 -0
- package/dist/captureRuntime.js +8 -1
- package/dist/catalogue.d.ts +9 -0
- package/dist/catalogue.js +14 -0
- package/dist/commandContext.d.ts +1 -0
- package/dist/commandContext.js +54 -17
- package/dist/commands/capture.js +1 -1
- package/dist/commands/devBrowser.js +2 -0
- package/dist/commands/devServer.d.ts +1 -0
- package/dist/commands/devServer.js +57 -3
- package/dist/commands/upload-content.js +5 -2
- package/dist/commands/upload.d.ts +1 -0
- package/dist/commands/upload.js +613 -33
- package/dist/commands/validate.d.ts +3 -1
- package/dist/commands/validate.js +28 -9
- package/dist/commands/whoami.d.ts +3 -1
- package/dist/commands/whoami.js +13 -3
- package/dist/index.js +13 -5
- package/dist/playwright.d.ts +5 -2
- package/dist/playwright.js +24 -3
- package/dist/taskSelection.js +2 -3
- package/dist/taskUtils.js +4 -4
- package/dist/uploadLog.d.ts +1 -1
- package/dist/uploadLog.js +12 -3
- package/node_modules/@playdrop/config/client-meta.json +9 -9
- package/package.json +1 -1
package/dist/commands/upload.js
CHANGED
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.upload = upload;
|
|
4
4
|
const node_fs_1 = require("node:fs");
|
|
5
5
|
const node_path_1 = require("node:path");
|
|
6
|
+
const semver_1 = require("semver");
|
|
6
7
|
const types_1 = require("@playdrop/types");
|
|
7
8
|
const apps_1 = require("../apps");
|
|
8
9
|
const commandContext_1 = require("../commandContext");
|
|
@@ -182,6 +183,522 @@ function normalizeCreatorUsername(username) {
|
|
|
182
183
|
}
|
|
183
184
|
return trimmed;
|
|
184
185
|
}
|
|
186
|
+
function isNotFoundApiError(error) {
|
|
187
|
+
return error instanceof types_1.ApiError && error.status === 404;
|
|
188
|
+
}
|
|
189
|
+
function createRemoteDependencyCache() {
|
|
190
|
+
return {
|
|
191
|
+
assetSpecVersionExists: new Map(),
|
|
192
|
+
assetSpecSupportExists: new Map(),
|
|
193
|
+
assetVersionExists: new Map(),
|
|
194
|
+
packVersionExists: new Map(),
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
function createTaskExecutionId(task, creator, index) {
|
|
198
|
+
if (task.kind === 'asset-spec') {
|
|
199
|
+
return `${index}:${task.kind}:${creator}/${task.name}@${task.version}`;
|
|
200
|
+
}
|
|
201
|
+
if (task.kind === 'asset-pack') {
|
|
202
|
+
return `${index}:${task.kind}:${creator}/${task.name}@${task.version}`;
|
|
203
|
+
}
|
|
204
|
+
if (task.kind === 'owned-asset') {
|
|
205
|
+
return `${index}:${task.kind}:${creator}/${task.appName}:${task.name}`;
|
|
206
|
+
}
|
|
207
|
+
return `${index}:${task.kind}:${creator}/${task.name}`;
|
|
208
|
+
}
|
|
209
|
+
function buildTaskDetailEntry(task, entityId, status, detail) {
|
|
210
|
+
return {
|
|
211
|
+
action: 'upload',
|
|
212
|
+
status,
|
|
213
|
+
entityType: task.kind,
|
|
214
|
+
entityId,
|
|
215
|
+
catalogue: task.cataloguePath,
|
|
216
|
+
detail,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
function buildTaskDirectErrorEntry(task, entityId, detail) {
|
|
220
|
+
return buildTaskDetailEntry(task, entityId, 'error', detail.startsWith('upload failed:') ? detail : `upload failed: ${detail}`);
|
|
221
|
+
}
|
|
222
|
+
function buildTaskBlockedEntry(task, entityId, detail) {
|
|
223
|
+
return buildTaskDetailEntry(task, entityId, 'blocked', detail);
|
|
224
|
+
}
|
|
225
|
+
function buildAssetSpecFamilyKey(creatorUsername, name) {
|
|
226
|
+
return `${creatorUsername}/${name}`;
|
|
227
|
+
}
|
|
228
|
+
function buildAssetSpecVersionKey(creatorUsername, name, version) {
|
|
229
|
+
return `${creatorUsername}/${name}@${version}`;
|
|
230
|
+
}
|
|
231
|
+
function pushIndexedNode(map, key, node) {
|
|
232
|
+
const existing = map.get(key);
|
|
233
|
+
if (existing) {
|
|
234
|
+
existing.push(node);
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
map.set(key, [node]);
|
|
238
|
+
}
|
|
239
|
+
function pickSingleLocalDependency(candidates, ref) {
|
|
240
|
+
if (!candidates || candidates.length === 0) {
|
|
241
|
+
return { node: null, blockedDetail: null };
|
|
242
|
+
}
|
|
243
|
+
if (candidates.length > 1) {
|
|
244
|
+
return {
|
|
245
|
+
node: null,
|
|
246
|
+
blockedDetail: `local_dependency_ambiguous:${ref}`,
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
return { node: candidates[0], blockedDetail: null };
|
|
250
|
+
}
|
|
251
|
+
function pickCompatibleLocalAssetSpecTask(candidates, versionRange) {
|
|
252
|
+
if (!candidates || candidates.length === 0) {
|
|
253
|
+
return null;
|
|
254
|
+
}
|
|
255
|
+
const matching = candidates.filter((candidate) => (candidate.task.kind === 'asset-spec' && (0, semver_1.satisfies)(candidate.task.version, versionRange)));
|
|
256
|
+
if (matching.length === 0) {
|
|
257
|
+
return null;
|
|
258
|
+
}
|
|
259
|
+
matching.sort((left, right) => (0, semver_1.compare)(right.task.version, left.task.version));
|
|
260
|
+
return matching[0];
|
|
261
|
+
}
|
|
262
|
+
async function validateRemoteAssetSpecVersionExists(client, rawRef, cache) {
|
|
263
|
+
if (cache.assetSpecVersionExists.has(rawRef)) {
|
|
264
|
+
return cache.assetSpecVersionExists.get(rawRef);
|
|
265
|
+
}
|
|
266
|
+
const parsed = (0, types_1.parseAssetSpecVersionRef)(rawRef);
|
|
267
|
+
if (!parsed) {
|
|
268
|
+
cache.assetSpecVersionExists.set(rawRef, false);
|
|
269
|
+
return false;
|
|
270
|
+
}
|
|
271
|
+
try {
|
|
272
|
+
await client.fetchAssetSpecVersion(parsed.creatorUsername, parsed.name, parsed.version);
|
|
273
|
+
cache.assetSpecVersionExists.set(rawRef, true);
|
|
274
|
+
return true;
|
|
275
|
+
}
|
|
276
|
+
catch (error) {
|
|
277
|
+
if (isNotFoundApiError(error)) {
|
|
278
|
+
cache.assetSpecVersionExists.set(rawRef, false);
|
|
279
|
+
return false;
|
|
280
|
+
}
|
|
281
|
+
throw error;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
async function validateRemoteAssetSpecSupportExists(client, familyRef, versionRange, cache) {
|
|
285
|
+
const cacheKey = `${familyRef}@@${versionRange}`;
|
|
286
|
+
if (cache.assetSpecSupportExists.has(cacheKey)) {
|
|
287
|
+
return cache.assetSpecSupportExists.get(cacheKey);
|
|
288
|
+
}
|
|
289
|
+
const parsed = (0, types_1.parseAssetSpecFamilyRef)(familyRef);
|
|
290
|
+
if (!parsed) {
|
|
291
|
+
cache.assetSpecSupportExists.set(cacheKey, false);
|
|
292
|
+
return false;
|
|
293
|
+
}
|
|
294
|
+
try {
|
|
295
|
+
const response = await client.listAssetSpecVersions(parsed.creatorUsername, parsed.name, { limit: 200, offset: 0 });
|
|
296
|
+
const exists = response.versions.some((version) => (0, semver_1.satisfies)(version.version, versionRange));
|
|
297
|
+
cache.assetSpecSupportExists.set(cacheKey, exists);
|
|
298
|
+
return exists;
|
|
299
|
+
}
|
|
300
|
+
catch (error) {
|
|
301
|
+
if (isNotFoundApiError(error)) {
|
|
302
|
+
cache.assetSpecSupportExists.set(cacheKey, false);
|
|
303
|
+
return false;
|
|
304
|
+
}
|
|
305
|
+
throw error;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
async function validateRemoteAssetVersionExists(client, rawRef, cache) {
|
|
309
|
+
if (cache.assetVersionExists.has(rawRef)) {
|
|
310
|
+
return cache.assetVersionExists.get(rawRef);
|
|
311
|
+
}
|
|
312
|
+
const parsed = (0, types_1.parseContentVersionRef)(rawRef);
|
|
313
|
+
if (!parsed || parsed.kind !== 'asset') {
|
|
314
|
+
cache.assetVersionExists.set(rawRef, false);
|
|
315
|
+
return false;
|
|
316
|
+
}
|
|
317
|
+
try {
|
|
318
|
+
const detail = await client.fetchAssetBySlug(parsed.creatorUsername, parsed.name);
|
|
319
|
+
if (detail.asset.currentVersion?.revision === parsed.revision) {
|
|
320
|
+
cache.assetVersionExists.set(rawRef, true);
|
|
321
|
+
return true;
|
|
322
|
+
}
|
|
323
|
+
const versions = await client.listAssetVersions(parsed.creatorUsername, parsed.name, { limit: 200, offset: 0 });
|
|
324
|
+
const exists = versions.versions.some((version) => version.revision === parsed.revision);
|
|
325
|
+
cache.assetVersionExists.set(rawRef, exists);
|
|
326
|
+
return exists;
|
|
327
|
+
}
|
|
328
|
+
catch (error) {
|
|
329
|
+
if (isNotFoundApiError(error)) {
|
|
330
|
+
cache.assetVersionExists.set(rawRef, false);
|
|
331
|
+
return false;
|
|
332
|
+
}
|
|
333
|
+
throw error;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
async function validateRemotePackVersionExists(client, rawRef, cache) {
|
|
337
|
+
if (cache.packVersionExists.has(rawRef)) {
|
|
338
|
+
return cache.packVersionExists.get(rawRef);
|
|
339
|
+
}
|
|
340
|
+
const parsed = (0, types_1.parseContentVersionRef)(rawRef);
|
|
341
|
+
if (!parsed || parsed.kind !== 'pack') {
|
|
342
|
+
cache.packVersionExists.set(rawRef, false);
|
|
343
|
+
return false;
|
|
344
|
+
}
|
|
345
|
+
try {
|
|
346
|
+
const detail = await client.fetchAssetPackBySlug(parsed.creatorUsername, parsed.name);
|
|
347
|
+
if (detail.pack.currentVersion?.version === parsed.version) {
|
|
348
|
+
cache.packVersionExists.set(rawRef, true);
|
|
349
|
+
return true;
|
|
350
|
+
}
|
|
351
|
+
const versions = await client.listAssetPackVersions(parsed.creatorUsername, parsed.name, { limit: 200, offset: 0 });
|
|
352
|
+
const exists = versions.versions.some((version) => version.version === parsed.version);
|
|
353
|
+
cache.packVersionExists.set(rawRef, exists);
|
|
354
|
+
return exists;
|
|
355
|
+
}
|
|
356
|
+
catch (error) {
|
|
357
|
+
if (isNotFoundApiError(error)) {
|
|
358
|
+
cache.packVersionExists.set(rawRef, false);
|
|
359
|
+
return false;
|
|
360
|
+
}
|
|
361
|
+
throw error;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
function addLocalDependency(node, dependencyTaskId, ref) {
|
|
365
|
+
if (node.localDependencies.some((dependency) => dependency.taskId === dependencyTaskId)) {
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
node.localDependencies.push({ taskId: dependencyTaskId, ref });
|
|
369
|
+
}
|
|
370
|
+
function normalizePackDependencyRef(rawRef) {
|
|
371
|
+
const parsed = (0, types_1.parseContentVersionRef)(rawRef);
|
|
372
|
+
if (!parsed || parsed.kind !== 'pack') {
|
|
373
|
+
return null;
|
|
374
|
+
}
|
|
375
|
+
return (0, types_1.formatContentVersionRef)(parsed);
|
|
376
|
+
}
|
|
377
|
+
function buildTagRegistryEntry(allowedTargetKinds) {
|
|
378
|
+
return {
|
|
379
|
+
allowedTargetKinds: normalizeSavedItemKinds(allowedTargetKinds),
|
|
380
|
+
tags: new Set(),
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
function mergeTagRegistryEntry(registry, groupSlug, entry, options) {
|
|
384
|
+
const existing = registry.get(groupSlug);
|
|
385
|
+
if (!existing) {
|
|
386
|
+
registry.set(groupSlug, {
|
|
387
|
+
allowedTargetKinds: [...entry.allowedTargetKinds],
|
|
388
|
+
tags: new Set(entry.tags),
|
|
389
|
+
});
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
if (options.replaceAllowedTargetKinds) {
|
|
393
|
+
existing.allowedTargetKinds = [...entry.allowedTargetKinds];
|
|
394
|
+
}
|
|
395
|
+
entry.tags.forEach((tagSlug) => existing.tags.add(tagSlug));
|
|
396
|
+
}
|
|
397
|
+
async function buildRemoteTagRegistry(client) {
|
|
398
|
+
const registry = new Map();
|
|
399
|
+
let directoryOffset = 0;
|
|
400
|
+
while (true) {
|
|
401
|
+
const directory = await client.fetchTagDirectory({
|
|
402
|
+
limit: 200,
|
|
403
|
+
...(directoryOffset > 0 ? { offset: directoryOffset } : {}),
|
|
404
|
+
tagLimit: 1,
|
|
405
|
+
});
|
|
406
|
+
for (const summary of directory.groups) {
|
|
407
|
+
const groupSlug = summary.group.slug;
|
|
408
|
+
const tagCount = Math.max(summary.tagCount, summary.tags.length);
|
|
409
|
+
const entry = buildTagRegistryEntry(summary.group.allowedTargetKinds);
|
|
410
|
+
let offset = 0;
|
|
411
|
+
while (offset < tagCount || (tagCount === 0 && offset === 0)) {
|
|
412
|
+
const response = await client.fetchTagGroup(groupSlug, {
|
|
413
|
+
sort: 'alpha',
|
|
414
|
+
limit: 200,
|
|
415
|
+
...(offset > 0 ? { offset } : {}),
|
|
416
|
+
});
|
|
417
|
+
response.tags.forEach((tag) => entry.tags.add(tag.slug));
|
|
418
|
+
if (response.tags.length === 0 || response.tags.length < 200) {
|
|
419
|
+
break;
|
|
420
|
+
}
|
|
421
|
+
offset += response.tags.length;
|
|
422
|
+
}
|
|
423
|
+
mergeTagRegistryEntry(registry, groupSlug, entry, { replaceAllowedTargetKinds: true });
|
|
424
|
+
}
|
|
425
|
+
if (!directory.pagination?.hasMore) {
|
|
426
|
+
break;
|
|
427
|
+
}
|
|
428
|
+
directoryOffset += directory.groups.length;
|
|
429
|
+
}
|
|
430
|
+
return registry;
|
|
431
|
+
}
|
|
432
|
+
function buildLocalTagRegistry(groups) {
|
|
433
|
+
const registry = new Map();
|
|
434
|
+
for (const group of groups) {
|
|
435
|
+
const entry = buildTagRegistryEntry(group.allowedTargetKinds);
|
|
436
|
+
group.tags.forEach((tag) => entry.tags.add(tag.slug));
|
|
437
|
+
mergeTagRegistryEntry(registry, group.slug, entry, { replaceAllowedTargetKinds: true });
|
|
438
|
+
}
|
|
439
|
+
return registry;
|
|
440
|
+
}
|
|
441
|
+
async function buildUploadTagRegistry(client, localGroups) {
|
|
442
|
+
const registry = await buildRemoteTagRegistry(client);
|
|
443
|
+
const localRegistry = buildLocalTagRegistry(localGroups);
|
|
444
|
+
for (const [groupSlug, entry] of localRegistry.entries()) {
|
|
445
|
+
mergeTagRegistryEntry(registry, groupSlug, entry, { replaceAllowedTargetKinds: true });
|
|
446
|
+
}
|
|
447
|
+
return registry;
|
|
448
|
+
}
|
|
449
|
+
function validateTagRefsForTarget(tagRegistry, target) {
|
|
450
|
+
for (const rawTagRef of target.tags) {
|
|
451
|
+
let normalizedRef = '';
|
|
452
|
+
try {
|
|
453
|
+
normalizedRef = (0, types_1.normalizeTagRef)(rawTagRef);
|
|
454
|
+
}
|
|
455
|
+
catch {
|
|
456
|
+
return `invalid_tag_ref:${String(rawTagRef ?? '').trim() || String(rawTagRef ?? '')}`;
|
|
457
|
+
}
|
|
458
|
+
const [groupSlug, tagSlug] = normalizedRef.split('/');
|
|
459
|
+
if (!groupSlug || !tagSlug) {
|
|
460
|
+
return `invalid_tag_ref:${normalizedRef}`;
|
|
461
|
+
}
|
|
462
|
+
const registryEntry = tagRegistry.get(groupSlug);
|
|
463
|
+
if (!registryEntry || !registryEntry.tags.has(tagSlug)) {
|
|
464
|
+
return `unknown_tag_ref:${normalizedRef}`;
|
|
465
|
+
}
|
|
466
|
+
if (registryEntry.allowedTargetKinds.length > 0
|
|
467
|
+
&& !registryEntry.allowedTargetKinds.includes(target.targetKind)) {
|
|
468
|
+
return `tag_not_allowed_for_target_kind:${normalizedRef}:${target.targetKind}`;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
return null;
|
|
472
|
+
}
|
|
473
|
+
function collectTaskTagValidationTargets(task) {
|
|
474
|
+
if (task.kind === 'app') {
|
|
475
|
+
return [
|
|
476
|
+
{ tags: task.tags, targetKind: 'APP' },
|
|
477
|
+
...task.ownedAssets.map((ownedAsset) => ({
|
|
478
|
+
tags: ownedAsset.tags,
|
|
479
|
+
targetKind: 'ASSET',
|
|
480
|
+
})),
|
|
481
|
+
];
|
|
482
|
+
}
|
|
483
|
+
if (task.kind === 'asset') {
|
|
484
|
+
return [{ tags: task.tags, targetKind: 'ASSET' }];
|
|
485
|
+
}
|
|
486
|
+
if (task.kind === 'owned-asset') {
|
|
487
|
+
return [{ tags: task.tags, targetKind: 'ASSET' }];
|
|
488
|
+
}
|
|
489
|
+
if (task.kind === 'asset-pack') {
|
|
490
|
+
return [
|
|
491
|
+
{ tags: task.tags, targetKind: 'PACK' },
|
|
492
|
+
...task.ownedAssets.map((ownedAsset) => ({
|
|
493
|
+
tags: ownedAsset.tags,
|
|
494
|
+
targetKind: 'ASSET',
|
|
495
|
+
})),
|
|
496
|
+
];
|
|
497
|
+
}
|
|
498
|
+
return [];
|
|
499
|
+
}
|
|
500
|
+
function tasksRequireTagValidation(tasks) {
|
|
501
|
+
return tasks.some((task) => collectTaskTagValidationTargets(task).some((target) => target.tags.length > 0));
|
|
502
|
+
}
|
|
503
|
+
async function buildUploadPreflight(client, tasks, defaultCreator, currentUserRole, localTagGroups) {
|
|
504
|
+
const nodes = (0, taskUtils_1.sortTasks)(tasks).map((task, index) => {
|
|
505
|
+
const creatorResult = (0, upload_content_1.getTaskCreatorResult)(task, defaultCreator, currentUserRole);
|
|
506
|
+
const entityId = buildTaskEntityId(task, creatorResult.taskCreator);
|
|
507
|
+
const node = {
|
|
508
|
+
id: createTaskExecutionId(task, creatorResult.taskCreator, index),
|
|
509
|
+
task,
|
|
510
|
+
creator: creatorResult.taskCreator,
|
|
511
|
+
entityId,
|
|
512
|
+
localDependencies: [],
|
|
513
|
+
localAppAssetKeysByRef: new Map(),
|
|
514
|
+
localAppPackRefsByRef: new Map(),
|
|
515
|
+
};
|
|
516
|
+
return { node, creatorResult };
|
|
517
|
+
});
|
|
518
|
+
const initialEntries = [];
|
|
519
|
+
for (const { node, creatorResult } of nodes) {
|
|
520
|
+
if (creatorResult.creatorTargetError) {
|
|
521
|
+
initialEntries.push({
|
|
522
|
+
taskId: node.id,
|
|
523
|
+
entry: buildTaskDirectErrorEntry(node.task, node.entityId, creatorResult.creatorTargetError),
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
const assetSpecVersionTasksByKey = new Map();
|
|
528
|
+
const assetSpecFamilyTasksByKey = new Map();
|
|
529
|
+
const assetTasksByKey = new Map();
|
|
530
|
+
const packTasksByKey = new Map();
|
|
531
|
+
for (const { node } of nodes) {
|
|
532
|
+
if (node.task.kind === 'asset-spec') {
|
|
533
|
+
pushIndexedNode(assetSpecVersionTasksByKey, buildAssetSpecVersionKey(node.creator, node.task.name, node.task.version), node);
|
|
534
|
+
pushIndexedNode(assetSpecFamilyTasksByKey, buildAssetSpecFamilyKey(node.creator, node.task.name), node);
|
|
535
|
+
continue;
|
|
536
|
+
}
|
|
537
|
+
if (node.task.kind === 'asset') {
|
|
538
|
+
pushIndexedNode(assetTasksByKey, (0, upload_content_1.buildAssetKey)(node.creator, node.task.name), node);
|
|
539
|
+
continue;
|
|
540
|
+
}
|
|
541
|
+
if (node.task.kind === 'asset-pack') {
|
|
542
|
+
pushIndexedNode(packTasksByKey, (0, upload_content_1.buildPackKey)(node.creator, node.task.name, node.task.version), node);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
const remoteCache = createRemoteDependencyCache();
|
|
546
|
+
const tagRegistry = tasksRequireTagValidation(tasks)
|
|
547
|
+
? await buildUploadTagRegistry(client, localTagGroups)
|
|
548
|
+
: null;
|
|
549
|
+
const initialStatusByTaskId = new Map(initialEntries.map((entry) => [entry.taskId, entry.entry.status]));
|
|
550
|
+
for (const { node } of nodes) {
|
|
551
|
+
if (initialStatusByTaskId.has(node.id)) {
|
|
552
|
+
continue;
|
|
553
|
+
}
|
|
554
|
+
let blockedDetail = null;
|
|
555
|
+
if (!blockedDetail && tagRegistry) {
|
|
556
|
+
for (const target of collectTaskTagValidationTargets(node.task)) {
|
|
557
|
+
blockedDetail = validateTagRefsForTarget(tagRegistry, target);
|
|
558
|
+
if (blockedDetail) {
|
|
559
|
+
break;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
if (!blockedDetail && node.task.kind === 'asset' && node.task.assetSpec) {
|
|
564
|
+
const parsedAssetSpec = (0, types_1.parseAssetSpecVersionRef)(node.task.assetSpec);
|
|
565
|
+
if (!parsedAssetSpec) {
|
|
566
|
+
blockedDetail = `dependency_invalid:${node.task.assetSpec}`;
|
|
567
|
+
}
|
|
568
|
+
else {
|
|
569
|
+
const assetSpecKey = buildAssetSpecVersionKey(parsedAssetSpec.creatorUsername, parsedAssetSpec.name, parsedAssetSpec.version);
|
|
570
|
+
const localDependency = pickSingleLocalDependency(assetSpecVersionTasksByKey.get(assetSpecKey), (0, types_1.formatAssetSpecVersionRef)(parsedAssetSpec));
|
|
571
|
+
if (localDependency.blockedDetail) {
|
|
572
|
+
blockedDetail = localDependency.blockedDetail;
|
|
573
|
+
}
|
|
574
|
+
else if (localDependency.node) {
|
|
575
|
+
addLocalDependency(node, localDependency.node.id, (0, types_1.formatAssetSpecVersionRef)(parsedAssetSpec));
|
|
576
|
+
}
|
|
577
|
+
else if (!(await validateRemoteAssetSpecVersionExists(client, (0, types_1.formatAssetSpecVersionRef)(parsedAssetSpec), remoteCache))) {
|
|
578
|
+
blockedDetail = `dependency_missing:${(0, types_1.formatAssetSpecVersionRef)(parsedAssetSpec)}`;
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
if (!blockedDetail && node.task.kind === 'app') {
|
|
583
|
+
for (const support of node.task.assetSpecSupport) {
|
|
584
|
+
const parsedFamily = (0, types_1.parseAssetSpecFamilyRef)(support.assetSpec);
|
|
585
|
+
if (!parsedFamily) {
|
|
586
|
+
blockedDetail = `dependency_invalid:${support.assetSpec}`;
|
|
587
|
+
break;
|
|
588
|
+
}
|
|
589
|
+
const familyKey = buildAssetSpecFamilyKey(parsedFamily.creatorUsername, parsedFamily.name);
|
|
590
|
+
const localDependency = pickCompatibleLocalAssetSpecTask(assetSpecFamilyTasksByKey.get(familyKey), support.versionRange);
|
|
591
|
+
if (localDependency) {
|
|
592
|
+
addLocalDependency(node, localDependency.id, `${support.assetSpec}@${support.versionRange}`);
|
|
593
|
+
continue;
|
|
594
|
+
}
|
|
595
|
+
const remoteExists = await validateRemoteAssetSpecSupportExists(client, support.assetSpec, support.versionRange, remoteCache);
|
|
596
|
+
if (!remoteExists) {
|
|
597
|
+
blockedDetail = `dependency_missing:${support.assetSpec}@${support.versionRange}`;
|
|
598
|
+
break;
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
if (!blockedDetail && node.task.kind === 'app') {
|
|
603
|
+
for (const assetDependency of node.task.uses.assets) {
|
|
604
|
+
const parsedAsset = (0, types_1.parseContentVersionRef)(assetDependency.ref);
|
|
605
|
+
if (!parsedAsset || parsedAsset.kind !== 'asset') {
|
|
606
|
+
blockedDetail = `dependency_invalid:${assetDependency.ref}`;
|
|
607
|
+
break;
|
|
608
|
+
}
|
|
609
|
+
const assetRef = (0, types_1.formatContentVersionRef)(parsedAsset);
|
|
610
|
+
const assetKey = (0, upload_content_1.buildAssetKey)(parsedAsset.creatorUsername, parsedAsset.name);
|
|
611
|
+
const localDependency = pickSingleLocalDependency(assetTasksByKey.get(assetKey), assetRef);
|
|
612
|
+
if (localDependency.blockedDetail) {
|
|
613
|
+
blockedDetail = localDependency.blockedDetail;
|
|
614
|
+
break;
|
|
615
|
+
}
|
|
616
|
+
if (localDependency.node) {
|
|
617
|
+
addLocalDependency(node, localDependency.node.id, assetRef);
|
|
618
|
+
node.localAppAssetKeysByRef.set(assetRef, (0, upload_content_1.buildAssetKey)(localDependency.node.creator, localDependency.node.task.name));
|
|
619
|
+
continue;
|
|
620
|
+
}
|
|
621
|
+
if (!(await validateRemoteAssetVersionExists(client, assetRef, remoteCache))) {
|
|
622
|
+
blockedDetail = `dependency_missing:${assetRef}`;
|
|
623
|
+
break;
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
if (!blockedDetail && node.task.kind === 'app') {
|
|
628
|
+
for (const rawPackRef of node.task.uses.packs) {
|
|
629
|
+
const normalizedPackRef = normalizePackDependencyRef(rawPackRef);
|
|
630
|
+
if (!normalizedPackRef) {
|
|
631
|
+
blockedDetail = `dependency_invalid:${rawPackRef.trim() || rawPackRef}`;
|
|
632
|
+
break;
|
|
633
|
+
}
|
|
634
|
+
const parsedPack = (0, types_1.parseContentVersionRef)(normalizedPackRef);
|
|
635
|
+
if (!parsedPack || parsedPack.kind !== 'pack') {
|
|
636
|
+
blockedDetail = `dependency_invalid:${normalizedPackRef}`;
|
|
637
|
+
break;
|
|
638
|
+
}
|
|
639
|
+
const localDependency = pickSingleLocalDependency(packTasksByKey.get((0, upload_content_1.buildPackKey)(parsedPack.creatorUsername, parsedPack.name, parsedPack.version)), normalizedPackRef);
|
|
640
|
+
if (localDependency.blockedDetail) {
|
|
641
|
+
blockedDetail = localDependency.blockedDetail;
|
|
642
|
+
break;
|
|
643
|
+
}
|
|
644
|
+
if (localDependency.node) {
|
|
645
|
+
addLocalDependency(node, localDependency.node.id, normalizedPackRef);
|
|
646
|
+
if (localDependency.node.task.kind === 'asset-pack') {
|
|
647
|
+
node.localAppPackRefsByRef.set(normalizedPackRef, `pack:${localDependency.node.creator}/${localDependency.node.task.name}@${localDependency.node.task.version}`);
|
|
648
|
+
}
|
|
649
|
+
continue;
|
|
650
|
+
}
|
|
651
|
+
if (!(await validateRemotePackVersionExists(client, normalizedPackRef, remoteCache))) {
|
|
652
|
+
blockedDetail = `dependency_missing:${normalizedPackRef}`;
|
|
653
|
+
break;
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
if (blockedDetail) {
|
|
658
|
+
initialEntries.push({
|
|
659
|
+
taskId: node.id,
|
|
660
|
+
entry: buildTaskBlockedEntry(node.task, node.entityId, blockedDetail),
|
|
661
|
+
});
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
return {
|
|
665
|
+
nodes: nodes.map((entry) => entry.node),
|
|
666
|
+
initialEntries,
|
|
667
|
+
};
|
|
668
|
+
}
|
|
669
|
+
function resolveAppTaskLocalDependencies(task, node, state) {
|
|
670
|
+
if (node.localAppAssetKeysByRef.size === 0 && node.localAppPackRefsByRef.size === 0) {
|
|
671
|
+
return task;
|
|
672
|
+
}
|
|
673
|
+
const usesAssets = task.uses.assets.map((dependency) => {
|
|
674
|
+
const assetKey = node.localAppAssetKeysByRef.get(dependency.ref);
|
|
675
|
+
if (!assetKey) {
|
|
676
|
+
return dependency;
|
|
677
|
+
}
|
|
678
|
+
const uploaded = state.uploadedAssetsByKey.get(assetKey);
|
|
679
|
+
if (!uploaded) {
|
|
680
|
+
throw new Error(`Local asset dependency "${dependency.ref}" did not produce an uploaded asset ref.`);
|
|
681
|
+
}
|
|
682
|
+
return {
|
|
683
|
+
...dependency,
|
|
684
|
+
ref: uploaded.ref,
|
|
685
|
+
};
|
|
686
|
+
});
|
|
687
|
+
const usesPacks = task.uses.packs.map((rawRef) => {
|
|
688
|
+
const normalized = normalizePackDependencyRef(rawRef);
|
|
689
|
+
if (!normalized) {
|
|
690
|
+
return rawRef;
|
|
691
|
+
}
|
|
692
|
+
return node.localAppPackRefsByRef.get(normalized) ?? normalized;
|
|
693
|
+
});
|
|
694
|
+
return {
|
|
695
|
+
...task,
|
|
696
|
+
uses: {
|
|
697
|
+
assets: usesAssets,
|
|
698
|
+
packs: usesPacks,
|
|
699
|
+
},
|
|
700
|
+
};
|
|
701
|
+
}
|
|
185
702
|
function buildAppOverviewUrl(base, creator, task) {
|
|
186
703
|
const typeSlug = (0, appUrls_1.getAppTypeSlug)(task.type ?? 'GAME');
|
|
187
704
|
const encodedCreator = encodeURIComponent(creator);
|
|
@@ -233,6 +750,22 @@ function buildUploadErrorDetail(error) {
|
|
|
233
750
|
if (error instanceof types_1.ApiError && error.code === 'tag_clear_confirmation_required') {
|
|
234
751
|
return 'tag_clear_confirmation_required: This publish would remove existing live tags. Re-run with --clear-tags to confirm.';
|
|
235
752
|
}
|
|
753
|
+
if (error instanceof types_1.ApiError) {
|
|
754
|
+
const ref = typeof error.details?.ref === 'string' ? error.details.ref.trim() : '';
|
|
755
|
+
const targetKind = typeof error.details?.targetKind === 'string' ? error.details.targetKind.trim() : '';
|
|
756
|
+
if (error.code === 'unknown_tag_ref' && ref) {
|
|
757
|
+
return `unknown_tag_ref:${ref}`;
|
|
758
|
+
}
|
|
759
|
+
if (error.code === 'invalid_tag_ref' && ref) {
|
|
760
|
+
return `invalid_tag_ref:${ref}`;
|
|
761
|
+
}
|
|
762
|
+
if (error.code === 'duplicate_tag_ref' && ref) {
|
|
763
|
+
return `duplicate_tag_ref:${ref}`;
|
|
764
|
+
}
|
|
765
|
+
if (error.code === 'tag_not_allowed_for_target_kind' && ref && targetKind) {
|
|
766
|
+
return `tag_not_allowed_for_target_kind:${ref}:${targetKind}`;
|
|
767
|
+
}
|
|
768
|
+
}
|
|
236
769
|
const rawMessage = typeof error?.message === 'string' && error.message.trim()
|
|
237
770
|
? error.message.trim()
|
|
238
771
|
: 'upload failed';
|
|
@@ -370,12 +903,12 @@ async function uploadStandaloneAssetTask(state, task, taskCreator, options) {
|
|
|
370
903
|
appendOverviewLink(entry, task, state.portalBase, uploaded.creatorUsername);
|
|
371
904
|
return entry;
|
|
372
905
|
}
|
|
373
|
-
async function uploadOwnedAssetTask(state, task, taskCreator) {
|
|
906
|
+
async function uploadOwnedAssetTask(state, task, taskCreator, options) {
|
|
374
907
|
const sourceApp = state.uploadedAppsByName.get(task.appName);
|
|
375
908
|
if (!sourceApp) {
|
|
376
909
|
throw new Error(`Owned asset "${task.name}" references app "${task.appName}" that was not uploaded in this run.`);
|
|
377
910
|
}
|
|
378
|
-
const uploaded = await (0, upload_content_1.uploadAssetTask)(state.client, task, sourceApp.versionId, sourceApp.creatorUsername);
|
|
911
|
+
const uploaded = await (0, upload_content_1.uploadAssetTask)(state.client, task, sourceApp.versionId, sourceApp.creatorUsername, { clearTags: options?.clearTags });
|
|
379
912
|
state.uploadedAssetsByKey.set(`${uploaded.creatorUsername}/${uploaded.name}`, uploaded);
|
|
380
913
|
(0, upload_graph_1.registerCanonicalNode)(state.graphState, uploaded.ref, uploaded.versionNodeId);
|
|
381
914
|
(0, upload_graph_1.registerLocalRef)(state.graphState.localAssetNodeByName, state.graphState.ambiguousAssetNames, task.name, uploaded.versionNodeId);
|
|
@@ -446,7 +979,10 @@ async function uploadPackTask(state, task, taskCreator, options) {
|
|
|
446
979
|
appendOverviewLink(entry, task, state.portalBase, taskCreator);
|
|
447
980
|
return entry;
|
|
448
981
|
}
|
|
449
|
-
async function processSingleUploadTask(state,
|
|
982
|
+
async function processSingleUploadTask(state, node, taskCreator, options) {
|
|
983
|
+
const task = node.task.kind === 'app'
|
|
984
|
+
? resolveAppTaskLocalDependencies(node.task, node, state)
|
|
985
|
+
: node.task;
|
|
450
986
|
if (task.kind === 'app') {
|
|
451
987
|
return await uploadAppTask(state, task, taskCreator, options);
|
|
452
988
|
}
|
|
@@ -457,7 +993,7 @@ async function processSingleUploadTask(state, task, taskCreator, options) {
|
|
|
457
993
|
return { entry: await uploadStandaloneAssetTask(state, task, taskCreator, options), warnings: [] };
|
|
458
994
|
}
|
|
459
995
|
if (task.kind === 'owned-asset') {
|
|
460
|
-
return { entry: await uploadOwnedAssetTask(state, task, taskCreator), warnings: [] };
|
|
996
|
+
return { entry: await uploadOwnedAssetTask(state, task, taskCreator, options), warnings: [] };
|
|
461
997
|
}
|
|
462
998
|
if (task.kind === 'asset-pack') {
|
|
463
999
|
return { entry: await uploadPackTask(state, task, taskCreator, options), warnings: [] };
|
|
@@ -484,17 +1020,21 @@ async function flushGraphState(client, graphState, results) {
|
|
|
484
1020
|
process.exitCode = process.exitCode || 1;
|
|
485
1021
|
}
|
|
486
1022
|
}
|
|
487
|
-
async function processUploadTasks(client, tasks, owner, ownerUsername, currentUserRole, currentUser, token, warnings, apiBase, webBase, options) {
|
|
1023
|
+
async function processUploadTasks(client, tasks, owner, ownerUsername, currentUserRole, localTagGroups, currentUser, token, warnings, apiBase, webBase, options) {
|
|
488
1024
|
const portalBase = normalizePortalBase(webBase);
|
|
489
1025
|
const defaultCreator = normalizeCreatorUsername(ownerUsername) ?? owner;
|
|
490
1026
|
const results = [];
|
|
491
|
-
|
|
492
|
-
const sortedTasks = (
|
|
1027
|
+
const preflight = await buildUploadPreflight(client, tasks, defaultCreator, currentUserRole, localTagGroups);
|
|
1028
|
+
const sortedTasks = preflight.nodes.map((node) => node.task);
|
|
493
1029
|
const packPlanning = (0, upload_content_1.buildAssetPackUploadPlans)(sortedTasks, defaultCreator, currentUserRole);
|
|
1030
|
+
const taskStatusById = new Map(preflight.nodes.map((node) => [node.id, 'pending']));
|
|
1031
|
+
preflight.initialEntries.forEach(({ taskId, entry }) => {
|
|
1032
|
+
taskStatusById.set(taskId, entry.status);
|
|
1033
|
+
pushLoggedEntry(results, entry);
|
|
1034
|
+
});
|
|
494
1035
|
if (!packPlanning.ok) {
|
|
495
1036
|
const entry = buildPackPlanningFailureEntry(packPlanning.task, defaultCreator, currentUserRole, packPlanning.message);
|
|
496
1037
|
pushLoggedEntry(results, entry);
|
|
497
|
-
process.exitCode = process.exitCode || 1;
|
|
498
1038
|
return results;
|
|
499
1039
|
}
|
|
500
1040
|
const state = {
|
|
@@ -511,45 +1051,85 @@ async function processUploadTasks(client, tasks, owner, ownerUsername, currentUs
|
|
|
511
1051
|
graphState: (0, upload_graph_1.buildEmptyGraphState)(),
|
|
512
1052
|
packPlanning,
|
|
513
1053
|
};
|
|
514
|
-
|
|
515
|
-
const
|
|
516
|
-
if (
|
|
517
|
-
|
|
1054
|
+
while (true) {
|
|
1055
|
+
const pendingNodes = preflight.nodes.filter((node) => taskStatusById.get(node.id) === 'pending');
|
|
1056
|
+
if (pendingNodes.length === 0) {
|
|
1057
|
+
break;
|
|
518
1058
|
}
|
|
519
|
-
|
|
520
|
-
const
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
throw new Error(creatorResult.creatorTargetError);
|
|
1059
|
+
let madeProgress = false;
|
|
1060
|
+
for (const node of preflight.nodes) {
|
|
1061
|
+
if (taskStatusById.get(node.id) !== 'pending') {
|
|
1062
|
+
continue;
|
|
524
1063
|
}
|
|
525
|
-
const
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
1064
|
+
const failedDependency = node.localDependencies.find((dependency) => {
|
|
1065
|
+
const dependencyStatus = taskStatusById.get(dependency.taskId);
|
|
1066
|
+
return dependencyStatus === 'error' || dependencyStatus === 'blocked';
|
|
1067
|
+
});
|
|
1068
|
+
if (failedDependency) {
|
|
1069
|
+
const entry = buildTaskBlockedEntry(node.task, node.entityId, `local_dependency_failed:${failedDependency.ref}`);
|
|
1070
|
+
taskStatusById.set(node.id, entry.status);
|
|
1071
|
+
pushLoggedEntry(results, entry);
|
|
1072
|
+
madeProgress = true;
|
|
1073
|
+
continue;
|
|
532
1074
|
}
|
|
533
|
-
const
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
1075
|
+
const waitingOnLocalDependency = node.localDependencies.some((dependency) => taskStatusById.get(dependency.taskId) === 'pending');
|
|
1076
|
+
if (waitingOnLocalDependency) {
|
|
1077
|
+
continue;
|
|
1078
|
+
}
|
|
1079
|
+
const creatorResult = (0, upload_content_1.getTaskCreatorResult)(node.task, defaultCreator, currentUserRole);
|
|
1080
|
+
if (creatorResult.requestedTaskOwner && !creatorResult.creatorTargetError) {
|
|
1081
|
+
console.log(`[Admin] Publishing ${node.task.name} as user: ${creatorResult.taskCreator}`);
|
|
1082
|
+
}
|
|
1083
|
+
try {
|
|
1084
|
+
const outcome = await processSingleUploadTask(state, node, node.creator, options);
|
|
1085
|
+
outcome.warnings.forEach((warning) => warnings.add(warning));
|
|
1086
|
+
taskStatusById.set(node.id, outcome.entry.status);
|
|
1087
|
+
pushLoggedEntry(results, outcome.entry);
|
|
1088
|
+
}
|
|
1089
|
+
catch (error) {
|
|
1090
|
+
if (error instanceof http_1.CLIUnsupportedClientError) {
|
|
1091
|
+
throw error;
|
|
1092
|
+
}
|
|
1093
|
+
const entry = buildTaskErrorEntry(node.task, node.entityId, error);
|
|
1094
|
+
taskStatusById.set(node.id, entry.status);
|
|
1095
|
+
pushLoggedEntry(results, entry);
|
|
1096
|
+
}
|
|
1097
|
+
madeProgress = true;
|
|
537
1098
|
break;
|
|
538
1099
|
}
|
|
1100
|
+
if (madeProgress) {
|
|
1101
|
+
continue;
|
|
1102
|
+
}
|
|
1103
|
+
for (const node of pendingNodes) {
|
|
1104
|
+
const entry = buildTaskDirectErrorEntry(node.task, node.entityId, 'dependency_cycle_detected');
|
|
1105
|
+
taskStatusById.set(node.id, entry.status);
|
|
1106
|
+
pushLoggedEntry(results, entry);
|
|
1107
|
+
}
|
|
539
1108
|
}
|
|
540
|
-
|
|
541
|
-
await flushGraphState(client, state.graphState, results);
|
|
542
|
-
}
|
|
1109
|
+
await flushGraphState(client, state.graphState, results);
|
|
543
1110
|
return results;
|
|
544
1111
|
}
|
|
1112
|
+
function printResolvedUploadAccount(input) {
|
|
1113
|
+
const username = typeof input.username === 'string' && input.username.trim().length > 0
|
|
1114
|
+
? input.username.trim()
|
|
1115
|
+
: 'unknown';
|
|
1116
|
+
console.log(`Resolved account: ${username} (${input.env})`);
|
|
1117
|
+
}
|
|
545
1118
|
async function upload(pathOrName, options) {
|
|
546
1119
|
const selection = (0, taskSelection_1.selectTasks)(pathOrName);
|
|
547
1120
|
const workspaceRoot = resolveUploadWorkspaceRoot(pathOrName, selection.resolution);
|
|
548
|
-
const ctx = await (0, commandContext_1.resolveAuthenticatedEnvironmentContext)('project publish', 'Publishing content', {
|
|
1121
|
+
const ctx = await (0, commandContext_1.resolveAuthenticatedEnvironmentContext)('project publish', 'Publishing content', {
|
|
1122
|
+
env: options?.env,
|
|
1123
|
+
workspacePath: workspaceRoot,
|
|
1124
|
+
});
|
|
549
1125
|
if (!ctx) {
|
|
550
1126
|
return;
|
|
551
1127
|
}
|
|
552
1128
|
const { client, env, envConfig } = ctx;
|
|
1129
|
+
printResolvedUploadAccount({
|
|
1130
|
+
env,
|
|
1131
|
+
username: ctx.account?.username,
|
|
1132
|
+
});
|
|
553
1133
|
let userInfo = { username: null, role: null, user: null };
|
|
554
1134
|
try {
|
|
555
1135
|
userInfo = await fetchCurrentUserInfo(client, envConfig.apiBase);
|
|
@@ -600,7 +1180,7 @@ async function upload(pathOrName, options) {
|
|
|
600
1180
|
taxonomyEntries.forEach((entry) => pushLoggedEntry(results, entry));
|
|
601
1181
|
}
|
|
602
1182
|
if (tasks.length > 0) {
|
|
603
|
-
const uploadEntries = await processUploadTasks(client, tasks, owner, userInfo.username, userInfo.role, userInfo.user, ctx.token, warnings, envConfig.apiBase, envConfig.webBase, options);
|
|
1183
|
+
const uploadEntries = await processUploadTasks(client, tasks, owner, userInfo.username, userInfo.role, tagGroupLoad.groups, userInfo.user, ctx.token, warnings, envConfig.apiBase, envConfig.webBase, options);
|
|
604
1184
|
results.push(...uploadEntries);
|
|
605
1185
|
}
|
|
606
1186
|
(0, uploadLog_1.printTaskSummary)(results, warnings, { action: 'upload', environment: env });
|