@seedvault/cli 0.4.2 → 0.4.4

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/sv.js +53 -26
  2. package/package.json +1 -1
package/dist/sv.js CHANGED
@@ -158,6 +158,9 @@ function createClient(serverUrl, token) {
158
158
  if (opts.contentType) {
159
159
  headers["Content-Type"] = opts.contentType;
160
160
  }
161
+ if (opts.extraHeaders) {
162
+ Object.assign(headers, opts.extraHeaders);
163
+ }
161
164
  const res = await fetch(`${base}${path}`, {
162
165
  method,
163
166
  headers,
@@ -199,10 +202,16 @@ function createClient(serverUrl, token) {
199
202
  const res = await request("GET", "/v1/contributors");
200
203
  return res.json();
201
204
  },
202
- async putFile(username, path, content) {
205
+ async putFile(username, path, content, opts) {
206
+ const extraHeaders = {};
207
+ if (opts?.originCtime)
208
+ extraHeaders["X-Origin-Ctime"] = opts.originCtime;
209
+ if (opts?.originMtime)
210
+ extraHeaders["X-Origin-Mtime"] = opts.originMtime;
203
211
  const res = await request("PUT", `/v1/files/${username}/${encodePath(path)}`, {
204
212
  body: content,
205
- contentType: "text/markdown"
213
+ contentType: "text/markdown",
214
+ extraHeaders: Object.keys(extraHeaders).length > 0 ? extraHeaders : undefined
206
215
  });
207
216
  return res.json();
208
217
  },
@@ -2148,7 +2157,8 @@ class RetryQueue {
2148
2157
  const op = this.items[0];
2149
2158
  try {
2150
2159
  if (op.type === "put" && op.content !== null) {
2151
- await this.client.putFile(op.username, op.serverPath, op.content);
2160
+ const opts = op.originCtime || op.originMtime ? { originCtime: op.originCtime ?? undefined, originMtime: op.originMtime ?? undefined } : undefined;
2161
+ await this.client.putFile(op.username, op.serverPath, op.content, opts);
2152
2162
  } else if (op.type === "delete") {
2153
2163
  await this.client.deleteFile(op.username, op.serverPath);
2154
2164
  }
@@ -2228,7 +2238,9 @@ class Syncer {
2228
2238
  username: this.username,
2229
2239
  serverPath: f.path,
2230
2240
  content: null,
2231
- queuedAt: new Date().toISOString()
2241
+ queuedAt: new Date().toISOString(),
2242
+ originCtime: null,
2243
+ originMtime: null
2232
2244
  });
2233
2245
  }
2234
2246
  });
@@ -2244,7 +2256,7 @@ class Syncer {
2244
2256
  const { files: serverFiles } = await this.client.listFiles(this.username, collection.name + "/");
2245
2257
  const serverMap = new Map;
2246
2258
  for (const f of serverFiles) {
2247
- serverMap.set(f.path, f.modifiedAt);
2259
+ serverMap.set(f.path, f);
2248
2260
  }
2249
2261
  const localFiles = await walkMd(collection.path);
2250
2262
  const localServerPaths = new Set;
@@ -2253,8 +2265,9 @@ class Syncer {
2253
2265
  const relPath = toPosixPath(relative5(collection.path, localFile.path));
2254
2266
  const serverPath = `${collection.name}/${relPath}`;
2255
2267
  localServerPaths.add(serverPath);
2256
- const serverMod = serverMap.get(serverPath);
2257
- if (serverMod) {
2268
+ const serverEntry = serverMap.get(serverPath);
2269
+ if (serverEntry) {
2270
+ const serverMod = serverEntry.originMtime || serverEntry.modifiedAt;
2258
2271
  const serverDate = new Date(serverMod).getTime();
2259
2272
  const localDate = localFile.mtimeMs;
2260
2273
  if (localDate <= serverDate) {
@@ -2263,11 +2276,16 @@ class Syncer {
2263
2276
  }
2264
2277
  }
2265
2278
  const content = await readFile(localFile.path, "utf-8");
2266
- toUpload.push({ serverPath, content });
2279
+ const originCtime = new Date(localFile.birthtimeMs).toISOString();
2280
+ const originMtime = new Date(localFile.mtimeMs).toISOString();
2281
+ toUpload.push({ serverPath, content, originCtime, originMtime });
2267
2282
  }
2268
2283
  await pooled(toUpload, SYNC_CONCURRENCY, async (item) => {
2269
2284
  try {
2270
- await this.client.putFile(this.username, item.serverPath, item.content);
2285
+ await this.client.putFile(this.username, item.serverPath, item.content, {
2286
+ originCtime: item.originCtime,
2287
+ originMtime: item.originMtime
2288
+ });
2271
2289
  uploaded++;
2272
2290
  } catch {
2273
2291
  this.queue.enqueue({
@@ -2275,7 +2293,9 @@ class Syncer {
2275
2293
  username: this.username,
2276
2294
  serverPath: item.serverPath,
2277
2295
  content: item.content,
2278
- queuedAt: new Date().toISOString()
2296
+ queuedAt: new Date().toISOString(),
2297
+ originCtime: item.originCtime,
2298
+ originMtime: item.originMtime
2279
2299
  });
2280
2300
  }
2281
2301
  });
@@ -2329,14 +2349,21 @@ class Syncer {
2329
2349
  }
2330
2350
  async handleEvent(event) {
2331
2351
  if (event.type === "add" || event.type === "change") {
2332
- const content = await readFile(event.localPath, "utf-8");
2352
+ const [content, s] = await Promise.all([
2353
+ readFile(event.localPath, "utf-8"),
2354
+ stat4(event.localPath)
2355
+ ]);
2356
+ const originCtime = new Date(s.birthtimeMs).toISOString();
2357
+ const originMtime = new Date(s.mtimeMs).toISOString();
2333
2358
  this.log(`PUT ${event.serverPath} (${content.length} bytes)`);
2334
2359
  this.queue.enqueue({
2335
2360
  type: "put",
2336
2361
  username: this.username,
2337
2362
  serverPath: event.serverPath,
2338
2363
  content,
2339
- queuedAt: new Date().toISOString()
2364
+ queuedAt: new Date().toISOString(),
2365
+ originCtime,
2366
+ originMtime
2340
2367
  });
2341
2368
  } else if (event.type === "unlink") {
2342
2369
  this.log(`DELETE ${event.serverPath}`);
@@ -2345,7 +2372,9 @@ class Syncer {
2345
2372
  username: this.username,
2346
2373
  serverPath: event.serverPath,
2347
2374
  content: null,
2348
- queuedAt: new Date().toISOString()
2375
+ queuedAt: new Date().toISOString(),
2376
+ originCtime: null,
2377
+ originMtime: null
2349
2378
  });
2350
2379
  }
2351
2380
  }
@@ -2384,7 +2413,7 @@ async function walkDirRecursive(dir, results) {
2384
2413
  await walkDirRecursive(full, results);
2385
2414
  } else if (entry.isFile() && entry.name.endsWith(".md")) {
2386
2415
  const s = await stat4(full);
2387
- results.push({ path: full, mtimeMs: s.mtimeMs });
2416
+ results.push({ path: full, mtimeMs: s.mtimeMs, birthtimeMs: s.birthtimeMs });
2388
2417
  }
2389
2418
  }
2390
2419
  }
@@ -2780,15 +2809,13 @@ async function startForeground() {
2780
2809
  collections: config.collections,
2781
2810
  onLog: log
2782
2811
  });
2783
- if (config.collections.length > 0) {
2784
- log("Running initial sync...");
2785
- try {
2786
- const { uploaded, skipped, deleted } = await syncer.initialSync();
2787
- log(`Initial sync complete: ${uploaded} uploaded, ${skipped} skipped, ${deleted} deleted`);
2788
- } catch (e) {
2789
- log(`Initial sync failed: ${e.message}`);
2790
- log("Will continue watching for changes...");
2791
- }
2812
+ log("Running initial sync...");
2813
+ try {
2814
+ const { uploaded, skipped, deleted } = await syncer.initialSync();
2815
+ log(`Initial sync complete: ${uploaded} uploaded, ${skipped} skipped, ${deleted} deleted`);
2816
+ } catch (e) {
2817
+ log(`Initial sync failed: ${e.message}`);
2818
+ log("Will continue watching for changes...");
2792
2819
  }
2793
2820
  let watcher = null;
2794
2821
  const rebuildWatcher = async (collections2) => {
@@ -3047,7 +3074,7 @@ Share this with the person you want to invite.`);
3047
3074
  }
3048
3075
  }
3049
3076
 
3050
- // src/commands/upgrade.ts
3077
+ // src/commands/update.ts
3051
3078
  import { readFileSync as readFileSync2 } from "fs";
3052
3079
  import { resolve as resolve6 } from "path";
3053
3080
  var {$ } = globalThis.Bun;
@@ -3105,7 +3132,7 @@ Vault:
3105
3132
  invite Generate an invite code (operator only)
3106
3133
 
3107
3134
  Maintenance:
3108
- upgrade Upgrade CLI to latest version
3135
+ update Update CLI to latest version
3109
3136
  `.trim();
3110
3137
  async function main() {
3111
3138
  const [cmd, ...args] = process.argv.slice(2);
@@ -3145,7 +3172,7 @@ async function main() {
3145
3172
  return await contributors();
3146
3173
  case "invite":
3147
3174
  return await invite();
3148
- case "upgrade":
3175
+ case "update":
3149
3176
  return await upgrade();
3150
3177
  default:
3151
3178
  console.error(`Unknown command: ${cmd}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seedvault/cli",
3
- "version": "0.4.2",
3
+ "version": "0.4.4",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "sv": "bin/sv.mjs"