@vosjs/cli 0.35.0 → 0.36.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.
@@ -3876,9 +3876,9 @@ function proposeTransitions(input, kind) {
3876
3876
  for (const step of steps) {
3877
3877
  if (!step.navigated || step.skipped) continue;
3878
3878
  const name = step.id ?? String(step.step);
3879
- const wait = step.do === "wait";
3880
- const outEnd = round3(wait ? step.tStart : step.tEnd);
3881
- const inStart = round3(wait ? step.tEnd : step.tEnd + STEP_SETTLE_SECONDS);
3879
+ const wait2 = step.do === "wait";
3880
+ const outEnd = round3(wait2 ? step.tStart : step.tEnd);
3881
+ const inStart = round3(wait2 ? step.tEnd : step.tEnd + STEP_SETTLE_SECONDS);
3882
3882
  const segs = doc.segments;
3883
3883
  if (segs.some(
3884
3884
  (s) => !s.media && Math.abs(s.out - outEnd) < 0.05 && s !== segs.at(-1)
@@ -6165,8 +6165,8 @@ async function clockMotion(dur, at2, clock) {
6165
6165
  await at2(easeInOutCubic(Math.min(1, elapsed / dur)));
6166
6166
  samples++;
6167
6167
  const next = start + Math.ceil((clock.now() - start) / MOTION_TICK_MS) * MOTION_TICK_MS;
6168
- const wait = Math.min(next, start + dur) - clock.now();
6169
- if (wait > 0) await clock.sleep(wait);
6168
+ const wait2 = Math.min(next, start + dur) - clock.now();
6169
+ if (wait2 > 0) await clock.sleep(wait2);
6170
6170
  }
6171
6171
  await at2(1);
6172
6172
  return samples + 1;
@@ -6176,8 +6176,8 @@ async function clockTyping(chars, delay, type, clock) {
6176
6176
  for (let i = 0; i < chars.length; i++) {
6177
6177
  await type(chars[i], i);
6178
6178
  const due = start + (i + 1) * delay;
6179
- const wait = due - clock.now();
6180
- if (wait > 0 && i < chars.length - 1) await clock.sleep(wait);
6179
+ const wait2 = due - clock.now();
6180
+ if (wait2 > 0 && i < chars.length - 1) await clock.sleep(wait2);
6181
6181
  }
6182
6182
  }
6183
6183
  function askedMs(step) {
@@ -7385,7 +7385,7 @@ async function cmdMove(argv) {
7385
7385
  });
7386
7386
  const key = requireCredential(strFlag(flags, "key"));
7387
7387
  const folderId = toRef === "none" ? null : resolveFolder(await listFolders(origin, key), toRef).id;
7388
- let failed = 0;
7388
+ let failed2 = 0;
7389
7389
  const moved = [];
7390
7390
  for (const raw of positionals) {
7391
7391
  const id = parseVosId(raw);
@@ -7400,28 +7400,28 @@ async function cmdMove(argv) {
7400
7400
  continue;
7401
7401
  }
7402
7402
  if (asVos.status === 404) {
7403
- const asAsset = await apiJson(origin, `/api/assets/${id}`, {
7403
+ const asAsset2 = await apiJson(origin, `/api/assets/${id}`, {
7404
7404
  method: "PATCH",
7405
7405
  key,
7406
7406
  body: { folderId }
7407
7407
  });
7408
- if (asAsset.status === 200) {
7408
+ if (asAsset2.status === 200) {
7409
7409
  moved.push({ id, kind: "asset" });
7410
7410
  r.log(`moved asset ${id}`);
7411
7411
  continue;
7412
7412
  }
7413
- failed++;
7414
- r.log(apiError(`move ${id}`, asAsset));
7413
+ failed2++;
7414
+ r.log(apiError(`move ${id}`, asAsset2));
7415
7415
  continue;
7416
7416
  }
7417
- failed++;
7417
+ failed2++;
7418
7418
  r.log(apiError(`move ${id}`, asVos));
7419
7419
  }
7420
7420
  r.done(
7421
- { moved, failed, folderId },
7422
- `moved ${moved.length}/${positionals.length} into ${toRef === "none" ? "the top level" : toRef}${failed ? ` \u2014 ${failed} failed` : ""}`
7421
+ { moved, failed: failed2, folderId },
7422
+ `moved ${moved.length}/${positionals.length} into ${toRef === "none" ? "the top level" : toRef}${failed2 ? ` \u2014 ${failed2} failed` : ""}`
7423
7423
  );
7424
- return failed ? EXIT_ERROR : EXIT_OK;
7424
+ return failed2 ? EXIT_ERROR : EXIT_OK;
7425
7425
  }
7426
7426
  function recipeLine(filename, body) {
7427
7427
  const h = recipeHints(body);
@@ -7567,6 +7567,144 @@ async function cmdFolder(argv) {
7567
7567
  }
7568
7568
  }
7569
7569
 
7570
+ // src/plugin/uploadAsset.ts
7571
+ var SINGLE_SHOT_MAX_BYTES = 32 * 1024 * 1024;
7572
+ var PART_ATTEMPTS = 3;
7573
+ function planParts(totalBytes, partBytes) {
7574
+ if (totalBytes <= 0 || partBytes <= 0) return [];
7575
+ const parts = [];
7576
+ for (let start = 0; start < totalBytes; start += partBytes) {
7577
+ parts.push({
7578
+ partNumber: parts.length + 1,
7579
+ start,
7580
+ end: Math.min(start + partBytes, totalBytes)
7581
+ });
7582
+ }
7583
+ return parts;
7584
+ }
7585
+ function failed(what, r) {
7586
+ const detail = typeof r.body.error === "string" ? r.body.error : "";
7587
+ return new Error(`${what} (${r.status})${detail ? `: ${detail}` : ""}`);
7588
+ }
7589
+ function asAsset(body) {
7590
+ return {
7591
+ id: String(body.id),
7592
+ url: String(body.url),
7593
+ size: typeof body.size === "number" ? body.size : 0,
7594
+ reused: body.reused === true
7595
+ };
7596
+ }
7597
+ function transient(status) {
7598
+ return status >= 500 || status === 429;
7599
+ }
7600
+ var wait = (ms) => new Promise((r) => setTimeout(r, ms));
7601
+ async function uploadAsset(target, bytes, opts) {
7602
+ return bytes.length > SINGLE_SHOT_MAX_BYTES ? uploadInParts(target, bytes, opts) : uploadWhole(target, bytes, opts);
7603
+ }
7604
+ async function uploadWhole(target, bytes, opts) {
7605
+ const res = await apiJson(target.origin, "/api/assets/recording", {
7606
+ method: "POST",
7607
+ key: target.key,
7608
+ headers: {
7609
+ "Content-Type": opts.contentType,
7610
+ "Content-Length": String(bytes.length),
7611
+ "X-Filename": opts.filename,
7612
+ "X-Content-Hash": opts.contentHash,
7613
+ ...opts.durationSeconds && opts.durationSeconds > 0 ? { "X-Content-Duration": opts.durationSeconds.toFixed(3) } : {}
7614
+ },
7615
+ raw: bytes
7616
+ });
7617
+ if (res.status !== 201 && res.status !== 200)
7618
+ throw failed("upload failed", res);
7619
+ return asAsset(res.body);
7620
+ }
7621
+ async function uploadInParts(target, bytes, opts) {
7622
+ const begun = await apiJson(
7623
+ target.origin,
7624
+ "/api/assets/recording/multipart",
7625
+ {
7626
+ method: "POST",
7627
+ key: target.key,
7628
+ body: {
7629
+ filename: opts.filename,
7630
+ contentType: opts.contentType,
7631
+ size: bytes.length,
7632
+ contentHash: opts.contentHash,
7633
+ ...opts.durationSeconds && opts.durationSeconds > 0 ? { durationSeconds: opts.durationSeconds } : {}
7634
+ }
7635
+ }
7636
+ );
7637
+ if (begun.status !== 201 && begun.status !== 200) {
7638
+ throw failed("upload failed", begun);
7639
+ }
7640
+ if (begun.body.reused === true || typeof begun.body.url === "string") {
7641
+ return asAsset(begun.body);
7642
+ }
7643
+ const uploadId = String(begun.body.id);
7644
+ const partBytes = Number(begun.body.partBytes);
7645
+ if (!Number.isFinite(partBytes) || partBytes <= 0) {
7646
+ throw new Error("upload failed: the platform returned no part size");
7647
+ }
7648
+ const parts = planParts(bytes.length, partBytes);
7649
+ const etags = [];
7650
+ try {
7651
+ for (const part of parts) {
7652
+ const chunk2 = bytes.subarray(part.start, part.end);
7653
+ let sent = null;
7654
+ for (let attempt = 1; attempt <= PART_ATTEMPTS; attempt++) {
7655
+ try {
7656
+ sent = await apiJson(
7657
+ target.origin,
7658
+ `/api/assets/recording/multipart/${uploadId}/parts/${part.partNumber}`,
7659
+ {
7660
+ method: "PUT",
7661
+ key: target.key,
7662
+ headers: {
7663
+ "Content-Type": "application/octet-stream",
7664
+ "Content-Length": String(chunk2.length)
7665
+ },
7666
+ raw: chunk2
7667
+ }
7668
+ );
7669
+ } catch (networkError) {
7670
+ if (attempt === PART_ATTEMPTS) throw networkError;
7671
+ await wait(500 * 2 ** (attempt - 1));
7672
+ continue;
7673
+ }
7674
+ if (sent.status === 200) break;
7675
+ if (!transient(sent.status) || attempt === PART_ATTEMPTS) {
7676
+ throw failed(`part ${part.partNumber} failed`, sent);
7677
+ }
7678
+ await wait(500 * 2 ** (attempt - 1));
7679
+ }
7680
+ if (!sent || sent.status !== 200) {
7681
+ throw new Error(`part ${part.partNumber} failed`);
7682
+ }
7683
+ etags.push({
7684
+ partNumber: Number(sent.body.partNumber),
7685
+ etag: String(sent.body.etag)
7686
+ });
7687
+ opts.onPart?.(etags.length, parts.length);
7688
+ }
7689
+ const sealed = await apiJson(
7690
+ target.origin,
7691
+ `/api/assets/recording/multipart/${uploadId}/complete`,
7692
+ { method: "POST", key: target.key, body: { parts: etags } }
7693
+ );
7694
+ if (sealed.status !== 201 && sealed.status !== 200) {
7695
+ throw failed("upload failed", sealed);
7696
+ }
7697
+ return asAsset(sealed.body);
7698
+ } catch (err) {
7699
+ await apiJson(
7700
+ target.origin,
7701
+ `/api/assets/recording/multipart/${uploadId}`,
7702
+ { method: "DELETE", key: target.key }
7703
+ ).catch(() => void 0);
7704
+ throw err;
7705
+ }
7706
+ }
7707
+
7570
7708
  // src/plugin/sync.ts
7571
7709
  function apiContext(flags) {
7572
7710
  return {
@@ -7649,31 +7787,25 @@ async function pushTake(dir, flags, r) {
7649
7787
  const bytes = await readFile13(take.paths.recording);
7650
7788
  const hash = createHash("sha256").update(bytes).digest("hex");
7651
7789
  r.log(`uploading recording (${Math.round(bytes.length / 1024)} kB)\u2026`);
7652
- const upload = await api(ctx, "/assets/recording", {
7653
- method: "POST",
7654
- headers: {
7655
- "Content-Type": "video/webm",
7656
- "Content-Length": String(bytes.length),
7657
- "X-Filename": RECORDING_NAME,
7658
- "X-Content-Hash": hash,
7790
+ let upload;
7791
+ try {
7792
+ upload = await uploadAsset(ctx, new Uint8Array(bytes), {
7793
+ filename: RECORDING_NAME,
7794
+ contentType: "video/webm",
7795
+ contentHash: hash,
7659
7796
  // The take's length so the server holds it to the plan's cap.
7660
- ...take.meta.durationMs > 0 ? { "X-Content-Duration": (take.meta.durationMs / 1e3).toFixed(3) } : {}
7661
- },
7662
- raw: new Uint8Array(bytes)
7663
- });
7664
- if (upload.status !== 201 && upload.status !== 200) {
7797
+ ...take.meta.durationMs > 0 ? { durationSeconds: take.meta.durationMs / 1e3 } : {},
7798
+ onPart: (done, total) => r.log(` part ${done}/${total}`)
7799
+ });
7800
+ } catch (e) {
7665
7801
  throw new Error(
7666
- `recording upload failed (${upload.status}): ${String(upload.json.error ?? "")}`
7802
+ `recording upload failed: ${e instanceof Error ? e.message : String(e)}`
7667
7803
  );
7668
7804
  }
7669
- const assetId = String(upload.json.id);
7670
- const assetUrl = String(upload.json.url);
7671
- r.event({
7672
- event: "recording",
7673
- assetId,
7674
- reused: upload.json.reused === true
7675
- });
7676
- if (upload.json.reused === true) r.log(" recording already hosted \u2014 reused");
7805
+ const assetId = upload.id;
7806
+ const assetUrl = upload.url;
7807
+ r.event({ event: "recording", assetId, reused: upload.reused });
7808
+ if (upload.reused) r.log(" recording already hosted \u2014 reused");
7677
7809
  const docForPush = structuredClone(take.doc);
7678
7810
  docForPush.source.videoKey = assetUrl;
7679
7811
  if (docForPush.source.camKey && !/^(https?:)?\//.test(docForPush.source.camKey)) {
@@ -7693,24 +7825,21 @@ async function pushTake(dir, flags, r) {
7693
7825
  const media = await readFile13(file);
7694
7826
  const mediaHash = createHash("sha256").update(media).digest("hex");
7695
7827
  const type = mediaContentType(file);
7696
- const put = await api(ctx, "/assets/recording", {
7697
- method: "POST",
7698
- headers: {
7699
- "Content-Type": type,
7700
- "Content-Length": String(media.length),
7701
- "X-Filename": basename(file),
7702
- "X-Content-Hash": mediaHash
7703
- },
7704
- raw: new Uint8Array(media)
7705
- });
7706
- if (put.status !== 201 && put.status !== 200) {
7828
+ let put;
7829
+ try {
7830
+ put = await uploadAsset(ctx, new Uint8Array(media), {
7831
+ filename: basename(file),
7832
+ contentType: type,
7833
+ contentHash: mediaHash
7834
+ });
7835
+ } catch (e) {
7707
7836
  throw new Error(
7708
- `${ref.where} upload failed (${put.status}): ${String(put.json.error ?? "")}`
7837
+ `${ref.where} upload failed: ${e instanceof Error ? e.message : String(e)}`
7709
7838
  );
7710
7839
  }
7711
- ref.set(String(put.json.url));
7840
+ ref.set(put.url);
7712
7841
  r.log(
7713
- ` ${ref.where}: ${ref.key} \u2192 asset ${String(put.json.id)}${put.json.reused === true ? " (reused)" : ""}`
7842
+ ` ${ref.where}: ${ref.key} \u2192 asset ${put.id}${put.reused ? " (reused)" : ""}`
7714
7843
  );
7715
7844
  }
7716
7845
  const lowered = lowerToComposition3(docForPush);
@@ -11246,4 +11375,4 @@ export {
11246
11375
  convertAgentBrowser,
11247
11376
  run
11248
11377
  };
11249
- //# sourceMappingURL=chunk-VQXOM2NU.js.map
11378
+ //# sourceMappingURL=chunk-MIZWK5AN.js.map