trajectories-sh 1.10.3 → 1.11.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.
package/dist/cli.js CHANGED
@@ -81,7 +81,7 @@ async function uploadTrajectoryAction(directory, opts) {
81
81
  Uploading trajectory ${directory} \u2192 ${chalk.cyan(slug)}
82
82
  `));
83
83
  try {
84
- const { uploadJob } = await import("./upload-UXM6LEBL.js");
84
+ const { uploadJob } = await import("./upload-YKSKGJET.js");
85
85
  const result = await uploadJob(directory, {
86
86
  slug,
87
87
  name: opts.name,
@@ -10,8 +10,10 @@ import { tmpdir } from "os";
10
10
  import { randomBytes, createHash } from "crypto";
11
11
  var SKIP = /* @__PURE__ */ new Set(["__pycache__", ".DS_Store", ".git", "node_modules"]);
12
12
  var MAX_FILE_SIZE = 50 * 1024 * 1024;
13
- var CONCURRENCY = Number(process.env.TRAJECTORIES_CLI_CONCURRENCY ?? "15");
13
+ var CONCURRENCY = Number(process.env.TRAJECTORIES_CLI_CONCURRENCY ?? "32");
14
14
  var BATCH_SIZE = 100;
15
+ var WEBP_CONCURRENCY = Number(process.env.TRAJECTORIES_CLI_WEBP_CONCURRENCY ?? "4");
16
+ var HASH_CONCURRENCY = Number(process.env.TRAJECTORIES_CLI_HASH_CONCURRENCY ?? "16");
15
17
  function collectFiles(dir) {
16
18
  const files = [];
17
19
  function walk(current) {
@@ -85,26 +87,30 @@ async function convertToWebP(dir) {
85
87
  sharp.cache({ memory: 0, files: 0, items: 0 });
86
88
  let converted = 0;
87
89
  let savedBytes = 0;
88
- try {
89
- for (let i = 0; i < pngs.length; i++) {
90
- const f = pngs[i];
91
- const srcPath = join(tmpDir, f.relPath);
92
- const webpPath = srcPath.replace(/\.png$/i, ".webp");
93
- try {
94
- await pngToWebp(srcPath, webpPath);
95
- const origSize = statSync(srcPath).size;
96
- const newSize = statSync(webpPath).size;
97
- if (newSize < origSize) {
98
- unlinkSync(srcPath);
99
- savedBytes += origSize - newSize;
100
- converted++;
101
- } else {
102
- unlinkSync(webpPath);
103
- }
104
- } catch {
90
+ let processed = 0;
91
+ const total = pngs.length;
92
+ async function convertOne(f) {
93
+ const srcPath = join(tmpDir, f.relPath);
94
+ const webpPath = srcPath.replace(/\.png$/i, ".webp");
95
+ try {
96
+ await pngToWebp(srcPath, webpPath);
97
+ const origSize = statSync(srcPath).size;
98
+ const newSize = statSync(webpPath).size;
99
+ if (newSize < origSize) {
100
+ unlinkSync(srcPath);
101
+ savedBytes += origSize - newSize;
102
+ converted++;
103
+ } else {
104
+ unlinkSync(webpPath);
105
105
  }
106
- process.stdout.write(` Converting: ${i + 1}/${pngs.length} PNGs\r`);
106
+ } catch {
107
+ } finally {
108
+ processed++;
109
+ process.stdout.write(` Converting: ${processed}/${total} PNGs\r`);
107
110
  }
111
+ }
112
+ try {
113
+ await runWithConcurrency(pngs, WEBP_CONCURRENCY, convertOne);
108
114
  console.log("");
109
115
  } finally {
110
116
  try {
@@ -135,12 +141,34 @@ async function convertToWebP(dir) {
135
141
  }
136
142
  return { convertedDir: tmpDir, pngsConverted: converted, savedBytes };
137
143
  }
138
- function computeContentHash(files) {
139
- const hash = createHash("sha256");
144
+ async function runWithConcurrency(items, n, worker) {
145
+ if (items.length === 0) return;
146
+ let next = 0;
147
+ const runners = [];
148
+ for (let k = 0; k < Math.min(n, items.length); k++) {
149
+ runners.push(
150
+ (async () => {
151
+ while (true) {
152
+ const i = next++;
153
+ if (i >= items.length) return;
154
+ await worker(items[i], i);
155
+ }
156
+ })()
157
+ );
158
+ }
159
+ await Promise.all(runners);
160
+ }
161
+ async function computeContentHash(files) {
162
+ const { readFile } = await import("fs/promises");
140
163
  const sorted = [...files].sort((a, b) => a.relPath.localeCompare(b.relPath));
141
- for (const f of sorted) {
142
- hash.update(f.relPath);
143
- hash.update(readFileSync(f.absPath));
164
+ const contents = new Array(sorted.length);
165
+ await runWithConcurrency(sorted, HASH_CONCURRENCY, async (f, i) => {
166
+ contents[i] = await readFile(f.absPath);
167
+ });
168
+ const hash = createHash("sha256");
169
+ for (let i = 0; i < sorted.length; i++) {
170
+ hash.update(sorted[i].relPath);
171
+ hash.update(contents[i]);
144
172
  }
145
173
  return hash.digest("hex");
146
174
  }
@@ -280,9 +308,11 @@ async function uploadJob(dir, opts) {
280
308
  }
281
309
  const files = collectFiles(convertedDir);
282
310
  const totalSize = files.reduce((s, f) => s + f.size, 0);
283
- const contentHash = computeContentHash(files);
284
311
  console.log(` ${files.length} files, ${(totalSize / 1024 / 1024).toFixed(1)} MB`);
285
- console.log(` Hash: ${contentHash.slice(0, 16)}...`);
312
+ process.stdout.write(` Hashing...\r`);
313
+ const t0 = Date.now();
314
+ const contentHash = await computeContentHash(files);
315
+ console.log(` Hash: ${contentHash.slice(0, 16)}... (${((Date.now() - t0) / 1e3).toFixed(1)}s)`);
286
316
  try {
287
317
  const auth = await getAuthHeader();
288
318
  const usageResp = await fetch(`${getApiUrl()}/api/cli/usage`, {
@@ -360,9 +390,16 @@ async function uploadDirect(jobId, files) {
360
390
  const bar = new ProgressBar({ total: files.length, totalBytes, label: "\u2191" });
361
391
  const failedFiles = [];
362
392
  const failureReasons = [];
363
- for (let batchStart = 0; batchStart < files.length; batchStart += BATCH_SIZE) {
364
- const batch = files.slice(batchStart, batchStart + BATCH_SIZE);
365
- const urlMap = await fetchSignedUrlsForBatch(jobId, batch.map((f) => f.relPath));
393
+ const batches = [];
394
+ for (let i = 0; i < files.length; i += BATCH_SIZE) {
395
+ batches.push(files.slice(i, i + BATCH_SIZE));
396
+ }
397
+ let nextUrlsPromise = batches.length > 0 ? fetchSignedUrlsForBatch(jobId, batches[0].map((f) => f.relPath)) : null;
398
+ const pending = /* @__PURE__ */ new Set();
399
+ for (let bi = 0; bi < batches.length; bi++) {
400
+ const batch = batches[bi];
401
+ const urlMap = await nextUrlsPromise;
402
+ nextUrlsPromise = bi + 1 < batches.length ? fetchSignedUrlsForBatch(jobId, batches[bi + 1].map((f) => f.relPath)) : null;
366
403
  for (const file of batch) {
367
404
  if (!urlMap.has(file.relPath)) {
368
405
  failedFiles.push(file.relPath);
@@ -371,12 +408,9 @@ async function uploadDirect(jobId, files) {
371
408
  reason: "no signed URL from server after retries"
372
409
  });
373
410
  bar.tick(0);
411
+ continue;
374
412
  }
375
- }
376
- const pending = /* @__PURE__ */ new Set();
377
- for (const file of batch) {
378
413
  const signedUrl = urlMap.get(file.relPath);
379
- if (!signedUrl) continue;
380
414
  const task = (async () => {
381
415
  const result = await uploadOneFileWithError(file, signedUrl);
382
416
  if (result.ok) {
@@ -394,8 +428,8 @@ async function uploadDirect(jobId, files) {
394
428
  await Promise.race(pending);
395
429
  }
396
430
  }
397
- await Promise.all(pending);
398
431
  }
432
+ await Promise.all(pending);
399
433
  bar.done();
400
434
  if (failedFiles.length > 0) {
401
435
  const byReason = /* @__PURE__ */ new Map();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trajectories-sh",
3
- "version": "1.10.3",
3
+ "version": "1.11.0",
4
4
  "description": "CLI for uploading trajectory jobs to trajectories.sh",
5
5
  "type": "module",
6
6
  "bin": {