trajectories-sh 1.3.1 → 1.4.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 @@ program.command("upload <directory>").description("Upload a trajectory job direc
81
81
  Pushing ${directory} \u2192 ${chalk.cyan(slug)}
82
82
  `));
83
83
  try {
84
- const { uploadJob } = await import("./upload-3SQPHPNI.js");
84
+ const { uploadJob } = await import("./upload-WZGAR4S4.js");
85
85
  const result = await uploadJob(directory, {
86
86
  slug,
87
87
  name: opts.name,
@@ -4,8 +4,10 @@ import {
4
4
  } from "./chunk-P6J5X7LP.js";
5
5
 
6
6
  // src/upload.ts
7
- import { readdirSync, statSync, readFileSync } from "fs";
8
- import { join, relative } from "path";
7
+ import { readdirSync, statSync, readFileSync, writeFileSync, mkdirSync, copyFileSync } from "fs";
8
+ import { join, relative, dirname } from "path";
9
+ import { tmpdir } from "os";
10
+ import { randomBytes } from "crypto";
9
11
  var SKIP = /* @__PURE__ */ new Set(["__pycache__", ".DS_Store", ".git", "node_modules"]);
10
12
  var MAX_FILE_SIZE = 50 * 1024 * 1024;
11
13
  var CONCURRENCY = 12;
@@ -39,11 +41,94 @@ async function apiPost(path, body, timeout = 3e5) {
39
41
  signal: AbortSignal.timeout(timeout)
40
42
  });
41
43
  }
44
+ async function convertToWebP(dir) {
45
+ const files = collectFiles(dir);
46
+ const pngs = files.filter((f) => f.relPath.toLowerCase().endsWith(".png"));
47
+ if (pngs.length === 0) {
48
+ return { convertedDir: dir, pngsConverted: 0, savedBytes: 0 };
49
+ }
50
+ let sharp;
51
+ try {
52
+ sharp = (await import("sharp")).default;
53
+ } catch {
54
+ console.log(" \u26A0 sharp not available, skipping WebP conversion");
55
+ return { convertedDir: dir, pngsConverted: 0, savedBytes: 0 };
56
+ }
57
+ const tmpDir = join(tmpdir(), `trajectories-webp-${randomBytes(4).toString("hex")}`);
58
+ function copyDir(src, dest) {
59
+ mkdirSync(dest, { recursive: true });
60
+ for (const entry of readdirSync(src, { withFileTypes: true })) {
61
+ if (SKIP.has(entry.name)) continue;
62
+ const srcPath = join(src, entry.name);
63
+ const destPath = join(dest, entry.name);
64
+ if (entry.isDirectory()) {
65
+ copyDir(srcPath, destPath);
66
+ } else {
67
+ copyFileSync(srcPath, destPath);
68
+ }
69
+ }
70
+ }
71
+ copyDir(dir, tmpDir);
72
+ let converted = 0;
73
+ let savedBytes = 0;
74
+ const batchSize = 10;
75
+ for (let i = 0; i < pngs.length; i += batchSize) {
76
+ const batch = pngs.slice(i, i + batchSize);
77
+ await Promise.all(
78
+ batch.map(async (f) => {
79
+ const srcPath = join(tmpDir, f.relPath);
80
+ const webpPath = srcPath.replace(/\.png$/i, ".webp");
81
+ try {
82
+ await sharp(srcPath).webp({ quality: 80 }).toFile(webpPath);
83
+ const origSize = statSync(srcPath).size;
84
+ const newSize = statSync(webpPath).size;
85
+ if (newSize < origSize) {
86
+ const { unlinkSync } = await import("fs");
87
+ unlinkSync(srcPath);
88
+ savedBytes += origSize - newSize;
89
+ converted++;
90
+ } else {
91
+ const { unlinkSync } = await import("fs");
92
+ unlinkSync(webpPath);
93
+ }
94
+ } catch {
95
+ }
96
+ })
97
+ );
98
+ process.stdout.write(` Converting: ${Math.min(i + batchSize, pngs.length)}/${pngs.length} PNGs\r`);
99
+ }
100
+ console.log("");
101
+ const trajFiles = collectFiles(tmpDir).filter((f) => f.relPath.endsWith("trajectory.json"));
102
+ for (const tf of trajFiles) {
103
+ try {
104
+ let content = readFileSync(tf.absPath, "utf-8");
105
+ const original = content;
106
+ content = content.replace(/screenshots\/([^"]+)\.png/g, (match, name) => {
107
+ const webpPath = join(dirname(tf.absPath), "screenshots", `${name}.webp`);
108
+ try {
109
+ statSync(webpPath);
110
+ return `screenshots/${name}.webp`;
111
+ } catch {
112
+ return match;
113
+ }
114
+ });
115
+ if (content !== original) {
116
+ writeFileSync(tf.absPath, content);
117
+ }
118
+ } catch {
119
+ }
120
+ }
121
+ return { convertedDir: tmpDir, pngsConverted: converted, savedBytes };
122
+ }
42
123
  async function uploadJob(dir, opts) {
43
124
  const slug = opts.slug ?? dir.replace(/\/+$/, "").split("/").pop();
44
125
  const name = opts.name ?? slug;
45
126
  const visibility = opts.visibility ?? "private";
46
- const files = collectFiles(dir);
127
+ const { convertedDir, pngsConverted, savedBytes } = await convertToWebP(dir);
128
+ if (pngsConverted > 0) {
129
+ console.log(` \u2713 Converted ${pngsConverted} PNGs to WebP (saved ${(savedBytes / 1024 / 1024).toFixed(1)} MB)`);
130
+ }
131
+ const files = collectFiles(convertedDir);
47
132
  const totalSize = files.reduce((s, f) => s + f.size, 0);
48
133
  console.log(` ${files.length} files, ${(totalSize / 1024 / 1024).toFixed(1)} MB`);
49
134
  const initResp = await apiPost("/api/cli/push/init", { slug, name, visibility });
@@ -59,6 +144,13 @@ async function uploadJob(dir, opts) {
59
144
  throw new Error(`Finalize failed: ${await finalResp.text()}`);
60
145
  }
61
146
  const result = await finalResp.json();
147
+ if (convertedDir !== dir) {
148
+ try {
149
+ const { rmSync } = await import("fs");
150
+ rmSync(convertedDir, { recursive: true, force: true });
151
+ } catch {
152
+ }
153
+ }
62
154
  const apiUrl = getApiUrl();
63
155
  const siteUrl = apiUrl.includes("localhost") ? "http://localhost:3000" : apiUrl.replace("api.", "").replace(/\/$/, "");
64
156
  return {
@@ -134,9 +226,7 @@ async function uploadOneFile(file, signedUrl) {
134
226
  try {
135
227
  const resp = await fetch(signedUrl, {
136
228
  method: "PUT",
137
- headers: {
138
- "Content-Type": "application/octet-stream"
139
- },
229
+ headers: { "Content-Type": "application/octet-stream" },
140
230
  body: data,
141
231
  signal: AbortSignal.timeout(12e4)
142
232
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trajectories-sh",
3
- "version": "1.3.1",
3
+ "version": "1.4.0",
4
4
  "description": "CLI for uploading trajectory jobs to trajectories.sh",
5
5
  "type": "module",
6
6
  "bin": {
@@ -26,10 +26,11 @@
26
26
  "node": ">=18"
27
27
  },
28
28
  "dependencies": {
29
+ "chalk": "^5.0.0",
29
30
  "commander": "^13.0.0",
31
+ "sharp": "^0.34.5",
30
32
  "open": "^10.0.0",
31
33
  "ora": "^8.0.0",
32
- "chalk": "^5.0.0",
33
34
  "tar": "^7.0.0"
34
35
  },
35
36
  "devDependencies": {