@genex-ai/cli-demo 0.77.1-dev.199 → 0.78.0-dev.200

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 (2) hide show
  1. package/dist/index.js +100 -8
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -3225,7 +3225,7 @@ async function deployGame(ctx, opts, log) {
3225
3225
  return false;
3226
3226
  }
3227
3227
  log.step(`Uploading ${files.length} files\u2026`);
3228
- if (!await uploadAll(grant.uploadUrl, grant.token, files, log)) {
3228
+ if (!await uploadAll(grant, files, log)) {
3229
3229
  log.error("Couldn't upload your game \u2014 please try again.");
3230
3230
  return false;
3231
3231
  }
@@ -3306,21 +3306,24 @@ async function getUploadToken(ctx, commit, log) {
3306
3306
  }
3307
3307
  return await res.json();
3308
3308
  }
3309
- async function uploadAll(uploadUrl, uploadToken, files, log) {
3310
- const base = uploadUrl.replace(/\/+$/, "");
3309
+ async function uploadAll(grant, files, log) {
3310
+ const base = grant.uploadUrl.replace(/\/+$/, "");
3311
+ const threshold = grant.limits.singlePutMaxBytes ?? grant.limits.maxFileBytes;
3312
+ const small = files.filter((f) => f.bytes.length <= threshold);
3313
+ const large = files.filter((f) => f.bytes.length > threshold);
3311
3314
  let failed = false;
3312
3315
  let next = 0;
3313
3316
  const worker = async () => {
3314
3317
  while (!failed) {
3315
3318
  const i = next++;
3316
- if (i >= files.length) return;
3317
- const f = files[i];
3319
+ if (i >= small.length) return;
3320
+ const f = small[i];
3318
3321
  const encoded = f.relPath.split("/").map(encodeURIComponent).join("/");
3319
3322
  let res = null;
3320
3323
  try {
3321
3324
  res = await fetch(`${base}/${encoded}`, {
3322
3325
  method: "PUT",
3323
- headers: { Authorization: `Bearer ${uploadToken}` },
3326
+ headers: { Authorization: `Bearer ${grant.token}` },
3324
3327
  body: f.bytes
3325
3328
  });
3326
3329
  } catch {
@@ -3332,8 +3335,97 @@ async function uploadAll(uploadUrl, uploadToken, files, log) {
3332
3335
  }
3333
3336
  }
3334
3337
  };
3335
- await Promise.all(Array.from({ length: Math.min(8, files.length) }, () => worker()));
3336
- return !failed;
3338
+ await Promise.all(Array.from({ length: Math.min(8, small.length) }, () => worker()));
3339
+ if (failed) return false;
3340
+ for (const f of large) {
3341
+ if (!await uploadMultipart(base, grant.token, f, grant.limits.partSizeBytes, log)) return false;
3342
+ }
3343
+ return true;
3344
+ }
3345
+ async function uploadMultipart(base, uploadToken, f, partSize, log) {
3346
+ const encoded = f.relPath.split("/").map(encodeURIComponent).join("/");
3347
+ const auth = { Authorization: `Bearer ${uploadToken}` };
3348
+ const totalParts = Math.ceil(f.bytes.length / partSize);
3349
+ log.step(`Uploading ${f.relPath} (${(f.bytes.length / 1048576).toFixed(1)} MB in ${totalParts} parts)\u2026`);
3350
+ let uploadId;
3351
+ try {
3352
+ const res = await fetch(`${base}/${encoded}?action=mpu-create`, { method: "POST", headers: auth });
3353
+ if (!res.ok) {
3354
+ log.warn(`Upload failed for ${f.relPath} (HTTP ${res.status} starting multipart).`);
3355
+ return false;
3356
+ }
3357
+ uploadId = (await res.json()).uploadId;
3358
+ } catch {
3359
+ log.warn(`Upload failed for ${f.relPath} (network error starting multipart).`);
3360
+ return false;
3361
+ }
3362
+ const abort = async () => {
3363
+ try {
3364
+ await fetch(`${base}/${encoded}?action=mpu-abort&uploadId=${encodeURIComponent(uploadId)}`, {
3365
+ method: "DELETE",
3366
+ headers: auth
3367
+ });
3368
+ } catch {
3369
+ }
3370
+ };
3371
+ const parts = [];
3372
+ let failed = false;
3373
+ let next = 0;
3374
+ const partWorker = async () => {
3375
+ while (!failed) {
3376
+ const i = next++;
3377
+ if (i >= totalParts) return;
3378
+ const partNumber = i + 1;
3379
+ const bytes = f.bytes.subarray(i * partSize, Math.min((i + 1) * partSize, f.bytes.length));
3380
+ const part = await uploadPartWithRetry(base, encoded, auth, uploadId, partNumber, bytes);
3381
+ if (!part) {
3382
+ failed = true;
3383
+ log.warn(`Upload failed for ${f.relPath} (part ${partNumber}/${totalParts}).`);
3384
+ return;
3385
+ }
3386
+ parts.push(part);
3387
+ }
3388
+ };
3389
+ await Promise.all(Array.from({ length: Math.min(4, totalParts) }, () => partWorker()));
3390
+ if (failed) {
3391
+ await abort();
3392
+ return false;
3393
+ }
3394
+ parts.sort((a, b) => a.partNumber - b.partNumber);
3395
+ try {
3396
+ const res = await fetch(`${base}/${encoded}?action=mpu-complete&uploadId=${encodeURIComponent(uploadId)}`, {
3397
+ method: "POST",
3398
+ headers: { ...auth, "Content-Type": "application/json" },
3399
+ body: JSON.stringify({ parts })
3400
+ });
3401
+ if (!res.ok) {
3402
+ log.warn(`Upload failed for ${f.relPath} (HTTP ${res.status} completing multipart).`);
3403
+ await abort();
3404
+ return false;
3405
+ }
3406
+ } catch {
3407
+ log.warn(`Upload failed for ${f.relPath} (network error completing multipart).`);
3408
+ await abort();
3409
+ return false;
3410
+ }
3411
+ return true;
3412
+ }
3413
+ async function uploadPartWithRetry(base, encoded, auth, uploadId, partNumber, bytes) {
3414
+ for (let attempt = 0; ; attempt++) {
3415
+ let res = null;
3416
+ try {
3417
+ res = await fetch(
3418
+ `${base}/${encoded}?action=mpu-part&uploadId=${encodeURIComponent(uploadId)}&partNumber=${partNumber}`,
3419
+ { method: "PUT", headers: auth, body: bytes }
3420
+ );
3421
+ if (res.ok) return await res.json();
3422
+ } catch {
3423
+ res = null;
3424
+ }
3425
+ if (res && res.status < 500) return null;
3426
+ if (attempt >= 2) return null;
3427
+ await new Promise((resolve) => setTimeout(resolve, attempt === 0 ? 500 : 2e3));
3428
+ }
3337
3429
  }
3338
3430
  async function callPublish(ctx, commit, opts, log) {
3339
3431
  let res;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@genex-ai/cli-demo",
3
- "version": "0.77.1-dev.199",
3
+ "version": "0.78.0-dev.200",
4
4
  "description": "Set up your project's agent workspace (.claude/.codex/.cursor in the game folder), authorize, create a game project, generate AI assets, and publish (genex CLI).",
5
5
  "type": "module",
6
6
  "bin": {