trajectories-sh 1.10.3 → 1.12.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-
|
|
84
|
+
const { uploadJob } = await import("./upload-CDNIKYW3.js");
|
|
85
85
|
const result = await uploadJob(directory, {
|
|
86
86
|
slug,
|
|
87
87
|
name: opts.name,
|
|
@@ -92,8 +92,11 @@ async function uploadTrajectoryAction(directory, opts) {
|
|
|
92
92
|
if (result.meanReward != null) {
|
|
93
93
|
console.log(` Pass rate: ${(result.meanReward * 100).toFixed(0)}%`);
|
|
94
94
|
}
|
|
95
|
-
console.log(` Viewer:
|
|
96
|
-
|
|
95
|
+
console.log(` Viewer: ${result.viewerUrl}`);
|
|
96
|
+
if (result.existingDuplicateUrl) {
|
|
97
|
+
console.log(` Earlier copy: ${result.existingDuplicateUrl}`);
|
|
98
|
+
}
|
|
99
|
+
console.log("");
|
|
97
100
|
} catch (e) {
|
|
98
101
|
console.error(chalk.red(`
|
|
99
102
|
\u2717 ${e.message}
|
|
@@ -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 ?? "
|
|
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
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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
|
-
|
|
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
|
|
139
|
-
|
|
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
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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
|
-
|
|
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`, {
|
|
@@ -315,14 +345,18 @@ async function uploadJob(dir, opts) {
|
|
|
315
345
|
}
|
|
316
346
|
const initResp = await apiPost("/api/cli/push/init", { slug, name, visibility, estimated_bytes: totalSize, content_hash: contentHash });
|
|
317
347
|
if (!initResp.ok) {
|
|
318
|
-
if (initResp.status === 409) {
|
|
319
|
-
const err = await initResp.json().catch(() => ({ detail: "Duplicate trajectory" }));
|
|
320
|
-
throw new Error(err.detail);
|
|
321
|
-
}
|
|
322
348
|
throw new Error(`Init failed: ${await initResp.text()}`);
|
|
323
349
|
}
|
|
324
|
-
const
|
|
350
|
+
const initBody = await initResp.json();
|
|
351
|
+
const jobId = initBody.job_id;
|
|
325
352
|
console.log(` Job ID: ${jobId}`);
|
|
353
|
+
const apiUrlEarly = getApiUrl();
|
|
354
|
+
const siteUrlEarly = apiUrlEarly.includes("localhost") ? "http://localhost:3000" : apiUrlEarly.replace("api.", "").replace(/\/$/, "");
|
|
355
|
+
const existingDuplicateUrl = initBody.existing_duplicate ? `${siteUrlEarly}/t/${initBody.existing_duplicate.job_id}` : void 0;
|
|
356
|
+
if (existingDuplicateUrl) {
|
|
357
|
+
console.log(` \u24D8 This content was already uploaded as '${initBody.existing_duplicate.slug}' \u2014 uploading anyway.`);
|
|
358
|
+
console.log(` Existing: ${existingDuplicateUrl}`);
|
|
359
|
+
}
|
|
326
360
|
await uploadDirect(jobId, files);
|
|
327
361
|
console.log(" Finalizing...");
|
|
328
362
|
const finalResp = await apiPost(`/api/cli/push/${jobId}/finalize`, {});
|
|
@@ -351,7 +385,8 @@ async function uploadJob(dir, opts) {
|
|
|
351
385
|
slug: result.slug,
|
|
352
386
|
nTrials: result.n_trials,
|
|
353
387
|
meanReward: result.mean_reward,
|
|
354
|
-
viewerUrl: `${siteUrl}/t/${jobId}
|
|
388
|
+
viewerUrl: `${siteUrl}/t/${jobId}`,
|
|
389
|
+
existingDuplicateUrl
|
|
355
390
|
};
|
|
356
391
|
}
|
|
357
392
|
async function uploadDirect(jobId, files) {
|
|
@@ -360,9 +395,16 @@ async function uploadDirect(jobId, files) {
|
|
|
360
395
|
const bar = new ProgressBar({ total: files.length, totalBytes, label: "\u2191" });
|
|
361
396
|
const failedFiles = [];
|
|
362
397
|
const failureReasons = [];
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
398
|
+
const batches = [];
|
|
399
|
+
for (let i = 0; i < files.length; i += BATCH_SIZE) {
|
|
400
|
+
batches.push(files.slice(i, i + BATCH_SIZE));
|
|
401
|
+
}
|
|
402
|
+
let nextUrlsPromise = batches.length > 0 ? fetchSignedUrlsForBatch(jobId, batches[0].map((f) => f.relPath)) : null;
|
|
403
|
+
const pending = /* @__PURE__ */ new Set();
|
|
404
|
+
for (let bi = 0; bi < batches.length; bi++) {
|
|
405
|
+
const batch = batches[bi];
|
|
406
|
+
const urlMap = await nextUrlsPromise;
|
|
407
|
+
nextUrlsPromise = bi + 1 < batches.length ? fetchSignedUrlsForBatch(jobId, batches[bi + 1].map((f) => f.relPath)) : null;
|
|
366
408
|
for (const file of batch) {
|
|
367
409
|
if (!urlMap.has(file.relPath)) {
|
|
368
410
|
failedFiles.push(file.relPath);
|
|
@@ -371,12 +413,9 @@ async function uploadDirect(jobId, files) {
|
|
|
371
413
|
reason: "no signed URL from server after retries"
|
|
372
414
|
});
|
|
373
415
|
bar.tick(0);
|
|
416
|
+
continue;
|
|
374
417
|
}
|
|
375
|
-
}
|
|
376
|
-
const pending = /* @__PURE__ */ new Set();
|
|
377
|
-
for (const file of batch) {
|
|
378
418
|
const signedUrl = urlMap.get(file.relPath);
|
|
379
|
-
if (!signedUrl) continue;
|
|
380
419
|
const task = (async () => {
|
|
381
420
|
const result = await uploadOneFileWithError(file, signedUrl);
|
|
382
421
|
if (result.ok) {
|
|
@@ -394,8 +433,8 @@ async function uploadDirect(jobId, files) {
|
|
|
394
433
|
await Promise.race(pending);
|
|
395
434
|
}
|
|
396
435
|
}
|
|
397
|
-
await Promise.all(pending);
|
|
398
436
|
}
|
|
437
|
+
await Promise.all(pending);
|
|
399
438
|
bar.done();
|
|
400
439
|
if (failedFiles.length > 0) {
|
|
401
440
|
const byReason = /* @__PURE__ */ new Map();
|