@playdrop/playdrop-cli 0.3.8-build.3 → 0.3.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/config/client-meta.json +5 -5
- package/dist/apps/build.js +43 -38
- package/dist/catalogue-utils.js +23 -18
- package/dist/catalogue.d.ts +0 -2
- package/dist/catalogue.js +54 -41
- package/dist/clientInfo.js +16 -2
- package/dist/commands/browse.js +10 -1
- package/dist/commands/capture.js +3 -2
- package/dist/commands/create.js +49 -46
- package/dist/commands/createRemixContent.js +29 -44
- package/dist/commands/creations.js +10 -1
- package/dist/commands/devServer.d.ts +1 -1
- package/dist/commands/devShared.js +1 -1
- package/dist/commands/generation.js +91 -74
- package/dist/commands/gettingStarted.js +1 -1
- package/dist/commands/search.js +10 -1
- package/dist/commands/upload-content.d.ts +70 -0
- package/dist/commands/upload-content.js +627 -0
- package/dist/commands/upload-graph.d.ts +23 -0
- package/dist/commands/upload-graph.js +108 -0
- package/dist/commands/upload.js +264 -543
- package/dist/http.d.ts +1 -1
- package/dist/playwright.d.ts +12 -4
- package/dist/proxyFetch.js +3 -2
- package/node_modules/@playdrop/ai-client/dist/index.d.ts.map +1 -1
- package/node_modules/@playdrop/ai-client/dist/index.js +74 -54
- package/node_modules/@playdrop/api-client/dist/client.d.ts +20 -12
- package/node_modules/@playdrop/api-client/dist/client.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/client.js +6 -8
- package/node_modules/@playdrop/api-client/dist/core/errors.js +11 -11
- package/node_modules/@playdrop/api-client/dist/core/request.d.ts +2 -0
- package/node_modules/@playdrop/api-client/dist/core/request.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/core/request.js +10 -3
- package/node_modules/@playdrop/api-client/dist/domains/admin.d.ts +12 -10
- package/node_modules/@playdrop/api-client/dist/domains/admin.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/domains/admin.js +33 -30
- package/node_modules/@playdrop/api-client/dist/domains/apps.d.ts +1 -0
- package/node_modules/@playdrop/api-client/dist/domains/apps.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/domains/apps.js +127 -128
- package/node_modules/@playdrop/api-client/dist/domains/asset-packs.d.ts +9 -5
- package/node_modules/@playdrop/api-client/dist/domains/asset-packs.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/domains/asset-packs.js +151 -88
- package/node_modules/@playdrop/api-client/dist/domains/assets.d.ts +1 -0
- package/node_modules/@playdrop/api-client/dist/domains/assets.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/domains/assets.js +150 -115
- package/node_modules/@playdrop/api-client/dist/domains/auth.d.ts +3 -1
- package/node_modules/@playdrop/api-client/dist/domains/auth.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/domains/auth.js +21 -0
- package/node_modules/@playdrop/api-client/dist/domains/payments.d.ts +1 -0
- package/node_modules/@playdrop/api-client/dist/domains/payments.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/domains/payments.js +10 -0
- package/node_modules/@playdrop/api-client/dist/index.d.ts +34 -31
- package/node_modules/@playdrop/api-client/dist/index.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/index.js +19 -9
- package/node_modules/@playdrop/boxel-core/dist/src/entity-cleaner.js +2 -0
- package/node_modules/@playdrop/boxel-core/dist/src/entity-cleaner.js.map +1 -1
- package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r15/textured-builder.js +1 -1
- package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r15/textured-builder.js.map +1 -1
- package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r15/voxel-builder.js +1 -1
- package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r15/voxel-builder.js.map +1 -1
- package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r6/builder.js +95 -75
- package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r6/builder.js.map +1 -1
- package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r6/scanner.d.ts +2 -3
- package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r6/scanner.js.map +1 -1
- package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r6/textures/face-map.js +4 -4
- package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r6/textures/face-map.js.map +1 -1
- package/node_modules/@playdrop/boxel-core/dist/src/palette_tools.d.ts +2 -2
- package/node_modules/@playdrop/boxel-core/dist/src/transforms/textured-boxes/slice.d.ts +5 -5
- package/node_modules/@playdrop/boxel-core/dist/src/transforms/voxels/textured-to-voxel.d.ts +3 -3
- package/node_modules/@playdrop/boxel-core/dist/src/types.d.ts +25 -25
- package/node_modules/@playdrop/boxel-core/dist/src/validation.js +2 -1
- package/node_modules/@playdrop/boxel-core/dist/src/validation.js.map +1 -1
- package/node_modules/@playdrop/boxel-three/dist/src/exporters/glb.js +5 -0
- package/node_modules/@playdrop/config/client-meta.json +5 -5
- package/node_modules/@playdrop/config/dist/src/index.js +6 -6
- package/node_modules/@playdrop/config/dist/test/validateClientEnvironment.test.js +2 -2
- package/node_modules/@playdrop/config/dist/tsconfig.tsbuildinfo +1 -1
- package/node_modules/@playdrop/types/dist/api.d.ts +27 -7
- package/node_modules/@playdrop/types/dist/api.d.ts.map +1 -1
- package/node_modules/@playdrop/types/dist/api.js +15 -8
- package/node_modules/@playdrop/types/dist/asset-pack.d.ts +105 -11
- package/node_modules/@playdrop/types/dist/asset-pack.d.ts.map +1 -1
- package/node_modules/@playdrop/types/dist/asset-pack.js +2 -0
- package/node_modules/@playdrop/types/dist/ecs.d.ts.map +1 -1
- package/node_modules/@playdrop/types/dist/ecs.js +10 -6
- package/node_modules/@playdrop/types/dist/entity.d.ts +5 -10
- package/node_modules/@playdrop/types/dist/entity.d.ts.map +1 -1
- package/node_modules/@playdrop/types/dist/entity.js +40 -23
- package/node_modules/@playdrop/types/dist/graph.d.ts.map +1 -1
- package/node_modules/@playdrop/types/dist/graph.js +13 -5
- package/node_modules/@playdrop/types/dist/version.d.ts.map +1 -1
- package/node_modules/@playdrop/types/dist/version.js +7 -3
- package/node_modules/@playdrop/vox-three/dist/src/vox.d.ts +1 -0
- package/node_modules/@playdrop/vox-three/dist/src/vox.js +15 -6
- package/node_modules/@playdrop/vox-three/dist/src/vox.js.map +1 -1
- package/node_modules/@playdrop/vox-three/dist/test/vox.test.js +16 -0
- package/node_modules/@playdrop/vox-three/dist/test/vox.test.js.map +1 -1
- package/package.json +1 -1
package/dist/commands/upload.js
CHANGED
|
@@ -1,16 +1,15 @@
|
|
|
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");
|
|
6
4
|
const types_1 = require("@playdrop/types");
|
|
7
|
-
const http_1 = require("../http");
|
|
8
|
-
const commandContext_1 = require("../commandContext");
|
|
9
|
-
const uploadLog_1 = require("../uploadLog");
|
|
10
5
|
const apps_1 = require("../apps");
|
|
6
|
+
const commandContext_1 = require("../commandContext");
|
|
7
|
+
const http_1 = require("../http");
|
|
11
8
|
const taskSelection_1 = require("../taskSelection");
|
|
12
9
|
const taskUtils_1 = require("../taskUtils");
|
|
13
|
-
const
|
|
10
|
+
const uploadLog_1 = require("../uploadLog");
|
|
11
|
+
const upload_content_1 = require("./upload-content");
|
|
12
|
+
const upload_graph_1 = require("./upload-graph");
|
|
14
13
|
const DEFAULT_PORTAL_BASE = 'https://www.playdrop.ai';
|
|
15
14
|
const APP_TYPE_TO_SLUG = {
|
|
16
15
|
GAME: 'game',
|
|
@@ -18,39 +17,20 @@ const APP_TYPE_TO_SLUG = {
|
|
|
18
17
|
TOOL: 'tool',
|
|
19
18
|
TEMPLATE: 'template',
|
|
20
19
|
};
|
|
21
|
-
|
|
22
|
-
'
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
'.
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
'
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
'.glb': 'GLB',
|
|
36
|
-
};
|
|
37
|
-
const EXTENSION_TO_MIME = {
|
|
38
|
-
'.png': 'image/png',
|
|
39
|
-
'.jpg': 'image/jpeg',
|
|
40
|
-
'.jpeg': 'image/jpeg',
|
|
41
|
-
'.webp': 'image/webp',
|
|
42
|
-
'.gif': 'image/gif',
|
|
43
|
-
'.aseprite': 'application/octet-stream',
|
|
44
|
-
'.mp4': 'video/mp4',
|
|
45
|
-
'.webm': 'video/webm',
|
|
46
|
-
'.mp3': 'audio/mpeg',
|
|
47
|
-
'.wav': 'audio/wav',
|
|
48
|
-
'.ogg': 'audio/ogg',
|
|
49
|
-
'.vox': 'application/octet-stream',
|
|
50
|
-
'.gltf': 'model/gltf+json',
|
|
51
|
-
'.glb': 'model/gltf-binary',
|
|
52
|
-
'.json': 'application/json',
|
|
53
|
-
};
|
|
20
|
+
function buildApiUnavailableMessage(apiBase) {
|
|
21
|
+
if (apiBase && typeof apiBase === 'string') {
|
|
22
|
+
return `Could not reach the Playdrop API at ${apiBase}. Check your internet connection, then ensure the server is running before retrying.`;
|
|
23
|
+
}
|
|
24
|
+
return 'Could not reach the Playdrop API. Check your internet connection, then ensure the server is running before retrying.';
|
|
25
|
+
}
|
|
26
|
+
function isConnectionRefusedError(error) {
|
|
27
|
+
const networkCode = error?.cause?.code;
|
|
28
|
+
if (error instanceof TypeError || networkCode === 'ECONNREFUSED') {
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
const aggregateErrors = error?.cause?.errors;
|
|
32
|
+
return Array.isArray(aggregateErrors) && aggregateErrors.some((entry) => entry?.code === 'ECONNREFUSED');
|
|
33
|
+
}
|
|
54
34
|
async function fetchCurrentUserInfo(client, apiBase) {
|
|
55
35
|
try {
|
|
56
36
|
const data = await client.me();
|
|
@@ -70,16 +50,8 @@ async function fetchCurrentUserInfo(client, apiBase) {
|
|
|
70
50
|
if (error instanceof types_1.ApiError) {
|
|
71
51
|
return { username: null, role: null };
|
|
72
52
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
: 'Could not reach the Playdrop API. Check your internet connection, then ensure the server is running before retrying.';
|
|
76
|
-
const networkCode = error?.cause?.code;
|
|
77
|
-
const aggregateErrors = error?.cause?.errors;
|
|
78
|
-
if (error instanceof TypeError || networkCode === 'ECONNREFUSED') {
|
|
79
|
-
throw new Error(message);
|
|
80
|
-
}
|
|
81
|
-
if (Array.isArray(aggregateErrors) && aggregateErrors.some((entry) => entry?.code === 'ECONNREFUSED')) {
|
|
82
|
-
throw new Error(message);
|
|
53
|
+
if (isConnectionRefusedError(error)) {
|
|
54
|
+
throw new Error(buildApiUnavailableMessage(apiBase));
|
|
83
55
|
}
|
|
84
56
|
throw error;
|
|
85
57
|
}
|
|
@@ -132,548 +104,297 @@ function appendOverviewLink(entry, task, portalBase, creator) {
|
|
|
132
104
|
}
|
|
133
105
|
entry.detail = entry.detail ? `${entry.detail} | view: ${url}` : `view: ${url}`;
|
|
134
106
|
}
|
|
135
|
-
function
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
return new File([data], (0, node_path_1.basename)(filePath), { type: mimeType });
|
|
107
|
+
function buildTaskEntityId(task, creatorUsername) {
|
|
108
|
+
return task.kind === 'embedded-asset'
|
|
109
|
+
? `${creatorUsername}/${task.appName}:${task.name}`
|
|
110
|
+
: `${creatorUsername}/${task.name}`;
|
|
140
111
|
}
|
|
141
|
-
function
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
}
|
|
145
|
-
return (buffer[0] === 0x89
|
|
146
|
-
&& buffer[1] === 0x50
|
|
147
|
-
&& buffer[2] === 0x4e
|
|
148
|
-
&& buffer[3] === 0x47
|
|
149
|
-
&& buffer[4] === 0x0d
|
|
150
|
-
&& buffer[5] === 0x0a
|
|
151
|
-
&& buffer[6] === 0x1a
|
|
152
|
-
&& buffer[7] === 0x0a);
|
|
153
|
-
}
|
|
154
|
-
function isMp4Signature(buffer) {
|
|
155
|
-
if (buffer.length < 12) {
|
|
156
|
-
return false;
|
|
157
|
-
}
|
|
158
|
-
return buffer.toString('ascii', 4, 8) === 'ftyp';
|
|
159
|
-
}
|
|
160
|
-
function createListingFileFromPath(filePath, expectedMime) {
|
|
161
|
-
const data = (0, node_fs_1.readFileSync)(filePath);
|
|
162
|
-
if (expectedMime === 'image/png' && !isPngSignature(data)) {
|
|
163
|
-
throw new Error(`Invalid listing file signature for ${filePath}. Expected image/png.`);
|
|
164
|
-
}
|
|
165
|
-
if (expectedMime === 'video/mp4' && !isMp4Signature(data)) {
|
|
166
|
-
throw new Error(`Invalid listing file signature for ${filePath}. Expected video/mp4.`);
|
|
167
|
-
}
|
|
168
|
-
return new File([data], (0, node_path_1.basename)(filePath), { type: expectedMime });
|
|
169
|
-
}
|
|
170
|
-
function resolveAssetFormat(task) {
|
|
171
|
-
if (typeof task.format === 'string' && task.format.trim().length > 0) {
|
|
172
|
-
return task.format.trim().toUpperCase();
|
|
173
|
-
}
|
|
174
|
-
if (task.category === 'MODEL_3D') {
|
|
175
|
-
const primaryFile = task.filePaths.primary;
|
|
176
|
-
if (typeof primaryFile === 'string') {
|
|
177
|
-
const normalizedPrimary = primaryFile.trim().toLowerCase();
|
|
178
|
-
if (normalizedPrimary.endsWith('.boxel.json')) {
|
|
179
|
-
return 'PLAYDROP_BOXEL_JSON';
|
|
180
|
-
}
|
|
181
|
-
if (normalizedPrimary.endsWith('.vox')) {
|
|
182
|
-
return 'VOX';
|
|
183
|
-
}
|
|
184
|
-
const primaryExtension = (0, node_path_1.extname)(normalizedPrimary);
|
|
185
|
-
if (primaryExtension === '.glb') {
|
|
186
|
-
return 'GLB';
|
|
187
|
-
}
|
|
188
|
-
if (primaryExtension === '.gltf') {
|
|
189
|
-
return 'GLTF';
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
const boxelFile = task.filePaths.boxel;
|
|
193
|
-
if (typeof boxelFile === 'string' && boxelFile.trim().toLowerCase().endsWith('.boxel.json')) {
|
|
194
|
-
return 'PLAYDROP_BOXEL_JSON';
|
|
195
|
-
}
|
|
196
|
-
const meshFile = task.filePaths.mesh;
|
|
197
|
-
if (typeof meshFile === 'string') {
|
|
198
|
-
const meshExtension = (0, node_path_1.extname)(meshFile).toLowerCase();
|
|
199
|
-
if (meshExtension === '.glb') {
|
|
200
|
-
return 'GLB';
|
|
201
|
-
}
|
|
202
|
-
if (meshExtension === '.gltf') {
|
|
203
|
-
return 'GLTF';
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
throw new Error(`Asset "${task.name}" is missing files.primary or uses an unsupported MODEL_3D primary extension.`);
|
|
207
|
-
}
|
|
208
|
-
const filePaths = Object.values(task.filePaths);
|
|
209
|
-
if (filePaths.length === 0) {
|
|
210
|
-
throw new Error(`Asset "${task.name}" has no files.`);
|
|
211
|
-
}
|
|
212
|
-
if (filePaths.length > 1) {
|
|
213
|
-
throw new Error(`Asset "${task.name}" is missing format and has multiple files. Set "format" explicitly in catalogue.json.`);
|
|
214
|
-
}
|
|
215
|
-
const extension = (0, node_path_1.extname)(filePaths[0]).toLowerCase();
|
|
216
|
-
const inferred = EXTENSION_TO_FORMAT[extension];
|
|
217
|
-
if (!inferred) {
|
|
218
|
-
throw new Error(`Asset "${task.name}" uses unsupported file extension "${extension}". Set "format" explicitly.`);
|
|
219
|
-
}
|
|
220
|
-
return inferred;
|
|
112
|
+
function pushLoggedEntry(results, entry) {
|
|
113
|
+
results.push(entry);
|
|
114
|
+
console.log((0, uploadLog_1.formatTaskLogLine)(entry));
|
|
221
115
|
}
|
|
222
|
-
function
|
|
223
|
-
const
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
if (
|
|
227
|
-
|
|
116
|
+
function buildUploadErrorDetail(error) {
|
|
117
|
+
const message = typeof error?.message === 'string' && error.message.trim()
|
|
118
|
+
? error.message.trim()
|
|
119
|
+
: 'upload failed';
|
|
120
|
+
if (/status\s+413/i.test(message)) {
|
|
121
|
+
return 'upload failed: asset bundle too large (HTTP 413). Reduce the size of your app files and try again.';
|
|
228
122
|
}
|
|
229
|
-
if (!
|
|
230
|
-
|
|
123
|
+
if (!message.startsWith('upload failed')) {
|
|
124
|
+
return `upload failed: ${message}`;
|
|
231
125
|
}
|
|
232
|
-
|
|
233
|
-
return subcategory;
|
|
126
|
+
return message;
|
|
234
127
|
}
|
|
235
|
-
|
|
236
|
-
const format = resolveAssetFormat(task);
|
|
237
|
-
const subcategory = validateAssetSubcategoryCompatibility(task, format);
|
|
238
|
-
await (0, model_artifacts_1.prepareModel3DAssetArtifacts)(task);
|
|
239
|
-
const visibility = task.visibility;
|
|
240
|
-
const files = Object.entries(task.filePaths).map(([role, filePath]) => ({
|
|
241
|
-
file: createFileFromPath(filePath),
|
|
242
|
-
fieldName: role,
|
|
243
|
-
filename: (0, node_path_1.basename)(filePath),
|
|
244
|
-
}));
|
|
245
|
-
const targetCreatorUsername = typeof creatorUsername === 'string' ? creatorUsername.trim() : '';
|
|
246
|
-
if (!targetCreatorUsername) {
|
|
247
|
-
throw new Error(`Asset "${task.name}" upload is missing creator username.`);
|
|
248
|
-
}
|
|
249
|
-
const response = await client.createAssetVersion(targetCreatorUsername, task.name, {
|
|
250
|
-
displayName: task.kind === 'asset' ? task.displayName : undefined,
|
|
251
|
-
category: task.category,
|
|
252
|
-
subcategory,
|
|
253
|
-
format,
|
|
254
|
-
remixRef: task.kind === 'asset' ? task.remix : undefined,
|
|
255
|
-
visibility: visibility,
|
|
256
|
-
sourceKind: sourceAppVersionId ? 'APP_EMBEDDED' : 'UPLOAD',
|
|
257
|
-
sourceAppVersionId,
|
|
258
|
-
shopListed: task.shopListed,
|
|
259
|
-
shopPriceCredits: task.shopPriceCredits,
|
|
260
|
-
files,
|
|
261
|
-
});
|
|
262
|
-
const uploadedCreatorUsername = response.asset.creatorUsername;
|
|
263
|
-
const name = response.asset.name;
|
|
264
|
-
const revision = response.version.revision;
|
|
265
|
-
const versionNodeId = typeof response.versionNodeId === 'string' ? response.versionNodeId.trim() : '';
|
|
266
|
-
if (typeof uploadedCreatorUsername !== 'string' || uploadedCreatorUsername.trim().length === 0) {
|
|
267
|
-
throw new Error(`Asset "${task.name}" upload did not return creatorUsername.`);
|
|
268
|
-
}
|
|
269
|
-
if (!versionNodeId) {
|
|
270
|
-
throw new Error(`Asset "${task.name}" upload did not return versionNodeId.`);
|
|
271
|
-
}
|
|
128
|
+
function buildTaskErrorEntry(task, entityId, error) {
|
|
272
129
|
return {
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
130
|
+
action: 'upload',
|
|
131
|
+
status: 'error',
|
|
132
|
+
entityType: task.kind,
|
|
133
|
+
entityId,
|
|
134
|
+
catalogue: task.cataloguePath,
|
|
135
|
+
detail: buildUploadErrorDetail(error),
|
|
278
136
|
};
|
|
279
137
|
}
|
|
280
|
-
function
|
|
281
|
-
const
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
const plainMatch = /^([^/]+)\/([^@]+)@r(\d+)$/i.exec(trimmed);
|
|
291
|
-
if (plainMatch) {
|
|
292
|
-
return {
|
|
293
|
-
creatorUsername: plainMatch[1],
|
|
294
|
-
name: plainMatch[2],
|
|
295
|
-
revision: Number.parseInt(plainMatch[3], 10),
|
|
296
|
-
};
|
|
297
|
-
}
|
|
298
|
-
return null;
|
|
138
|
+
function buildPackPlanningFailureEntry(task, defaultCreator, currentUserRole, message) {
|
|
139
|
+
const creator = (0, upload_content_1.getTaskCreatorResult)(task, defaultCreator, currentUserRole);
|
|
140
|
+
return {
|
|
141
|
+
action: 'upload',
|
|
142
|
+
status: 'error',
|
|
143
|
+
entityType: task.kind,
|
|
144
|
+
entityId: buildTaskEntityId(task, creator.taskCreator),
|
|
145
|
+
catalogue: task.cataloguePath,
|
|
146
|
+
detail: `upload failed: ${message}`,
|
|
147
|
+
};
|
|
299
148
|
}
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
return `asset:${parsed.creatorUsername}/${parsed.name}@r${parsed.revision}`;
|
|
308
|
-
}
|
|
309
|
-
if (trimmed.includes('@')) {
|
|
310
|
-
throw new Error(`Invalid asset ref "${trimmed}". Use "asset:creator/name@rN" format.`);
|
|
311
|
-
}
|
|
312
|
-
const slashIndex = trimmed.indexOf('/');
|
|
313
|
-
const creatorUsername = slashIndex > 0 ? trimmed.slice(0, slashIndex) : fallbackCreator;
|
|
314
|
-
const name = slashIndex > 0 ? trimmed.slice(slashIndex + 1) : trimmed;
|
|
315
|
-
if (!creatorUsername || !name) {
|
|
316
|
-
throw new Error(`Invalid asset reference "${trimmed}".`);
|
|
317
|
-
}
|
|
318
|
-
const key = `${creatorUsername}/${name}`;
|
|
319
|
-
const uploaded = uploadedAssets.get(key);
|
|
320
|
-
if (uploaded) {
|
|
321
|
-
return uploaded.ref;
|
|
322
|
-
}
|
|
323
|
-
const detail = await client.fetchAssetBySlug(creatorUsername, name);
|
|
324
|
-
const revision = detail?.asset?.currentVersion?.revision;
|
|
325
|
-
if (typeof revision !== 'number' || !Number.isInteger(revision) || revision <= 0) {
|
|
326
|
-
throw new Error(`Asset "${creatorUsername}/${name}" has no current version to reference from asset pack.`);
|
|
327
|
-
}
|
|
328
|
-
return `asset:${creatorUsername}/${name}@r${revision}`;
|
|
149
|
+
function appendTaskRelations(graphState, fromNodeId, relations, contextLabel) {
|
|
150
|
+
(relations ?? []).forEach((relation, index) => {
|
|
151
|
+
if (relation.type === 'REMIXES') {
|
|
152
|
+
throw new Error(`${contextLabel} relations[${index}] must use remix instead of REMIXES.`);
|
|
153
|
+
}
|
|
154
|
+
(0, upload_graph_1.appendPendingRelation)(graphState, fromNodeId, relation.type, relation.to, `${contextLabel} relations[${index}]`);
|
|
155
|
+
});
|
|
329
156
|
}
|
|
330
|
-
async function
|
|
331
|
-
const
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
}
|
|
335
|
-
const mutationCreatorUsername = typeof targetCreatorUsername === 'string' && targetCreatorUsername.trim().length > 0
|
|
336
|
-
? targetCreatorUsername.trim()
|
|
337
|
-
: creatorUsername.trim();
|
|
338
|
-
if (!mutationCreatorUsername) {
|
|
339
|
-
throw new Error(`Asset pack "${task.name}@${task.version}" upload is missing creator username.`);
|
|
340
|
-
}
|
|
341
|
-
const response = await client.uploadAssetPackVersion(mutationCreatorUsername, task.name, {
|
|
342
|
-
request: {
|
|
343
|
-
version: task.version,
|
|
344
|
-
remixRef: task.remix,
|
|
345
|
-
visibility: task.visibility,
|
|
346
|
-
hostingMode: task.hostingMode,
|
|
347
|
-
externalUrl: task.externalUrl,
|
|
348
|
-
downloadUrl: task.downloadUrl,
|
|
349
|
-
releaseNotes: task.releaseNotes,
|
|
350
|
-
assets: assetRefs.map((assetRef) => ({ assetRef })),
|
|
351
|
-
},
|
|
352
|
-
icon: task.listing?.iconPath ? createListingFileFromPath(task.listing.iconPath, 'image/png') : undefined,
|
|
353
|
-
heroPortrait: task.listing?.heroPortraitPath ? createListingFileFromPath(task.listing.heroPortraitPath, 'image/png') : undefined,
|
|
354
|
-
heroLandscape: task.listing?.heroLandscapePath ? createListingFileFromPath(task.listing.heroLandscapePath, 'image/png') : undefined,
|
|
355
|
-
screenshotsPortrait: task.listing?.screenshotPortraitPaths?.map((filePath) => createListingFileFromPath(filePath, 'image/png')),
|
|
356
|
-
screenshotsLandscape: task.listing?.screenshotLandscapePaths?.map((filePath) => createListingFileFromPath(filePath, 'image/png')),
|
|
357
|
-
videosPortrait: task.listing?.videoPortraitPaths?.map((filePath) => createListingFileFromPath(filePath, 'video/mp4')),
|
|
358
|
-
videosLandscape: task.listing?.videoLandscapePaths?.map((filePath) => createListingFileFromPath(filePath, 'video/mp4')),
|
|
157
|
+
async function uploadAppTask(state, task, taskCreator, options) {
|
|
158
|
+
const { upload } = await (0, apps_1.runAppPipeline)(state.client, task, {
|
|
159
|
+
skipEcs: options?.skipEcs,
|
|
160
|
+
creatorUsername: taskCreator,
|
|
359
161
|
});
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
162
|
+
if (!upload.versionCreated || !upload.version) {
|
|
163
|
+
throw new Error(`App "${task.name}" upload did not return a created version.`);
|
|
164
|
+
}
|
|
165
|
+
if (typeof upload.versionId !== 'number') {
|
|
166
|
+
throw new Error(`App "${task.name}" upload did not return version ID.`);
|
|
167
|
+
}
|
|
168
|
+
if (typeof upload.versionNodeId !== 'string' || upload.versionNodeId.trim().length === 0) {
|
|
169
|
+
throw new Error(`App "${task.name}" upload did not return versionNodeId.`);
|
|
170
|
+
}
|
|
171
|
+
const appRef = `app:${taskCreator}/${task.name}@${upload.version}`;
|
|
172
|
+
const uploadedApp = {
|
|
173
|
+
creatorUsername: taskCreator,
|
|
174
|
+
name: task.name,
|
|
175
|
+
version: upload.version,
|
|
176
|
+
versionId: upload.versionId,
|
|
177
|
+
versionNodeId: upload.versionNodeId,
|
|
178
|
+
ref: appRef,
|
|
368
179
|
};
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
180
|
+
state.uploadedAppsByName.set(task.name, uploadedApp);
|
|
181
|
+
(0, upload_graph_1.registerCanonicalNode)(state.graphState, appRef, uploadedApp.versionNodeId);
|
|
182
|
+
(0, upload_graph_1.registerLocalRef)(state.graphState.localAppNodeByName, state.graphState.ambiguousAppNames, task.name, uploadedApp.versionNodeId);
|
|
183
|
+
task.uses.assets.forEach((toRef, index) => {
|
|
184
|
+
(0, upload_graph_1.appendPendingRelation)(state.graphState, uploadedApp.versionNodeId, 'USES', toRef, `[${task.cataloguePath}] app "${task.name}" uses.assets[${index}]`);
|
|
185
|
+
});
|
|
186
|
+
task.uses.packs.forEach((toRef, index) => {
|
|
187
|
+
(0, upload_graph_1.appendPendingRelation)(state.graphState, uploadedApp.versionNodeId, 'USES', toRef, `[${task.cataloguePath}] app "${task.name}" uses.packs[${index}]`);
|
|
188
|
+
});
|
|
189
|
+
appendTaskRelations(state.graphState, uploadedApp.versionNodeId, task.graph.relations, `[${task.cataloguePath}] app "${task.name}"`);
|
|
190
|
+
const entry = {
|
|
191
|
+
action: 'upload',
|
|
192
|
+
status: 'success',
|
|
193
|
+
entityType: 'app',
|
|
194
|
+
entityId: buildTaskEntityId(task, taskCreator),
|
|
195
|
+
catalogue: task.cataloguePath,
|
|
196
|
+
detail: `version ${upload.version} created`,
|
|
385
197
|
};
|
|
198
|
+
appendOverviewLink(entry, task, state.portalBase, taskCreator);
|
|
199
|
+
return entry;
|
|
386
200
|
}
|
|
387
|
-
function
|
|
388
|
-
const
|
|
389
|
-
if (
|
|
390
|
-
return
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
ambiguous.add(normalized);
|
|
201
|
+
async function uploadStandaloneAssetTask(state, task, taskCreator) {
|
|
202
|
+
const ownedPack = state.packPlanning.ownedPackByAssetKey.get((0, upload_content_1.buildAssetKey)(taskCreator, task.name));
|
|
203
|
+
if (ownedPack) {
|
|
204
|
+
return {
|
|
205
|
+
action: 'upload',
|
|
206
|
+
status: 'success',
|
|
207
|
+
entityType: 'asset',
|
|
208
|
+
entityId: buildTaskEntityId(task, taskCreator),
|
|
209
|
+
catalogue: task.cataloguePath,
|
|
210
|
+
detail: `staged with asset pack ${ownedPack.packTask.name}@${ownedPack.packTask.version}`,
|
|
211
|
+
};
|
|
399
212
|
}
|
|
213
|
+
const uploaded = await (0, upload_content_1.uploadAssetTask)(state.client, task, undefined, taskCreator);
|
|
214
|
+
state.uploadedAssetsByKey.set(`${uploaded.creatorUsername}/${uploaded.name}`, uploaded);
|
|
215
|
+
(0, upload_graph_1.registerCanonicalNode)(state.graphState, uploaded.ref, uploaded.versionNodeId);
|
|
216
|
+
(0, upload_graph_1.registerLocalRef)(state.graphState.localAssetNodeByName, state.graphState.ambiguousAssetNames, task.name, uploaded.versionNodeId);
|
|
217
|
+
appendTaskRelations(state.graphState, uploaded.versionNodeId, task.relations, `[${task.cataloguePath}] asset "${task.name}"`);
|
|
218
|
+
const entry = {
|
|
219
|
+
action: 'upload',
|
|
220
|
+
status: 'success',
|
|
221
|
+
entityType: 'asset',
|
|
222
|
+
entityId: buildTaskEntityId(task, taskCreator),
|
|
223
|
+
catalogue: task.cataloguePath,
|
|
224
|
+
detail: uploaded.ref,
|
|
225
|
+
};
|
|
226
|
+
appendOverviewLink(entry, task, state.portalBase, uploaded.creatorUsername);
|
|
227
|
+
return entry;
|
|
400
228
|
}
|
|
401
|
-
function
|
|
402
|
-
const
|
|
403
|
-
if (!
|
|
404
|
-
|
|
405
|
-
}
|
|
406
|
-
state.
|
|
229
|
+
async function uploadEmbeddedAssetTask(state, task, taskCreator) {
|
|
230
|
+
const sourceApp = state.uploadedAppsByName.get(task.appName);
|
|
231
|
+
if (!sourceApp) {
|
|
232
|
+
throw new Error(`Embedded asset "${task.name}" references app "${task.appName}" that was not uploaded in this run.`);
|
|
233
|
+
}
|
|
234
|
+
const uploaded = await (0, upload_content_1.uploadAssetTask)(state.client, task, sourceApp.versionId, sourceApp.creatorUsername);
|
|
235
|
+
state.uploadedAssetsByKey.set(`${uploaded.creatorUsername}/${uploaded.name}`, uploaded);
|
|
236
|
+
(0, upload_graph_1.registerCanonicalNode)(state.graphState, uploaded.ref, uploaded.versionNodeId);
|
|
237
|
+
(0, upload_graph_1.registerLocalRef)(state.graphState.localAssetNodeByName, state.graphState.ambiguousAssetNames, task.name, uploaded.versionNodeId);
|
|
238
|
+
const entry = {
|
|
239
|
+
action: 'upload',
|
|
240
|
+
status: 'success',
|
|
241
|
+
entityType: 'embedded-asset',
|
|
242
|
+
entityId: buildTaskEntityId(task, taskCreator),
|
|
243
|
+
catalogue: task.cataloguePath,
|
|
244
|
+
detail: `${uploaded.ref} (source app version ${sourceApp.versionId})`,
|
|
245
|
+
};
|
|
246
|
+
appendOverviewLink(entry, task, state.portalBase, uploaded.creatorUsername);
|
|
247
|
+
return entry;
|
|
407
248
|
}
|
|
408
|
-
function
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
249
|
+
function collectPackLocalAssetTasks(state, task, taskCreator) {
|
|
250
|
+
const packKey = (0, upload_content_1.buildPackKey)(taskCreator, task.name, task.version);
|
|
251
|
+
const packPlan = state.packPlanning.packPlansByKey.get(packKey);
|
|
252
|
+
if (!packPlan) {
|
|
253
|
+
throw new Error(`Asset pack "${task.name}@${task.version}" is missing an upload plan.`);
|
|
254
|
+
}
|
|
255
|
+
const localAssetTasks = packPlan.ownedAssetKeys.map((assetKey) => {
|
|
256
|
+
const ownedTask = state.sortedTasks.find((candidate) => candidate.kind === 'asset' && (0, upload_content_1.buildAssetKey)(taskCreator, candidate.name) === assetKey);
|
|
257
|
+
if (!ownedTask || ownedTask.kind !== 'asset') {
|
|
258
|
+
throw new Error(`Asset pack "${task.name}@${task.version}" is missing local asset task "${assetKey}".`);
|
|
259
|
+
}
|
|
260
|
+
return ownedTask;
|
|
414
261
|
});
|
|
262
|
+
return { packKey, packPlan, localAssetTasks };
|
|
415
263
|
}
|
|
416
|
-
function
|
|
417
|
-
const
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
}
|
|
423
|
-
function resolveLocalRefNodeId(map, ambiguous, key, context, kindLabel) {
|
|
424
|
-
if (ambiguous.has(key)) {
|
|
425
|
-
throw new Error(`${context}: local ${kindLabel} ref "${key}" is ambiguous. Use a canonical ref.`);
|
|
426
|
-
}
|
|
427
|
-
const nodeId = map.get(key);
|
|
428
|
-
if (!nodeId) {
|
|
429
|
-
throw new Error(`${context}: local ${kindLabel} ref "${key}" was not created in this upload run.`);
|
|
430
|
-
}
|
|
431
|
-
return nodeId;
|
|
432
|
-
}
|
|
433
|
-
function resolveRelationTargetNodeId(state, rawRef, context) {
|
|
434
|
-
const ref = rawRef.trim();
|
|
435
|
-
if (!ref) {
|
|
436
|
-
throw new Error(`${context}: relation target ref is empty.`);
|
|
437
|
-
}
|
|
438
|
-
if (ref.startsWith('@app/')) {
|
|
439
|
-
const name = ref.slice('@app/'.length).trim();
|
|
440
|
-
if (!name) {
|
|
441
|
-
throw new Error(`${context}: invalid local app ref "${ref}". Use "@app/<name>".`);
|
|
264
|
+
function registerUploadedLocalPackAssets(state, task, taskCreator, packPlan, localAssetTasks, uploadedPack) {
|
|
265
|
+
const localTaskByUploadKey = new Map();
|
|
266
|
+
for (const localAssetTask of localAssetTasks) {
|
|
267
|
+
const uploadKey = packPlan.uploadKeyByAssetKey.get((0, upload_content_1.buildAssetKey)(taskCreator, localAssetTask.name));
|
|
268
|
+
if (!uploadKey) {
|
|
269
|
+
throw new Error(`Asset pack "${task.name}@${task.version}" is missing upload key for local asset "${localAssetTask.name}".`);
|
|
442
270
|
}
|
|
443
|
-
|
|
271
|
+
localTaskByUploadKey.set(uploadKey, localAssetTask);
|
|
444
272
|
}
|
|
445
|
-
|
|
446
|
-
const
|
|
447
|
-
if (!
|
|
448
|
-
|
|
273
|
+
for (const uploadedLocalAsset of uploadedPack.uploadedLocalAssets) {
|
|
274
|
+
const localTask = localTaskByUploadKey.get(uploadedLocalAsset.uploadKey);
|
|
275
|
+
if (!localTask) {
|
|
276
|
+
continue;
|
|
449
277
|
}
|
|
450
|
-
|
|
278
|
+
const uploadedLocalRef = {
|
|
279
|
+
creatorUsername: uploadedLocalAsset.creatorUsername,
|
|
280
|
+
name: uploadedLocalAsset.name,
|
|
281
|
+
revision: uploadedLocalAsset.revision,
|
|
282
|
+
ref: `asset:${uploadedLocalAsset.creatorUsername}/${uploadedLocalAsset.name}@r${uploadedLocalAsset.revision}`,
|
|
283
|
+
versionNodeId: uploadedLocalAsset.versionNodeId,
|
|
284
|
+
};
|
|
285
|
+
state.uploadedAssetsByKey.set((0, upload_content_1.buildAssetKey)(uploadedLocalAsset.creatorUsername, uploadedLocalAsset.name), uploadedLocalRef);
|
|
286
|
+
(0, upload_graph_1.registerCanonicalNode)(state.graphState, uploadedLocalRef.ref, uploadedLocalRef.versionNodeId);
|
|
287
|
+
(0, upload_graph_1.registerLocalRef)(state.graphState.localAssetNodeByName, state.graphState.ambiguousAssetNames, localTask.name, uploadedLocalRef.versionNodeId);
|
|
288
|
+
appendTaskRelations(state.graphState, uploadedLocalRef.versionNodeId, localTask.relations, `[${localTask.cataloguePath}] asset "${localTask.name}"`);
|
|
451
289
|
}
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
290
|
+
}
|
|
291
|
+
async function uploadPackTask(state, task, taskCreator) {
|
|
292
|
+
const { packPlan, localAssetTasks } = collectPackLocalAssetTasks(state, task, taskCreator);
|
|
293
|
+
const mutationTargetCreator = typeof task.username === 'string' && task.username.trim().length > 0
|
|
294
|
+
? taskCreator
|
|
295
|
+
: undefined;
|
|
296
|
+
const uploadedPack = await (0, upload_content_1.uploadAssetPackTask)(state.client, task, taskCreator, state.uploadedAssetsByKey, localAssetTasks, packPlan.uploadKeyByAssetKey, mutationTargetCreator);
|
|
297
|
+
(0, upload_graph_1.registerCanonicalNode)(state.graphState, uploadedPack.ref, uploadedPack.versionNodeId);
|
|
298
|
+
(0, upload_graph_1.registerLocalRef)(state.graphState.localPackNodeByNameVersion, state.graphState.ambiguousPackNameVersions, `${task.name}@${task.version}`, uploadedPack.versionNodeId);
|
|
299
|
+
registerUploadedLocalPackAssets(state, task, taskCreator, packPlan, localAssetTasks, uploadedPack);
|
|
300
|
+
appendTaskRelations(state.graphState, uploadedPack.versionNodeId, task.relations, `[${task.cataloguePath}] assetPack "${task.name}@${task.version}"`);
|
|
301
|
+
const entry = {
|
|
302
|
+
action: 'upload',
|
|
303
|
+
status: 'success',
|
|
304
|
+
entityType: 'asset-pack',
|
|
305
|
+
entityId: buildTaskEntityId(task, taskCreator),
|
|
306
|
+
catalogue: task.cataloguePath,
|
|
307
|
+
detail: `version ${task.version} created (${uploadedPack.assetRefs.length} assets)`,
|
|
308
|
+
};
|
|
309
|
+
appendOverviewLink(entry, task, state.portalBase, taskCreator);
|
|
310
|
+
return entry;
|
|
311
|
+
}
|
|
312
|
+
async function processSingleUploadTask(state, task, taskCreator, options) {
|
|
313
|
+
if (task.kind === 'app') {
|
|
314
|
+
return await uploadAppTask(state, task, taskCreator, options);
|
|
459
315
|
}
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
throw new Error(`${context}: invalid relation target ref "${ref}". Use canonical refs (app:/asset:/pack:) or local refs (@app/@asset/@pack).`);
|
|
316
|
+
if (task.kind === 'asset') {
|
|
317
|
+
return await uploadStandaloneAssetTask(state, task, taskCreator);
|
|
463
318
|
}
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
throw new Error(`${context}: canonical ref "${ref}" was not resolved in this upload run.`);
|
|
319
|
+
if (task.kind === 'embedded-asset') {
|
|
320
|
+
return await uploadEmbeddedAssetTask(state, task, taskCreator);
|
|
467
321
|
}
|
|
468
|
-
|
|
322
|
+
if (task.kind === 'asset-pack') {
|
|
323
|
+
return await uploadPackTask(state, task, taskCreator);
|
|
324
|
+
}
|
|
325
|
+
throw new Error(`Unsupported task kind "${task.kind}".`);
|
|
469
326
|
}
|
|
470
|
-
async function
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
327
|
+
async function flushGraphState(client, graphState, results) {
|
|
328
|
+
try {
|
|
329
|
+
await (0, upload_graph_1.applyGraphOperations)(client, graphState);
|
|
330
|
+
}
|
|
331
|
+
catch (error) {
|
|
332
|
+
const message = typeof error?.message === 'string' && error.message.trim().length > 0
|
|
333
|
+
? error.message.trim()
|
|
334
|
+
: 'graph batch upload failed';
|
|
335
|
+
const entry = {
|
|
336
|
+
action: 'upload',
|
|
337
|
+
status: 'error',
|
|
338
|
+
entityType: 'app',
|
|
339
|
+
entityId: 'graph',
|
|
340
|
+
catalogue: 'graph:batch',
|
|
341
|
+
detail: `upload failed: ${message}`,
|
|
342
|
+
};
|
|
343
|
+
pushLoggedEntry(results, entry);
|
|
344
|
+
process.exitCode = process.exitCode || 1;
|
|
478
345
|
}
|
|
479
346
|
}
|
|
480
347
|
async function processUploadTasks(client, tasks, owner, ownerUsername, currentUserRole, webBase, options) {
|
|
481
348
|
const portalBase = webBase ? normalizePortalBase(webBase) : null;
|
|
482
349
|
const defaultCreator = normalizeCreatorUsername(ownerUsername) ?? owner;
|
|
483
|
-
const uploadedAppsByName = new Map();
|
|
484
|
-
const uploadedAssetsByKey = new Map();
|
|
485
|
-
const graphState = buildEmptyGraphState();
|
|
486
350
|
const results = [];
|
|
487
351
|
let aborted = false;
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
const
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
352
|
+
const sortedTasks = (0, taskUtils_1.sortTasks)(tasks);
|
|
353
|
+
const packPlanning = (0, upload_content_1.buildAssetPackUploadPlans)(sortedTasks, defaultCreator, currentUserRole);
|
|
354
|
+
if (!packPlanning.ok) {
|
|
355
|
+
const entry = buildPackPlanningFailureEntry(packPlanning.task, defaultCreator, currentUserRole, packPlanning.message);
|
|
356
|
+
pushLoggedEntry(results, entry);
|
|
357
|
+
process.exitCode = process.exitCode || 1;
|
|
358
|
+
return results;
|
|
359
|
+
}
|
|
360
|
+
const state = {
|
|
361
|
+
client,
|
|
362
|
+
sortedTasks,
|
|
363
|
+
defaultCreator,
|
|
364
|
+
currentUserRole,
|
|
365
|
+
portalBase,
|
|
366
|
+
uploadedAppsByName: new Map(),
|
|
367
|
+
uploadedAssetsByKey: new Map(),
|
|
368
|
+
graphState: (0, upload_graph_1.buildEmptyGraphState)(),
|
|
369
|
+
packPlanning,
|
|
370
|
+
};
|
|
371
|
+
for (const task of sortedTasks) {
|
|
372
|
+
const creatorResult = (0, upload_content_1.getTaskCreatorResult)(task, defaultCreator, currentUserRole);
|
|
373
|
+
if (creatorResult.requestedTaskOwner && !creatorResult.creatorTargetError) {
|
|
374
|
+
console.log(`[Admin] Publishing ${task.name} as user: ${creatorResult.taskCreator}`);
|
|
501
375
|
}
|
|
502
|
-
const entityId = task
|
|
503
|
-
const taskCreator =
|
|
376
|
+
const entityId = buildTaskEntityId(task, creatorResult.taskCreator);
|
|
377
|
+
const taskCreator = creatorResult.taskCreator;
|
|
504
378
|
try {
|
|
505
|
-
if (creatorTargetError) {
|
|
506
|
-
throw new Error(creatorTargetError);
|
|
507
|
-
}
|
|
508
|
-
if (task.kind === 'app') {
|
|
509
|
-
const { upload } = await (0, apps_1.runAppPipeline)(client, task, {
|
|
510
|
-
skipEcs: options?.skipEcs,
|
|
511
|
-
creatorUsername: taskCreator,
|
|
512
|
-
});
|
|
513
|
-
if (!upload.versionCreated || !upload.version) {
|
|
514
|
-
throw new Error(`App "${task.name}" upload did not return a created version.`);
|
|
515
|
-
}
|
|
516
|
-
if (typeof upload.versionId !== 'number') {
|
|
517
|
-
throw new Error(`App "${task.name}" upload did not return version ID.`);
|
|
518
|
-
}
|
|
519
|
-
if (typeof upload.versionNodeId !== 'string' || upload.versionNodeId.trim().length === 0) {
|
|
520
|
-
throw new Error(`App "${task.name}" upload did not return versionNodeId.`);
|
|
521
|
-
}
|
|
522
|
-
const appRef = `app:${taskCreator}/${task.name}@${upload.version}`;
|
|
523
|
-
const uploadedApp = {
|
|
524
|
-
creatorUsername: taskCreator,
|
|
525
|
-
name: task.name,
|
|
526
|
-
version: upload.version,
|
|
527
|
-
versionId: upload.versionId,
|
|
528
|
-
versionNodeId: upload.versionNodeId,
|
|
529
|
-
ref: appRef,
|
|
530
|
-
};
|
|
531
|
-
uploadedAppsByName.set(task.name, uploadedApp);
|
|
532
|
-
registerCanonicalNode(graphState, appRef, uploadedApp.versionNodeId);
|
|
533
|
-
registerLocalRef(graphState.localAppNodeByName, graphState.ambiguousAppNames, task.name, uploadedApp.versionNodeId);
|
|
534
|
-
task.uses.assets.forEach((toRef, index) => {
|
|
535
|
-
appendPendingRelation(graphState, uploadedApp.versionNodeId, 'USES', toRef, `[${task.cataloguePath}] app "${task.name}" uses.assets[${index}]`);
|
|
536
|
-
});
|
|
537
|
-
task.uses.packs.forEach((toRef, index) => {
|
|
538
|
-
appendPendingRelation(graphState, uploadedApp.versionNodeId, 'USES', toRef, `[${task.cataloguePath}] app "${task.name}" uses.packs[${index}]`);
|
|
539
|
-
});
|
|
540
|
-
task.graph.relations.forEach((relation, index) => {
|
|
541
|
-
if (relation.type === 'REMIXES') {
|
|
542
|
-
throw new Error(`[${task.cataloguePath}] app "${task.name}" relations[${index}] must use remix instead of REMIXES.`);
|
|
543
|
-
}
|
|
544
|
-
appendPendingRelation(graphState, uploadedApp.versionNodeId, relation.type, relation.to, `[${task.cataloguePath}] app "${task.name}" relations[${index}]`);
|
|
545
|
-
});
|
|
546
|
-
const entry = {
|
|
547
|
-
action: 'upload',
|
|
548
|
-
status: 'success',
|
|
549
|
-
entityType: 'app',
|
|
550
|
-
entityId,
|
|
551
|
-
catalogue: task.cataloguePath,
|
|
552
|
-
detail: `version ${upload.version} created`,
|
|
553
|
-
};
|
|
554
|
-
appendOverviewLink(entry, task, portalBase, taskCreator);
|
|
555
|
-
results.push(entry);
|
|
556
|
-
console.log((0, uploadLog_1.formatTaskLogLine)(entry));
|
|
557
|
-
}
|
|
558
|
-
else if (task.kind === 'asset') {
|
|
559
|
-
const uploaded = await uploadAssetTask(client, task, undefined, taskCreator);
|
|
560
|
-
uploadedAssetsByKey.set(`${uploaded.creatorUsername}/${uploaded.name}`, uploaded);
|
|
561
|
-
registerCanonicalNode(graphState, uploaded.ref, uploaded.versionNodeId);
|
|
562
|
-
registerLocalRef(graphState.localAssetNodeByName, graphState.ambiguousAssetNames, task.name, uploaded.versionNodeId);
|
|
563
|
-
(task.relations ?? []).forEach((relation, index) => {
|
|
564
|
-
if (relation.type === 'REMIXES') {
|
|
565
|
-
throw new Error(`[${task.cataloguePath}] asset "${task.name}" relations[${index}] must use remix instead of REMIXES.`);
|
|
566
|
-
}
|
|
567
|
-
appendPendingRelation(graphState, uploaded.versionNodeId, relation.type, relation.to, `[${task.cataloguePath}] asset "${task.name}" relations[${index}]`);
|
|
568
|
-
});
|
|
569
|
-
const entry = {
|
|
570
|
-
action: 'upload',
|
|
571
|
-
status: 'success',
|
|
572
|
-
entityType: 'asset',
|
|
573
|
-
entityId,
|
|
574
|
-
catalogue: task.cataloguePath,
|
|
575
|
-
detail: uploaded.ref,
|
|
576
|
-
};
|
|
577
|
-
appendOverviewLink(entry, task, portalBase, uploaded.creatorUsername);
|
|
578
|
-
results.push(entry);
|
|
579
|
-
console.log((0, uploadLog_1.formatTaskLogLine)(entry));
|
|
580
|
-
}
|
|
581
|
-
else if (task.kind === 'embedded-asset') {
|
|
582
|
-
const sourceApp = uploadedAppsByName.get(task.appName);
|
|
583
|
-
if (!sourceApp) {
|
|
584
|
-
throw new Error(`Embedded asset "${task.name}" references app "${task.appName}" that was not uploaded in this run.`);
|
|
585
|
-
}
|
|
586
|
-
const uploaded = await uploadAssetTask(client, task, sourceApp.versionId, sourceApp.creatorUsername);
|
|
587
|
-
uploadedAssetsByKey.set(`${uploaded.creatorUsername}/${uploaded.name}`, uploaded);
|
|
588
|
-
registerCanonicalNode(graphState, uploaded.ref, uploaded.versionNodeId);
|
|
589
|
-
registerLocalRef(graphState.localAssetNodeByName, graphState.ambiguousAssetNames, task.name, uploaded.versionNodeId);
|
|
590
|
-
const entry = {
|
|
591
|
-
action: 'upload',
|
|
592
|
-
status: 'success',
|
|
593
|
-
entityType: 'embedded-asset',
|
|
594
|
-
entityId,
|
|
595
|
-
catalogue: task.cataloguePath,
|
|
596
|
-
detail: `${uploaded.ref} (source app version ${sourceApp.versionId})`,
|
|
597
|
-
};
|
|
598
|
-
appendOverviewLink(entry, task, portalBase, uploaded.creatorUsername);
|
|
599
|
-
results.push(entry);
|
|
600
|
-
console.log((0, uploadLog_1.formatTaskLogLine)(entry));
|
|
601
|
-
}
|
|
602
|
-
else if (task.kind === 'asset-pack') {
|
|
603
|
-
const mutationTargetCreator = typeof task.username === 'string' && task.username.trim().length > 0
|
|
604
|
-
? taskCreator
|
|
605
|
-
: undefined;
|
|
606
|
-
const uploadedPack = await uploadAssetPackTask(client, task, taskCreator, uploadedAssetsByKey, mutationTargetCreator);
|
|
607
|
-
registerCanonicalNode(graphState, uploadedPack.ref, uploadedPack.versionNodeId);
|
|
608
|
-
registerLocalRef(graphState.localPackNodeByNameVersion, graphState.ambiguousPackNameVersions, `${task.name}@${task.version}`, uploadedPack.versionNodeId);
|
|
609
|
-
(task.relations ?? []).forEach((relation, index) => {
|
|
610
|
-
if (relation.type === 'REMIXES') {
|
|
611
|
-
throw new Error(`[${task.cataloguePath}] assetPack "${task.name}@${task.version}" relations[${index}] must use remix instead of REMIXES.`);
|
|
612
|
-
}
|
|
613
|
-
appendPendingRelation(graphState, uploadedPack.versionNodeId, relation.type, relation.to, `[${task.cataloguePath}] assetPack "${task.name}@${task.version}" relations[${index}]`);
|
|
614
|
-
});
|
|
615
|
-
const entry = {
|
|
616
|
-
action: 'upload',
|
|
617
|
-
status: 'success',
|
|
618
|
-
entityType: 'asset-pack',
|
|
619
|
-
entityId,
|
|
620
|
-
catalogue: task.cataloguePath,
|
|
621
|
-
detail: `version ${task.version} created (${uploadedPack.assetRefs.length} assets)`,
|
|
622
|
-
};
|
|
623
|
-
appendOverviewLink(entry, task, portalBase, taskCreator);
|
|
624
|
-
results.push(entry);
|
|
625
|
-
console.log((0, uploadLog_1.formatTaskLogLine)(entry));
|
|
626
|
-
}
|
|
627
|
-
else {
|
|
628
|
-
throw new Error(`Unsupported task kind "${task.kind}".`);
|
|
379
|
+
if (creatorResult.creatorTargetError) {
|
|
380
|
+
throw new Error(creatorResult.creatorTargetError);
|
|
629
381
|
}
|
|
382
|
+
const entry = await processSingleUploadTask(state, task, taskCreator, options);
|
|
383
|
+
pushLoggedEntry(results, entry);
|
|
630
384
|
}
|
|
631
385
|
catch (error) {
|
|
632
386
|
if (error instanceof http_1.CLIUnsupportedClientError) {
|
|
633
387
|
throw error;
|
|
634
388
|
}
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
message = 'upload failed: asset bundle too large (HTTP 413). Reduce the size of your app files and try again.';
|
|
638
|
-
}
|
|
639
|
-
else if (!message.startsWith('upload failed')) {
|
|
640
|
-
message = `upload failed: ${message}`;
|
|
641
|
-
}
|
|
642
|
-
const entry = {
|
|
643
|
-
action: 'upload',
|
|
644
|
-
status: 'error',
|
|
645
|
-
entityType: task.kind,
|
|
646
|
-
entityId,
|
|
647
|
-
catalogue: task.cataloguePath,
|
|
648
|
-
detail: message,
|
|
649
|
-
};
|
|
650
|
-
results.push(entry);
|
|
651
|
-
console.log((0, uploadLog_1.formatTaskLogLine)(entry));
|
|
389
|
+
const entry = buildTaskErrorEntry(task, entityId, error);
|
|
390
|
+
pushLoggedEntry(results, entry);
|
|
652
391
|
process.exitCode = process.exitCode || 1;
|
|
653
392
|
aborted = true;
|
|
654
393
|
break;
|
|
655
394
|
}
|
|
656
395
|
}
|
|
657
396
|
if (!aborted) {
|
|
658
|
-
|
|
659
|
-
await applyGraphOperations(client, graphState);
|
|
660
|
-
}
|
|
661
|
-
catch (error) {
|
|
662
|
-
const message = typeof error?.message === 'string' && error.message.trim().length > 0
|
|
663
|
-
? error.message.trim()
|
|
664
|
-
: 'graph batch upload failed';
|
|
665
|
-
const entry = {
|
|
666
|
-
action: 'upload',
|
|
667
|
-
status: 'error',
|
|
668
|
-
entityType: 'app',
|
|
669
|
-
entityId: 'graph',
|
|
670
|
-
catalogue: 'graph:batch',
|
|
671
|
-
detail: `upload failed: ${message}`,
|
|
672
|
-
};
|
|
673
|
-
results.push(entry);
|
|
674
|
-
console.log((0, uploadLog_1.formatTaskLogLine)(entry));
|
|
675
|
-
process.exitCode = process.exitCode || 1;
|
|
676
|
-
}
|
|
397
|
+
await flushGraphState(client, state.graphState, results);
|
|
677
398
|
}
|
|
678
399
|
return results;
|
|
679
400
|
}
|