@seedvault/cli 0.1.0 → 0.1.2

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/sv.js ADDED
@@ -0,0 +1,2705 @@
1
+ #!/usr/bin/env bun
2
+ // @bun
3
+
4
+ // src/commands/init.ts
5
+ import * as readline from "readline/promises";
6
+ import { stdin, stdout } from "process";
7
+
8
+ // src/config.ts
9
+ import { join, resolve, relative, isAbsolute } from "path";
10
+ import { homedir } from "os";
11
+ import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
12
+ var CONFIG_DIR = join(homedir(), ".config", "seedvault");
13
+ var CONFIG_PATH = join(CONFIG_DIR, "config.json");
14
+ var PID_PATH = join(CONFIG_DIR, "daemon.pid");
15
+ function getConfigDir() {
16
+ return CONFIG_DIR;
17
+ }
18
+ function getPidPath() {
19
+ return PID_PATH;
20
+ }
21
+ function ensureConfigDir() {
22
+ if (!existsSync(CONFIG_DIR)) {
23
+ mkdirSync(CONFIG_DIR, { recursive: true });
24
+ }
25
+ }
26
+ function configExists() {
27
+ return existsSync(CONFIG_PATH);
28
+ }
29
+ function loadConfig() {
30
+ if (!existsSync(CONFIG_PATH)) {
31
+ throw new Error(`No config found. Run 'sv init' first.
32
+ Expected: ${CONFIG_PATH}`);
33
+ }
34
+ const raw = readFileSync(CONFIG_PATH, "utf-8");
35
+ return JSON.parse(raw);
36
+ }
37
+ function saveConfig(config) {
38
+ ensureConfigDir();
39
+ writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2) + `
40
+ `);
41
+ }
42
+ function isChildPath(parentPath, maybeChildPath) {
43
+ const rel = relative(parentPath, maybeChildPath);
44
+ if (rel === "" || rel === ".")
45
+ return false;
46
+ return !rel.startsWith("..") && !isAbsolute(rel);
47
+ }
48
+ function pruneOverlappingCollections(collections) {
49
+ const removedOverlappingCollections = [];
50
+ const seenPaths = new Set;
51
+ const deduped = [];
52
+ for (const collection of collections) {
53
+ if (seenPaths.has(collection.path)) {
54
+ removedOverlappingCollections.push(collection);
55
+ continue;
56
+ }
57
+ seenPaths.add(collection.path);
58
+ deduped.push(collection);
59
+ }
60
+ const kept = [];
61
+ for (const candidate of deduped) {
62
+ const hasParent = deduped.some((other) => other.path !== candidate.path && isChildPath(other.path, candidate.path));
63
+ if (hasParent) {
64
+ removedOverlappingCollections.push(candidate);
65
+ } else {
66
+ kept.push(candidate);
67
+ }
68
+ }
69
+ return { collections: kept, removedOverlappingCollections };
70
+ }
71
+ function normalizeConfigCollections(config) {
72
+ const { collections, removedOverlappingCollections } = pruneOverlappingCollections(config.collections);
73
+ if (removedOverlappingCollections.length === 0) {
74
+ return { config, removedOverlappingCollections: [] };
75
+ }
76
+ return {
77
+ config: {
78
+ ...config,
79
+ collections
80
+ },
81
+ removedOverlappingCollections
82
+ };
83
+ }
84
+ function addCollection(config, collectionPath, name) {
85
+ const resolved = collectionPath.startsWith("~") ? resolve(homedir(), collectionPath.slice(2)) : resolve(collectionPath);
86
+ if (config.collections.some((f) => f.path === resolved)) {
87
+ throw new Error(`Collection path '${resolved}' is already configured.`);
88
+ }
89
+ const parentConflict = config.collections.find((f) => isChildPath(f.path, resolved));
90
+ if (parentConflict) {
91
+ throw new Error(`Cannot add '${resolved}' because it is inside existing collection '${parentConflict.name}' (${parentConflict.path}).`);
92
+ }
93
+ const removedChildCollections = config.collections.filter((f) => isChildPath(resolved, f.path));
94
+ const retainedCollections = config.collections.filter((f) => !isChildPath(resolved, f.path));
95
+ if (retainedCollections.some((f) => f.name === name)) {
96
+ throw new Error(`A collection named '${name}' already exists. Use --name to pick a different name.`);
97
+ }
98
+ return {
99
+ config: {
100
+ ...config,
101
+ collections: [...retainedCollections, { path: resolved, name }]
102
+ },
103
+ removedChildCollections
104
+ };
105
+ }
106
+ function removeCollection(config, name) {
107
+ const filtered = config.collections.filter((f) => f.name !== name);
108
+ if (filtered.length === config.collections.length) {
109
+ throw new Error(`No collection named '${name}' found.`);
110
+ }
111
+ return { ...config, collections: filtered };
112
+ }
113
+ function defaultCollectionName(collectionPath) {
114
+ const abs = collectionPath.startsWith("~") ? join(homedir(), collectionPath.slice(1)) : collectionPath;
115
+ const base = abs.split("/").filter(Boolean).pop();
116
+ if (!base)
117
+ throw new Error(`Cannot derive name from path: ${collectionPath}`);
118
+ return base;
119
+ }
120
+
121
+ // src/client.ts
122
+ class ApiError extends Error {
123
+ status;
124
+ constructor(status, message) {
125
+ super(message);
126
+ this.name = "ApiError";
127
+ this.status = status;
128
+ }
129
+ }
130
+ function encodePath(path) {
131
+ return path.split("/").map(encodeURIComponent).join("/");
132
+ }
133
+ function createClient(serverUrl, token) {
134
+ const base = serverUrl.replace(/\/+$/, "");
135
+ async function request(method, path, opts = {}) {
136
+ const headers = {};
137
+ if (opts.auth !== false && token) {
138
+ headers["Authorization"] = `Bearer ${token}`;
139
+ }
140
+ if (opts.contentType) {
141
+ headers["Content-Type"] = opts.contentType;
142
+ }
143
+ const res = await fetch(`${base}${path}`, {
144
+ method,
145
+ headers,
146
+ body: opts.body
147
+ });
148
+ if (!res.ok) {
149
+ let msg;
150
+ try {
151
+ const json = await res.json();
152
+ msg = json.error || res.statusText;
153
+ } catch {
154
+ msg = res.statusText;
155
+ }
156
+ throw new ApiError(res.status, msg);
157
+ }
158
+ return res;
159
+ }
160
+ return {
161
+ async signup(name, invite) {
162
+ const body = { name };
163
+ if (invite)
164
+ body.invite = invite;
165
+ const res = await request("POST", "/v1/signup", {
166
+ body: JSON.stringify(body),
167
+ contentType: "application/json",
168
+ auth: false
169
+ });
170
+ return res.json();
171
+ },
172
+ async createInvite() {
173
+ const res = await request("POST", "/v1/invites");
174
+ return res.json();
175
+ },
176
+ async listContributors() {
177
+ const res = await request("GET", "/v1/contributors");
178
+ return res.json();
179
+ },
180
+ async putFile(contributorId, path, content) {
181
+ const res = await request("PUT", `/v1/contributors/${contributorId}/files/${encodePath(path)}`, {
182
+ body: content,
183
+ contentType: "text/markdown"
184
+ });
185
+ return res.json();
186
+ },
187
+ async deleteFile(contributorId, path) {
188
+ await request("DELETE", `/v1/contributors/${contributorId}/files/${encodePath(path)}`);
189
+ },
190
+ async listFiles(contributorId, prefix) {
191
+ const qs = prefix ? `?prefix=${encodeURIComponent(prefix)}` : "";
192
+ const res = await request("GET", `/v1/contributors/${contributorId}/files${qs}`);
193
+ return res.json();
194
+ },
195
+ async getFile(contributorId, path) {
196
+ const res = await request("GET", `/v1/contributors/${contributorId}/files/${encodePath(path)}`);
197
+ return res.text();
198
+ },
199
+ async health() {
200
+ const res = await request("GET", "/health", { auth: false });
201
+ return res.json();
202
+ }
203
+ };
204
+ }
205
+
206
+ // src/commands/init.ts
207
+ async function init(args) {
208
+ const flags = parseFlags(args);
209
+ if (configExists() && !flags.force) {
210
+ console.log("Seedvault is already configured.");
211
+ console.log(" Run 'sv init --force' to overwrite.");
212
+ return;
213
+ }
214
+ if (flags.server && flags.token) {
215
+ const client = createClient(flags.server, flags.token);
216
+ try {
217
+ await client.health();
218
+ } catch {
219
+ console.error(`Could not reach server at ${flags.server}`);
220
+ process.exit(1);
221
+ }
222
+ const contributorId = flags["contributor-id"] || "";
223
+ if (!contributorId) {
224
+ console.error("When using --token, also pass --contributor-id");
225
+ process.exit(1);
226
+ }
227
+ const config = {
228
+ server: flags.server,
229
+ token: flags.token,
230
+ contributorId,
231
+ collections: []
232
+ };
233
+ saveConfig(config);
234
+ console.log("Seedvault configured.");
235
+ console.log(` Server: ${config.server}`);
236
+ console.log(` Contributor ID: ${config.contributorId}`);
237
+ return;
238
+ }
239
+ if (flags.server && flags.name) {
240
+ const client = createClient(flags.server);
241
+ try {
242
+ await client.health();
243
+ } catch {
244
+ console.error(`Could not reach server at ${flags.server}`);
245
+ process.exit(1);
246
+ }
247
+ const result = await client.signup(flags.name, flags.invite);
248
+ const config = {
249
+ server: flags.server,
250
+ token: result.token,
251
+ contributorId: result.contributor.id,
252
+ collections: []
253
+ };
254
+ saveConfig(config);
255
+ console.log("Signed up and configured.");
256
+ console.log(` Server: ${config.server}`);
257
+ console.log(` Contributor: ${result.contributor.name} (${result.contributor.id})`);
258
+ console.log(` Token: ${result.token}`);
259
+ return;
260
+ }
261
+ const rl = readline.createInterface({ input: stdin, output: stdout });
262
+ try {
263
+ console.log(`Seedvault Setup
264
+ `);
265
+ const server = await rl.question("Server URL: ");
266
+ if (!server) {
267
+ console.error("Server URL is required.");
268
+ process.exit(1);
269
+ }
270
+ const client = createClient(server);
271
+ try {
272
+ await client.health();
273
+ console.log(` Server is reachable.
274
+ `);
275
+ } catch {
276
+ console.error(` Could not reach server at ${server}`);
277
+ process.exit(1);
278
+ }
279
+ const hasToken = await rl.question("Do you already have a token? (y/N): ");
280
+ if (hasToken.toLowerCase() === "y") {
281
+ const token = await rl.question("Token: ");
282
+ const contributorId = await rl.question("Contributor ID: ");
283
+ const config = { server, token: token.trim(), contributorId: contributorId.trim(), collections: [] };
284
+ saveConfig(config);
285
+ console.log(`
286
+ Seedvault configured.`);
287
+ } else {
288
+ const name = await rl.question("Contributor name (e.g. your-name-notes): ");
289
+ const invite = await rl.question("Invite code (leave blank if first user): ");
290
+ const result = await client.signup(name.trim(), invite.trim() || undefined);
291
+ const config = {
292
+ server,
293
+ token: result.token,
294
+ contributorId: result.contributor.id,
295
+ collections: []
296
+ };
297
+ saveConfig(config);
298
+ console.log(`
299
+ Signed up as '${result.contributor.name}'.`);
300
+ console.log(` Contributor ID: ${result.contributor.id}`);
301
+ console.log(` Token: ${result.token}`);
302
+ console.log(`
303
+ Save your token \u2014 it won't be shown again.`);
304
+ }
305
+ console.log(`
306
+ Next steps:`);
307
+ console.log(" sv add ~/notes # Add a collection to sync");
308
+ console.log(" sv start # Start the daemon");
309
+ } finally {
310
+ rl.close();
311
+ }
312
+ }
313
+ function parseFlags(args) {
314
+ const flags = {};
315
+ for (let i = 0;i < args.length; i++) {
316
+ const arg = args[i];
317
+ if (arg.startsWith("--")) {
318
+ const key = arg.slice(2);
319
+ const next = args[i + 1];
320
+ if (next && !next.startsWith("--")) {
321
+ flags[key] = next;
322
+ i++;
323
+ } else {
324
+ flags[key] = "true";
325
+ }
326
+ }
327
+ }
328
+ return flags;
329
+ }
330
+
331
+ // src/commands/add.ts
332
+ import { existsSync as existsSync2 } from "fs";
333
+ import { resolve as resolve2 } from "path";
334
+ import { homedir as homedir2 } from "os";
335
+ async function add(args) {
336
+ if (args.length === 0 || args[0].startsWith("--")) {
337
+ console.error("Usage: sv add <path> [--name <name>]");
338
+ process.exit(1);
339
+ }
340
+ const rawPath = args[0];
341
+ const nameIdx = args.indexOf("--name");
342
+ const name = nameIdx !== -1 && args[nameIdx + 1] ? args[nameIdx + 1] : defaultCollectionName(rawPath);
343
+ const absPath = rawPath.startsWith("~") ? resolve2(homedir2(), rawPath.slice(2)) : resolve2(rawPath);
344
+ if (!existsSync2(absPath)) {
345
+ console.error(`Directory not found: ${absPath}`);
346
+ process.exit(1);
347
+ }
348
+ const config = loadConfig();
349
+ try {
350
+ const result = addCollection(config, absPath, name);
351
+ const updated = result.config;
352
+ saveConfig(updated);
353
+ console.log(`Added collection: ${absPath}`);
354
+ console.log(` Name: ${name}`);
355
+ console.log(` Files will sync to: ${name}/<relative-path>`);
356
+ if (result.removedChildCollections.length > 0) {
357
+ console.log(" Removed overlapping child collections:");
358
+ for (const child of result.removedChildCollections) {
359
+ console.log(` - ${child.name} (${child.path})`);
360
+ }
361
+ }
362
+ } catch (e) {
363
+ console.error(e.message);
364
+ process.exit(1);
365
+ }
366
+ }
367
+
368
+ // src/commands/remove.ts
369
+ async function remove(args) {
370
+ if (args.length === 0) {
371
+ console.error("Usage: sv remove <name>");
372
+ process.exit(1);
373
+ }
374
+ const name = args[0];
375
+ const config = loadConfig();
376
+ try {
377
+ const updated = removeCollection(config, name);
378
+ saveConfig(updated);
379
+ console.log(`Removed collection '${name}'.`);
380
+ } catch (e) {
381
+ console.error(e.message);
382
+ process.exit(1);
383
+ }
384
+ }
385
+
386
+ // src/commands/collections.ts
387
+ async function collections() {
388
+ const config = loadConfig();
389
+ if (config.collections.length === 0) {
390
+ console.log("No collections configured.");
391
+ console.log(" Run 'sv add <path>' to add one.");
392
+ return;
393
+ }
394
+ console.log(`Configured collections:
395
+ `);
396
+ for (const f of config.collections) {
397
+ console.log(` ${f.name}`);
398
+ console.log(` Path: ${f.path}`);
399
+ console.log(` Syncs: ${f.name}/<relative-path>`);
400
+ console.log();
401
+ }
402
+ }
403
+
404
+ // src/commands/start.ts
405
+ import { writeFileSync as writeFileSync2, unlinkSync } from "fs";
406
+
407
+ // ../node_modules/.bun/chokidar@4.0.3/node_modules/chokidar/esm/index.js
408
+ import { stat as statcb } from "fs";
409
+ import { stat as stat3, readdir as readdir2 } from "fs/promises";
410
+ import { EventEmitter } from "events";
411
+ import * as sysPath2 from "path";
412
+
413
+ // ../node_modules/.bun/readdirp@4.1.2/node_modules/readdirp/esm/index.js
414
+ import { stat, lstat, readdir, realpath } from "fs/promises";
415
+ import { Readable } from "stream";
416
+ import { resolve as presolve, relative as prelative, join as pjoin, sep as psep } from "path";
417
+ var EntryTypes = {
418
+ FILE_TYPE: "files",
419
+ DIR_TYPE: "directories",
420
+ FILE_DIR_TYPE: "files_directories",
421
+ EVERYTHING_TYPE: "all"
422
+ };
423
+ var defaultOptions = {
424
+ root: ".",
425
+ fileFilter: (_entryInfo) => true,
426
+ directoryFilter: (_entryInfo) => true,
427
+ type: EntryTypes.FILE_TYPE,
428
+ lstat: false,
429
+ depth: 2147483648,
430
+ alwaysStat: false,
431
+ highWaterMark: 4096
432
+ };
433
+ Object.freeze(defaultOptions);
434
+ var RECURSIVE_ERROR_CODE = "READDIRP_RECURSIVE_ERROR";
435
+ var NORMAL_FLOW_ERRORS = new Set(["ENOENT", "EPERM", "EACCES", "ELOOP", RECURSIVE_ERROR_CODE]);
436
+ var ALL_TYPES = [
437
+ EntryTypes.DIR_TYPE,
438
+ EntryTypes.EVERYTHING_TYPE,
439
+ EntryTypes.FILE_DIR_TYPE,
440
+ EntryTypes.FILE_TYPE
441
+ ];
442
+ var DIR_TYPES = new Set([
443
+ EntryTypes.DIR_TYPE,
444
+ EntryTypes.EVERYTHING_TYPE,
445
+ EntryTypes.FILE_DIR_TYPE
446
+ ]);
447
+ var FILE_TYPES = new Set([
448
+ EntryTypes.EVERYTHING_TYPE,
449
+ EntryTypes.FILE_DIR_TYPE,
450
+ EntryTypes.FILE_TYPE
451
+ ]);
452
+ var isNormalFlowError = (error) => NORMAL_FLOW_ERRORS.has(error.code);
453
+ var wantBigintFsStats = process.platform === "win32";
454
+ var emptyFn = (_entryInfo) => true;
455
+ var normalizeFilter = (filter) => {
456
+ if (filter === undefined)
457
+ return emptyFn;
458
+ if (typeof filter === "function")
459
+ return filter;
460
+ if (typeof filter === "string") {
461
+ const fl = filter.trim();
462
+ return (entry) => entry.basename === fl;
463
+ }
464
+ if (Array.isArray(filter)) {
465
+ const trItems = filter.map((item) => item.trim());
466
+ return (entry) => trItems.some((f) => entry.basename === f);
467
+ }
468
+ return emptyFn;
469
+ };
470
+
471
+ class ReaddirpStream extends Readable {
472
+ constructor(options = {}) {
473
+ super({
474
+ objectMode: true,
475
+ autoDestroy: true,
476
+ highWaterMark: options.highWaterMark
477
+ });
478
+ const opts = { ...defaultOptions, ...options };
479
+ const { root, type } = opts;
480
+ this._fileFilter = normalizeFilter(opts.fileFilter);
481
+ this._directoryFilter = normalizeFilter(opts.directoryFilter);
482
+ const statMethod = opts.lstat ? lstat : stat;
483
+ if (wantBigintFsStats) {
484
+ this._stat = (path) => statMethod(path, { bigint: true });
485
+ } else {
486
+ this._stat = statMethod;
487
+ }
488
+ this._maxDepth = opts.depth ?? defaultOptions.depth;
489
+ this._wantsDir = type ? DIR_TYPES.has(type) : false;
490
+ this._wantsFile = type ? FILE_TYPES.has(type) : false;
491
+ this._wantsEverything = type === EntryTypes.EVERYTHING_TYPE;
492
+ this._root = presolve(root);
493
+ this._isDirent = !opts.alwaysStat;
494
+ this._statsProp = this._isDirent ? "dirent" : "stats";
495
+ this._rdOptions = { encoding: "utf8", withFileTypes: this._isDirent };
496
+ this.parents = [this._exploreDir(root, 1)];
497
+ this.reading = false;
498
+ this.parent = undefined;
499
+ }
500
+ async _read(batch) {
501
+ if (this.reading)
502
+ return;
503
+ this.reading = true;
504
+ try {
505
+ while (!this.destroyed && batch > 0) {
506
+ const par = this.parent;
507
+ const fil = par && par.files;
508
+ if (fil && fil.length > 0) {
509
+ const { path, depth } = par;
510
+ const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path));
511
+ const awaited = await Promise.all(slice);
512
+ for (const entry of awaited) {
513
+ if (!entry)
514
+ continue;
515
+ if (this.destroyed)
516
+ return;
517
+ const entryType = await this._getEntryType(entry);
518
+ if (entryType === "directory" && this._directoryFilter(entry)) {
519
+ if (depth <= this._maxDepth) {
520
+ this.parents.push(this._exploreDir(entry.fullPath, depth + 1));
521
+ }
522
+ if (this._wantsDir) {
523
+ this.push(entry);
524
+ batch--;
525
+ }
526
+ } else if ((entryType === "file" || this._includeAsFile(entry)) && this._fileFilter(entry)) {
527
+ if (this._wantsFile) {
528
+ this.push(entry);
529
+ batch--;
530
+ }
531
+ }
532
+ }
533
+ } else {
534
+ const parent = this.parents.pop();
535
+ if (!parent) {
536
+ this.push(null);
537
+ break;
538
+ }
539
+ this.parent = await parent;
540
+ if (this.destroyed)
541
+ return;
542
+ }
543
+ }
544
+ } catch (error) {
545
+ this.destroy(error);
546
+ } finally {
547
+ this.reading = false;
548
+ }
549
+ }
550
+ async _exploreDir(path, depth) {
551
+ let files;
552
+ try {
553
+ files = await readdir(path, this._rdOptions);
554
+ } catch (error) {
555
+ this._onError(error);
556
+ }
557
+ return { files, depth, path };
558
+ }
559
+ async _formatEntry(dirent, path) {
560
+ let entry;
561
+ const basename = this._isDirent ? dirent.name : dirent;
562
+ try {
563
+ const fullPath = presolve(pjoin(path, basename));
564
+ entry = { path: prelative(this._root, fullPath), fullPath, basename };
565
+ entry[this._statsProp] = this._isDirent ? dirent : await this._stat(fullPath);
566
+ } catch (err) {
567
+ this._onError(err);
568
+ return;
569
+ }
570
+ return entry;
571
+ }
572
+ _onError(err) {
573
+ if (isNormalFlowError(err) && !this.destroyed) {
574
+ this.emit("warn", err);
575
+ } else {
576
+ this.destroy(err);
577
+ }
578
+ }
579
+ async _getEntryType(entry) {
580
+ if (!entry && this._statsProp in entry) {
581
+ return "";
582
+ }
583
+ const stats = entry[this._statsProp];
584
+ if (stats.isFile())
585
+ return "file";
586
+ if (stats.isDirectory())
587
+ return "directory";
588
+ if (stats && stats.isSymbolicLink()) {
589
+ const full = entry.fullPath;
590
+ try {
591
+ const entryRealPath = await realpath(full);
592
+ const entryRealPathStats = await lstat(entryRealPath);
593
+ if (entryRealPathStats.isFile()) {
594
+ return "file";
595
+ }
596
+ if (entryRealPathStats.isDirectory()) {
597
+ const len = entryRealPath.length;
598
+ if (full.startsWith(entryRealPath) && full.substr(len, 1) === psep) {
599
+ const recursiveError = new Error(`Circular symlink detected: "${full}" points to "${entryRealPath}"`);
600
+ recursiveError.code = RECURSIVE_ERROR_CODE;
601
+ return this._onError(recursiveError);
602
+ }
603
+ return "directory";
604
+ }
605
+ } catch (error) {
606
+ this._onError(error);
607
+ return "";
608
+ }
609
+ }
610
+ }
611
+ _includeAsFile(entry) {
612
+ const stats = entry && entry[this._statsProp];
613
+ return stats && this._wantsEverything && !stats.isDirectory();
614
+ }
615
+ }
616
+ function readdirp(root, options = {}) {
617
+ let type = options.entryType || options.type;
618
+ if (type === "both")
619
+ type = EntryTypes.FILE_DIR_TYPE;
620
+ if (type)
621
+ options.type = type;
622
+ if (!root) {
623
+ throw new Error("readdirp: root argument is required. Usage: readdirp(root, options)");
624
+ } else if (typeof root !== "string") {
625
+ throw new TypeError("readdirp: root argument must be a string. Usage: readdirp(root, options)");
626
+ } else if (type && !ALL_TYPES.includes(type)) {
627
+ throw new Error(`readdirp: Invalid type passed. Use one of ${ALL_TYPES.join(", ")}`);
628
+ }
629
+ options.root = root;
630
+ return new ReaddirpStream(options);
631
+ }
632
+
633
+ // ../node_modules/.bun/chokidar@4.0.3/node_modules/chokidar/esm/handler.js
634
+ import { watchFile, unwatchFile, watch as fs_watch } from "fs";
635
+ import { open, stat as stat2, lstat as lstat2, realpath as fsrealpath } from "fs/promises";
636
+ import * as sysPath from "path";
637
+ import { type as osType } from "os";
638
+ var STR_DATA = "data";
639
+ var STR_END = "end";
640
+ var STR_CLOSE = "close";
641
+ var EMPTY_FN = () => {};
642
+ var pl = process.platform;
643
+ var isWindows = pl === "win32";
644
+ var isMacos = pl === "darwin";
645
+ var isLinux = pl === "linux";
646
+ var isFreeBSD = pl === "freebsd";
647
+ var isIBMi = osType() === "OS400";
648
+ var EVENTS = {
649
+ ALL: "all",
650
+ READY: "ready",
651
+ ADD: "add",
652
+ CHANGE: "change",
653
+ ADD_DIR: "addDir",
654
+ UNLINK: "unlink",
655
+ UNLINK_DIR: "unlinkDir",
656
+ RAW: "raw",
657
+ ERROR: "error"
658
+ };
659
+ var EV = EVENTS;
660
+ var THROTTLE_MODE_WATCH = "watch";
661
+ var statMethods = { lstat: lstat2, stat: stat2 };
662
+ var KEY_LISTENERS = "listeners";
663
+ var KEY_ERR = "errHandlers";
664
+ var KEY_RAW = "rawEmitters";
665
+ var HANDLER_KEYS = [KEY_LISTENERS, KEY_ERR, KEY_RAW];
666
+ var binaryExtensions = new Set([
667
+ "3dm",
668
+ "3ds",
669
+ "3g2",
670
+ "3gp",
671
+ "7z",
672
+ "a",
673
+ "aac",
674
+ "adp",
675
+ "afdesign",
676
+ "afphoto",
677
+ "afpub",
678
+ "ai",
679
+ "aif",
680
+ "aiff",
681
+ "alz",
682
+ "ape",
683
+ "apk",
684
+ "appimage",
685
+ "ar",
686
+ "arj",
687
+ "asf",
688
+ "au",
689
+ "avi",
690
+ "bak",
691
+ "baml",
692
+ "bh",
693
+ "bin",
694
+ "bk",
695
+ "bmp",
696
+ "btif",
697
+ "bz2",
698
+ "bzip2",
699
+ "cab",
700
+ "caf",
701
+ "cgm",
702
+ "class",
703
+ "cmx",
704
+ "cpio",
705
+ "cr2",
706
+ "cur",
707
+ "dat",
708
+ "dcm",
709
+ "deb",
710
+ "dex",
711
+ "djvu",
712
+ "dll",
713
+ "dmg",
714
+ "dng",
715
+ "doc",
716
+ "docm",
717
+ "docx",
718
+ "dot",
719
+ "dotm",
720
+ "dra",
721
+ "DS_Store",
722
+ "dsk",
723
+ "dts",
724
+ "dtshd",
725
+ "dvb",
726
+ "dwg",
727
+ "dxf",
728
+ "ecelp4800",
729
+ "ecelp7470",
730
+ "ecelp9600",
731
+ "egg",
732
+ "eol",
733
+ "eot",
734
+ "epub",
735
+ "exe",
736
+ "f4v",
737
+ "fbs",
738
+ "fh",
739
+ "fla",
740
+ "flac",
741
+ "flatpak",
742
+ "fli",
743
+ "flv",
744
+ "fpx",
745
+ "fst",
746
+ "fvt",
747
+ "g3",
748
+ "gh",
749
+ "gif",
750
+ "graffle",
751
+ "gz",
752
+ "gzip",
753
+ "h261",
754
+ "h263",
755
+ "h264",
756
+ "icns",
757
+ "ico",
758
+ "ief",
759
+ "img",
760
+ "ipa",
761
+ "iso",
762
+ "jar",
763
+ "jpeg",
764
+ "jpg",
765
+ "jpgv",
766
+ "jpm",
767
+ "jxr",
768
+ "key",
769
+ "ktx",
770
+ "lha",
771
+ "lib",
772
+ "lvp",
773
+ "lz",
774
+ "lzh",
775
+ "lzma",
776
+ "lzo",
777
+ "m3u",
778
+ "m4a",
779
+ "m4v",
780
+ "mar",
781
+ "mdi",
782
+ "mht",
783
+ "mid",
784
+ "midi",
785
+ "mj2",
786
+ "mka",
787
+ "mkv",
788
+ "mmr",
789
+ "mng",
790
+ "mobi",
791
+ "mov",
792
+ "movie",
793
+ "mp3",
794
+ "mp4",
795
+ "mp4a",
796
+ "mpeg",
797
+ "mpg",
798
+ "mpga",
799
+ "mxu",
800
+ "nef",
801
+ "npx",
802
+ "numbers",
803
+ "nupkg",
804
+ "o",
805
+ "odp",
806
+ "ods",
807
+ "odt",
808
+ "oga",
809
+ "ogg",
810
+ "ogv",
811
+ "otf",
812
+ "ott",
813
+ "pages",
814
+ "pbm",
815
+ "pcx",
816
+ "pdb",
817
+ "pdf",
818
+ "pea",
819
+ "pgm",
820
+ "pic",
821
+ "png",
822
+ "pnm",
823
+ "pot",
824
+ "potm",
825
+ "potx",
826
+ "ppa",
827
+ "ppam",
828
+ "ppm",
829
+ "pps",
830
+ "ppsm",
831
+ "ppsx",
832
+ "ppt",
833
+ "pptm",
834
+ "pptx",
835
+ "psd",
836
+ "pya",
837
+ "pyc",
838
+ "pyo",
839
+ "pyv",
840
+ "qt",
841
+ "rar",
842
+ "ras",
843
+ "raw",
844
+ "resources",
845
+ "rgb",
846
+ "rip",
847
+ "rlc",
848
+ "rmf",
849
+ "rmvb",
850
+ "rpm",
851
+ "rtf",
852
+ "rz",
853
+ "s3m",
854
+ "s7z",
855
+ "scpt",
856
+ "sgi",
857
+ "shar",
858
+ "snap",
859
+ "sil",
860
+ "sketch",
861
+ "slk",
862
+ "smv",
863
+ "snk",
864
+ "so",
865
+ "stl",
866
+ "suo",
867
+ "sub",
868
+ "swf",
869
+ "tar",
870
+ "tbz",
871
+ "tbz2",
872
+ "tga",
873
+ "tgz",
874
+ "thmx",
875
+ "tif",
876
+ "tiff",
877
+ "tlz",
878
+ "ttc",
879
+ "ttf",
880
+ "txz",
881
+ "udf",
882
+ "uvh",
883
+ "uvi",
884
+ "uvm",
885
+ "uvp",
886
+ "uvs",
887
+ "uvu",
888
+ "viv",
889
+ "vob",
890
+ "war",
891
+ "wav",
892
+ "wax",
893
+ "wbmp",
894
+ "wdp",
895
+ "weba",
896
+ "webm",
897
+ "webp",
898
+ "whl",
899
+ "wim",
900
+ "wm",
901
+ "wma",
902
+ "wmv",
903
+ "wmx",
904
+ "woff",
905
+ "woff2",
906
+ "wrm",
907
+ "wvx",
908
+ "xbm",
909
+ "xif",
910
+ "xla",
911
+ "xlam",
912
+ "xls",
913
+ "xlsb",
914
+ "xlsm",
915
+ "xlsx",
916
+ "xlt",
917
+ "xltm",
918
+ "xltx",
919
+ "xm",
920
+ "xmind",
921
+ "xpi",
922
+ "xpm",
923
+ "xwd",
924
+ "xz",
925
+ "z",
926
+ "zip",
927
+ "zipx"
928
+ ]);
929
+ var isBinaryPath = (filePath) => binaryExtensions.has(sysPath.extname(filePath).slice(1).toLowerCase());
930
+ var foreach = (val, fn) => {
931
+ if (val instanceof Set) {
932
+ val.forEach(fn);
933
+ } else {
934
+ fn(val);
935
+ }
936
+ };
937
+ var addAndConvert = (main, prop, item) => {
938
+ let container = main[prop];
939
+ if (!(container instanceof Set)) {
940
+ main[prop] = container = new Set([container]);
941
+ }
942
+ container.add(item);
943
+ };
944
+ var clearItem = (cont) => (key) => {
945
+ const set = cont[key];
946
+ if (set instanceof Set) {
947
+ set.clear();
948
+ } else {
949
+ delete cont[key];
950
+ }
951
+ };
952
+ var delFromSet = (main, prop, item) => {
953
+ const container = main[prop];
954
+ if (container instanceof Set) {
955
+ container.delete(item);
956
+ } else if (container === item) {
957
+ delete main[prop];
958
+ }
959
+ };
960
+ var isEmptySet = (val) => val instanceof Set ? val.size === 0 : !val;
961
+ var FsWatchInstances = new Map;
962
+ function createFsWatchInstance(path, options, listener, errHandler, emitRaw) {
963
+ const handleEvent = (rawEvent, evPath) => {
964
+ listener(path);
965
+ emitRaw(rawEvent, evPath, { watchedPath: path });
966
+ if (evPath && path !== evPath) {
967
+ fsWatchBroadcast(sysPath.resolve(path, evPath), KEY_LISTENERS, sysPath.join(path, evPath));
968
+ }
969
+ };
970
+ try {
971
+ return fs_watch(path, {
972
+ persistent: options.persistent
973
+ }, handleEvent);
974
+ } catch (error) {
975
+ errHandler(error);
976
+ return;
977
+ }
978
+ }
979
+ var fsWatchBroadcast = (fullPath, listenerType, val1, val2, val3) => {
980
+ const cont = FsWatchInstances.get(fullPath);
981
+ if (!cont)
982
+ return;
983
+ foreach(cont[listenerType], (listener) => {
984
+ listener(val1, val2, val3);
985
+ });
986
+ };
987
+ var setFsWatchListener = (path, fullPath, options, handlers) => {
988
+ const { listener, errHandler, rawEmitter } = handlers;
989
+ let cont = FsWatchInstances.get(fullPath);
990
+ let watcher;
991
+ if (!options.persistent) {
992
+ watcher = createFsWatchInstance(path, options, listener, errHandler, rawEmitter);
993
+ if (!watcher)
994
+ return;
995
+ return watcher.close.bind(watcher);
996
+ }
997
+ if (cont) {
998
+ addAndConvert(cont, KEY_LISTENERS, listener);
999
+ addAndConvert(cont, KEY_ERR, errHandler);
1000
+ addAndConvert(cont, KEY_RAW, rawEmitter);
1001
+ } else {
1002
+ watcher = createFsWatchInstance(path, options, fsWatchBroadcast.bind(null, fullPath, KEY_LISTENERS), errHandler, fsWatchBroadcast.bind(null, fullPath, KEY_RAW));
1003
+ if (!watcher)
1004
+ return;
1005
+ watcher.on(EV.ERROR, async (error) => {
1006
+ const broadcastErr = fsWatchBroadcast.bind(null, fullPath, KEY_ERR);
1007
+ if (cont)
1008
+ cont.watcherUnusable = true;
1009
+ if (isWindows && error.code === "EPERM") {
1010
+ try {
1011
+ const fd = await open(path, "r");
1012
+ await fd.close();
1013
+ broadcastErr(error);
1014
+ } catch (err) {}
1015
+ } else {
1016
+ broadcastErr(error);
1017
+ }
1018
+ });
1019
+ cont = {
1020
+ listeners: listener,
1021
+ errHandlers: errHandler,
1022
+ rawEmitters: rawEmitter,
1023
+ watcher
1024
+ };
1025
+ FsWatchInstances.set(fullPath, cont);
1026
+ }
1027
+ return () => {
1028
+ delFromSet(cont, KEY_LISTENERS, listener);
1029
+ delFromSet(cont, KEY_ERR, errHandler);
1030
+ delFromSet(cont, KEY_RAW, rawEmitter);
1031
+ if (isEmptySet(cont.listeners)) {
1032
+ cont.watcher.close();
1033
+ FsWatchInstances.delete(fullPath);
1034
+ HANDLER_KEYS.forEach(clearItem(cont));
1035
+ cont.watcher = undefined;
1036
+ Object.freeze(cont);
1037
+ }
1038
+ };
1039
+ };
1040
+ var FsWatchFileInstances = new Map;
1041
+ var setFsWatchFileListener = (path, fullPath, options, handlers) => {
1042
+ const { listener, rawEmitter } = handlers;
1043
+ let cont = FsWatchFileInstances.get(fullPath);
1044
+ const copts = cont && cont.options;
1045
+ if (copts && (copts.persistent < options.persistent || copts.interval > options.interval)) {
1046
+ unwatchFile(fullPath);
1047
+ cont = undefined;
1048
+ }
1049
+ if (cont) {
1050
+ addAndConvert(cont, KEY_LISTENERS, listener);
1051
+ addAndConvert(cont, KEY_RAW, rawEmitter);
1052
+ } else {
1053
+ cont = {
1054
+ listeners: listener,
1055
+ rawEmitters: rawEmitter,
1056
+ options,
1057
+ watcher: watchFile(fullPath, options, (curr, prev) => {
1058
+ foreach(cont.rawEmitters, (rawEmitter2) => {
1059
+ rawEmitter2(EV.CHANGE, fullPath, { curr, prev });
1060
+ });
1061
+ const currmtime = curr.mtimeMs;
1062
+ if (curr.size !== prev.size || currmtime > prev.mtimeMs || currmtime === 0) {
1063
+ foreach(cont.listeners, (listener2) => listener2(path, curr));
1064
+ }
1065
+ })
1066
+ };
1067
+ FsWatchFileInstances.set(fullPath, cont);
1068
+ }
1069
+ return () => {
1070
+ delFromSet(cont, KEY_LISTENERS, listener);
1071
+ delFromSet(cont, KEY_RAW, rawEmitter);
1072
+ if (isEmptySet(cont.listeners)) {
1073
+ FsWatchFileInstances.delete(fullPath);
1074
+ unwatchFile(fullPath);
1075
+ cont.options = cont.watcher = undefined;
1076
+ Object.freeze(cont);
1077
+ }
1078
+ };
1079
+ };
1080
+
1081
+ class NodeFsHandler {
1082
+ constructor(fsW) {
1083
+ this.fsw = fsW;
1084
+ this._boundHandleError = (error) => fsW._handleError(error);
1085
+ }
1086
+ _watchWithNodeFs(path, listener) {
1087
+ const opts = this.fsw.options;
1088
+ const directory = sysPath.dirname(path);
1089
+ const basename2 = sysPath.basename(path);
1090
+ const parent = this.fsw._getWatchedDir(directory);
1091
+ parent.add(basename2);
1092
+ const absolutePath = sysPath.resolve(path);
1093
+ const options = {
1094
+ persistent: opts.persistent
1095
+ };
1096
+ if (!listener)
1097
+ listener = EMPTY_FN;
1098
+ let closer;
1099
+ if (opts.usePolling) {
1100
+ const enableBin = opts.interval !== opts.binaryInterval;
1101
+ options.interval = enableBin && isBinaryPath(basename2) ? opts.binaryInterval : opts.interval;
1102
+ closer = setFsWatchFileListener(path, absolutePath, options, {
1103
+ listener,
1104
+ rawEmitter: this.fsw._emitRaw
1105
+ });
1106
+ } else {
1107
+ closer = setFsWatchListener(path, absolutePath, options, {
1108
+ listener,
1109
+ errHandler: this._boundHandleError,
1110
+ rawEmitter: this.fsw._emitRaw
1111
+ });
1112
+ }
1113
+ return closer;
1114
+ }
1115
+ _handleFile(file, stats, initialAdd) {
1116
+ if (this.fsw.closed) {
1117
+ return;
1118
+ }
1119
+ const dirname2 = sysPath.dirname(file);
1120
+ const basename2 = sysPath.basename(file);
1121
+ const parent = this.fsw._getWatchedDir(dirname2);
1122
+ let prevStats = stats;
1123
+ if (parent.has(basename2))
1124
+ return;
1125
+ const listener = async (path, newStats) => {
1126
+ if (!this.fsw._throttle(THROTTLE_MODE_WATCH, file, 5))
1127
+ return;
1128
+ if (!newStats || newStats.mtimeMs === 0) {
1129
+ try {
1130
+ const newStats2 = await stat2(file);
1131
+ if (this.fsw.closed)
1132
+ return;
1133
+ const at = newStats2.atimeMs;
1134
+ const mt = newStats2.mtimeMs;
1135
+ if (!at || at <= mt || mt !== prevStats.mtimeMs) {
1136
+ this.fsw._emit(EV.CHANGE, file, newStats2);
1137
+ }
1138
+ if ((isMacos || isLinux || isFreeBSD) && prevStats.ino !== newStats2.ino) {
1139
+ this.fsw._closeFile(path);
1140
+ prevStats = newStats2;
1141
+ const closer2 = this._watchWithNodeFs(file, listener);
1142
+ if (closer2)
1143
+ this.fsw._addPathCloser(path, closer2);
1144
+ } else {
1145
+ prevStats = newStats2;
1146
+ }
1147
+ } catch (error) {
1148
+ this.fsw._remove(dirname2, basename2);
1149
+ }
1150
+ } else if (parent.has(basename2)) {
1151
+ const at = newStats.atimeMs;
1152
+ const mt = newStats.mtimeMs;
1153
+ if (!at || at <= mt || mt !== prevStats.mtimeMs) {
1154
+ this.fsw._emit(EV.CHANGE, file, newStats);
1155
+ }
1156
+ prevStats = newStats;
1157
+ }
1158
+ };
1159
+ const closer = this._watchWithNodeFs(file, listener);
1160
+ if (!(initialAdd && this.fsw.options.ignoreInitial) && this.fsw._isntIgnored(file)) {
1161
+ if (!this.fsw._throttle(EV.ADD, file, 0))
1162
+ return;
1163
+ this.fsw._emit(EV.ADD, file, stats);
1164
+ }
1165
+ return closer;
1166
+ }
1167
+ async _handleSymlink(entry, directory, path, item) {
1168
+ if (this.fsw.closed) {
1169
+ return;
1170
+ }
1171
+ const full = entry.fullPath;
1172
+ const dir = this.fsw._getWatchedDir(directory);
1173
+ if (!this.fsw.options.followSymlinks) {
1174
+ this.fsw._incrReadyCount();
1175
+ let linkPath;
1176
+ try {
1177
+ linkPath = await fsrealpath(path);
1178
+ } catch (e) {
1179
+ this.fsw._emitReady();
1180
+ return true;
1181
+ }
1182
+ if (this.fsw.closed)
1183
+ return;
1184
+ if (dir.has(item)) {
1185
+ if (this.fsw._symlinkPaths.get(full) !== linkPath) {
1186
+ this.fsw._symlinkPaths.set(full, linkPath);
1187
+ this.fsw._emit(EV.CHANGE, path, entry.stats);
1188
+ }
1189
+ } else {
1190
+ dir.add(item);
1191
+ this.fsw._symlinkPaths.set(full, linkPath);
1192
+ this.fsw._emit(EV.ADD, path, entry.stats);
1193
+ }
1194
+ this.fsw._emitReady();
1195
+ return true;
1196
+ }
1197
+ if (this.fsw._symlinkPaths.has(full)) {
1198
+ return true;
1199
+ }
1200
+ this.fsw._symlinkPaths.set(full, true);
1201
+ }
1202
+ _handleRead(directory, initialAdd, wh, target, dir, depth, throttler) {
1203
+ directory = sysPath.join(directory, "");
1204
+ throttler = this.fsw._throttle("readdir", directory, 1000);
1205
+ if (!throttler)
1206
+ return;
1207
+ const previous = this.fsw._getWatchedDir(wh.path);
1208
+ const current = new Set;
1209
+ let stream = this.fsw._readdirp(directory, {
1210
+ fileFilter: (entry) => wh.filterPath(entry),
1211
+ directoryFilter: (entry) => wh.filterDir(entry)
1212
+ });
1213
+ if (!stream)
1214
+ return;
1215
+ stream.on(STR_DATA, async (entry) => {
1216
+ if (this.fsw.closed) {
1217
+ stream = undefined;
1218
+ return;
1219
+ }
1220
+ const item = entry.path;
1221
+ let path = sysPath.join(directory, item);
1222
+ current.add(item);
1223
+ if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path, item)) {
1224
+ return;
1225
+ }
1226
+ if (this.fsw.closed) {
1227
+ stream = undefined;
1228
+ return;
1229
+ }
1230
+ if (item === target || !target && !previous.has(item)) {
1231
+ this.fsw._incrReadyCount();
1232
+ path = sysPath.join(dir, sysPath.relative(dir, path));
1233
+ this._addToNodeFs(path, initialAdd, wh, depth + 1);
1234
+ }
1235
+ }).on(EV.ERROR, this._boundHandleError);
1236
+ return new Promise((resolve4, reject) => {
1237
+ if (!stream)
1238
+ return reject();
1239
+ stream.once(STR_END, () => {
1240
+ if (this.fsw.closed) {
1241
+ stream = undefined;
1242
+ return;
1243
+ }
1244
+ const wasThrottled = throttler ? throttler.clear() : false;
1245
+ resolve4(undefined);
1246
+ previous.getChildren().filter((item) => {
1247
+ return item !== directory && !current.has(item);
1248
+ }).forEach((item) => {
1249
+ this.fsw._remove(directory, item);
1250
+ });
1251
+ stream = undefined;
1252
+ if (wasThrottled)
1253
+ this._handleRead(directory, false, wh, target, dir, depth, throttler);
1254
+ });
1255
+ });
1256
+ }
1257
+ async _handleDir(dir, stats, initialAdd, depth, target, wh, realpath2) {
1258
+ const parentDir = this.fsw._getWatchedDir(sysPath.dirname(dir));
1259
+ const tracked = parentDir.has(sysPath.basename(dir));
1260
+ if (!(initialAdd && this.fsw.options.ignoreInitial) && !target && !tracked) {
1261
+ this.fsw._emit(EV.ADD_DIR, dir, stats);
1262
+ }
1263
+ parentDir.add(sysPath.basename(dir));
1264
+ this.fsw._getWatchedDir(dir);
1265
+ let throttler;
1266
+ let closer;
1267
+ const oDepth = this.fsw.options.depth;
1268
+ if ((oDepth == null || depth <= oDepth) && !this.fsw._symlinkPaths.has(realpath2)) {
1269
+ if (!target) {
1270
+ await this._handleRead(dir, initialAdd, wh, target, dir, depth, throttler);
1271
+ if (this.fsw.closed)
1272
+ return;
1273
+ }
1274
+ closer = this._watchWithNodeFs(dir, (dirPath, stats2) => {
1275
+ if (stats2 && stats2.mtimeMs === 0)
1276
+ return;
1277
+ this._handleRead(dirPath, false, wh, target, dir, depth, throttler);
1278
+ });
1279
+ }
1280
+ return closer;
1281
+ }
1282
+ async _addToNodeFs(path, initialAdd, priorWh, depth, target) {
1283
+ const ready = this.fsw._emitReady;
1284
+ if (this.fsw._isIgnored(path) || this.fsw.closed) {
1285
+ ready();
1286
+ return false;
1287
+ }
1288
+ const wh = this.fsw._getWatchHelpers(path);
1289
+ if (priorWh) {
1290
+ wh.filterPath = (entry) => priorWh.filterPath(entry);
1291
+ wh.filterDir = (entry) => priorWh.filterDir(entry);
1292
+ }
1293
+ try {
1294
+ const stats = await statMethods[wh.statMethod](wh.watchPath);
1295
+ if (this.fsw.closed)
1296
+ return;
1297
+ if (this.fsw._isIgnored(wh.watchPath, stats)) {
1298
+ ready();
1299
+ return false;
1300
+ }
1301
+ const follow = this.fsw.options.followSymlinks;
1302
+ let closer;
1303
+ if (stats.isDirectory()) {
1304
+ const absPath = sysPath.resolve(path);
1305
+ const targetPath = follow ? await fsrealpath(path) : path;
1306
+ if (this.fsw.closed)
1307
+ return;
1308
+ closer = await this._handleDir(wh.watchPath, stats, initialAdd, depth, target, wh, targetPath);
1309
+ if (this.fsw.closed)
1310
+ return;
1311
+ if (absPath !== targetPath && targetPath !== undefined) {
1312
+ this.fsw._symlinkPaths.set(absPath, targetPath);
1313
+ }
1314
+ } else if (stats.isSymbolicLink()) {
1315
+ const targetPath = follow ? await fsrealpath(path) : path;
1316
+ if (this.fsw.closed)
1317
+ return;
1318
+ const parent = sysPath.dirname(wh.watchPath);
1319
+ this.fsw._getWatchedDir(parent).add(wh.watchPath);
1320
+ this.fsw._emit(EV.ADD, wh.watchPath, stats);
1321
+ closer = await this._handleDir(parent, stats, initialAdd, depth, path, wh, targetPath);
1322
+ if (this.fsw.closed)
1323
+ return;
1324
+ if (targetPath !== undefined) {
1325
+ this.fsw._symlinkPaths.set(sysPath.resolve(path), targetPath);
1326
+ }
1327
+ } else {
1328
+ closer = this._handleFile(wh.watchPath, stats, initialAdd);
1329
+ }
1330
+ ready();
1331
+ if (closer)
1332
+ this.fsw._addPathCloser(path, closer);
1333
+ return false;
1334
+ } catch (error) {
1335
+ if (this.fsw._handleError(error)) {
1336
+ ready();
1337
+ return path;
1338
+ }
1339
+ }
1340
+ }
1341
+ }
1342
+
1343
+ // ../node_modules/.bun/chokidar@4.0.3/node_modules/chokidar/esm/index.js
1344
+ /*! chokidar - MIT License (c) 2012 Paul Miller (paulmillr.com) */
1345
+ var SLASH = "/";
1346
+ var SLASH_SLASH = "//";
1347
+ var ONE_DOT = ".";
1348
+ var TWO_DOTS = "..";
1349
+ var STRING_TYPE = "string";
1350
+ var BACK_SLASH_RE = /\\/g;
1351
+ var DOUBLE_SLASH_RE = /\/\//;
1352
+ var DOT_RE = /\..*\.(sw[px])$|~$|\.subl.*\.tmp/;
1353
+ var REPLACER_RE = /^\.[/\\]/;
1354
+ function arrify(item) {
1355
+ return Array.isArray(item) ? item : [item];
1356
+ }
1357
+ var isMatcherObject = (matcher) => typeof matcher === "object" && matcher !== null && !(matcher instanceof RegExp);
1358
+ function createPattern(matcher) {
1359
+ if (typeof matcher === "function")
1360
+ return matcher;
1361
+ if (typeof matcher === "string")
1362
+ return (string) => matcher === string;
1363
+ if (matcher instanceof RegExp)
1364
+ return (string) => matcher.test(string);
1365
+ if (typeof matcher === "object" && matcher !== null) {
1366
+ return (string) => {
1367
+ if (matcher.path === string)
1368
+ return true;
1369
+ if (matcher.recursive) {
1370
+ const relative4 = sysPath2.relative(matcher.path, string);
1371
+ if (!relative4) {
1372
+ return false;
1373
+ }
1374
+ return !relative4.startsWith("..") && !sysPath2.isAbsolute(relative4);
1375
+ }
1376
+ return false;
1377
+ };
1378
+ }
1379
+ return () => false;
1380
+ }
1381
+ function normalizePath(path) {
1382
+ if (typeof path !== "string")
1383
+ throw new Error("string expected");
1384
+ path = sysPath2.normalize(path);
1385
+ path = path.replace(/\\/g, "/");
1386
+ let prepend = false;
1387
+ if (path.startsWith("//"))
1388
+ prepend = true;
1389
+ const DOUBLE_SLASH_RE2 = /\/\//;
1390
+ while (path.match(DOUBLE_SLASH_RE2))
1391
+ path = path.replace(DOUBLE_SLASH_RE2, "/");
1392
+ if (prepend)
1393
+ path = "/" + path;
1394
+ return path;
1395
+ }
1396
+ function matchPatterns(patterns, testString, stats) {
1397
+ const path = normalizePath(testString);
1398
+ for (let index = 0;index < patterns.length; index++) {
1399
+ const pattern = patterns[index];
1400
+ if (pattern(path, stats)) {
1401
+ return true;
1402
+ }
1403
+ }
1404
+ return false;
1405
+ }
1406
+ function anymatch(matchers, testString) {
1407
+ if (matchers == null) {
1408
+ throw new TypeError("anymatch: specify first argument");
1409
+ }
1410
+ const matchersArray = arrify(matchers);
1411
+ const patterns = matchersArray.map((matcher) => createPattern(matcher));
1412
+ if (testString == null) {
1413
+ return (testString2, stats) => {
1414
+ return matchPatterns(patterns, testString2, stats);
1415
+ };
1416
+ }
1417
+ return matchPatterns(patterns, testString);
1418
+ }
1419
+ var unifyPaths = (paths_) => {
1420
+ const paths = arrify(paths_).flat();
1421
+ if (!paths.every((p) => typeof p === STRING_TYPE)) {
1422
+ throw new TypeError(`Non-string provided as watch path: ${paths}`);
1423
+ }
1424
+ return paths.map(normalizePathToUnix);
1425
+ };
1426
+ var toUnix = (string) => {
1427
+ let str = string.replace(BACK_SLASH_RE, SLASH);
1428
+ let prepend = false;
1429
+ if (str.startsWith(SLASH_SLASH)) {
1430
+ prepend = true;
1431
+ }
1432
+ while (str.match(DOUBLE_SLASH_RE)) {
1433
+ str = str.replace(DOUBLE_SLASH_RE, SLASH);
1434
+ }
1435
+ if (prepend) {
1436
+ str = SLASH + str;
1437
+ }
1438
+ return str;
1439
+ };
1440
+ var normalizePathToUnix = (path) => toUnix(sysPath2.normalize(toUnix(path)));
1441
+ var normalizeIgnored = (cwd = "") => (path) => {
1442
+ if (typeof path === "string") {
1443
+ return normalizePathToUnix(sysPath2.isAbsolute(path) ? path : sysPath2.join(cwd, path));
1444
+ } else {
1445
+ return path;
1446
+ }
1447
+ };
1448
+ var getAbsolutePath = (path, cwd) => {
1449
+ if (sysPath2.isAbsolute(path)) {
1450
+ return path;
1451
+ }
1452
+ return sysPath2.join(cwd, path);
1453
+ };
1454
+ var EMPTY_SET = Object.freeze(new Set);
1455
+
1456
+ class DirEntry {
1457
+ constructor(dir, removeWatcher) {
1458
+ this.path = dir;
1459
+ this._removeWatcher = removeWatcher;
1460
+ this.items = new Set;
1461
+ }
1462
+ add(item) {
1463
+ const { items } = this;
1464
+ if (!items)
1465
+ return;
1466
+ if (item !== ONE_DOT && item !== TWO_DOTS)
1467
+ items.add(item);
1468
+ }
1469
+ async remove(item) {
1470
+ const { items } = this;
1471
+ if (!items)
1472
+ return;
1473
+ items.delete(item);
1474
+ if (items.size > 0)
1475
+ return;
1476
+ const dir = this.path;
1477
+ try {
1478
+ await readdir2(dir);
1479
+ } catch (err) {
1480
+ if (this._removeWatcher) {
1481
+ this._removeWatcher(sysPath2.dirname(dir), sysPath2.basename(dir));
1482
+ }
1483
+ }
1484
+ }
1485
+ has(item) {
1486
+ const { items } = this;
1487
+ if (!items)
1488
+ return;
1489
+ return items.has(item);
1490
+ }
1491
+ getChildren() {
1492
+ const { items } = this;
1493
+ if (!items)
1494
+ return [];
1495
+ return [...items.values()];
1496
+ }
1497
+ dispose() {
1498
+ this.items.clear();
1499
+ this.path = "";
1500
+ this._removeWatcher = EMPTY_FN;
1501
+ this.items = EMPTY_SET;
1502
+ Object.freeze(this);
1503
+ }
1504
+ }
1505
+ var STAT_METHOD_F = "stat";
1506
+ var STAT_METHOD_L = "lstat";
1507
+
1508
+ class WatchHelper {
1509
+ constructor(path, follow, fsw) {
1510
+ this.fsw = fsw;
1511
+ const watchPath = path;
1512
+ this.path = path = path.replace(REPLACER_RE, "");
1513
+ this.watchPath = watchPath;
1514
+ this.fullWatchPath = sysPath2.resolve(watchPath);
1515
+ this.dirParts = [];
1516
+ this.dirParts.forEach((parts) => {
1517
+ if (parts.length > 1)
1518
+ parts.pop();
1519
+ });
1520
+ this.followSymlinks = follow;
1521
+ this.statMethod = follow ? STAT_METHOD_F : STAT_METHOD_L;
1522
+ }
1523
+ entryPath(entry) {
1524
+ return sysPath2.join(this.watchPath, sysPath2.relative(this.watchPath, entry.fullPath));
1525
+ }
1526
+ filterPath(entry) {
1527
+ const { stats } = entry;
1528
+ if (stats && stats.isSymbolicLink())
1529
+ return this.filterDir(entry);
1530
+ const resolvedPath = this.entryPath(entry);
1531
+ return this.fsw._isntIgnored(resolvedPath, stats) && this.fsw._hasReadPermissions(stats);
1532
+ }
1533
+ filterDir(entry) {
1534
+ return this.fsw._isntIgnored(this.entryPath(entry), entry.stats);
1535
+ }
1536
+ }
1537
+
1538
+ class FSWatcher extends EventEmitter {
1539
+ constructor(_opts = {}) {
1540
+ super();
1541
+ this.closed = false;
1542
+ this._closers = new Map;
1543
+ this._ignoredPaths = new Set;
1544
+ this._throttled = new Map;
1545
+ this._streams = new Set;
1546
+ this._symlinkPaths = new Map;
1547
+ this._watched = new Map;
1548
+ this._pendingWrites = new Map;
1549
+ this._pendingUnlinks = new Map;
1550
+ this._readyCount = 0;
1551
+ this._readyEmitted = false;
1552
+ const awf = _opts.awaitWriteFinish;
1553
+ const DEF_AWF = { stabilityThreshold: 2000, pollInterval: 100 };
1554
+ const opts = {
1555
+ persistent: true,
1556
+ ignoreInitial: false,
1557
+ ignorePermissionErrors: false,
1558
+ interval: 100,
1559
+ binaryInterval: 300,
1560
+ followSymlinks: true,
1561
+ usePolling: false,
1562
+ atomic: true,
1563
+ ..._opts,
1564
+ ignored: _opts.ignored ? arrify(_opts.ignored) : arrify([]),
1565
+ awaitWriteFinish: awf === true ? DEF_AWF : typeof awf === "object" ? { ...DEF_AWF, ...awf } : false
1566
+ };
1567
+ if (isIBMi)
1568
+ opts.usePolling = true;
1569
+ if (opts.atomic === undefined)
1570
+ opts.atomic = !opts.usePolling;
1571
+ const envPoll = process.env.CHOKIDAR_USEPOLLING;
1572
+ if (envPoll !== undefined) {
1573
+ const envLower = envPoll.toLowerCase();
1574
+ if (envLower === "false" || envLower === "0")
1575
+ opts.usePolling = false;
1576
+ else if (envLower === "true" || envLower === "1")
1577
+ opts.usePolling = true;
1578
+ else
1579
+ opts.usePolling = !!envLower;
1580
+ }
1581
+ const envInterval = process.env.CHOKIDAR_INTERVAL;
1582
+ if (envInterval)
1583
+ opts.interval = Number.parseInt(envInterval, 10);
1584
+ let readyCalls = 0;
1585
+ this._emitReady = () => {
1586
+ readyCalls++;
1587
+ if (readyCalls >= this._readyCount) {
1588
+ this._emitReady = EMPTY_FN;
1589
+ this._readyEmitted = true;
1590
+ process.nextTick(() => this.emit(EVENTS.READY));
1591
+ }
1592
+ };
1593
+ this._emitRaw = (...args) => this.emit(EVENTS.RAW, ...args);
1594
+ this._boundRemove = this._remove.bind(this);
1595
+ this.options = opts;
1596
+ this._nodeFsHandler = new NodeFsHandler(this);
1597
+ Object.freeze(opts);
1598
+ }
1599
+ _addIgnoredPath(matcher) {
1600
+ if (isMatcherObject(matcher)) {
1601
+ for (const ignored of this._ignoredPaths) {
1602
+ if (isMatcherObject(ignored) && ignored.path === matcher.path && ignored.recursive === matcher.recursive) {
1603
+ return;
1604
+ }
1605
+ }
1606
+ }
1607
+ this._ignoredPaths.add(matcher);
1608
+ }
1609
+ _removeIgnoredPath(matcher) {
1610
+ this._ignoredPaths.delete(matcher);
1611
+ if (typeof matcher === "string") {
1612
+ for (const ignored of this._ignoredPaths) {
1613
+ if (isMatcherObject(ignored) && ignored.path === matcher) {
1614
+ this._ignoredPaths.delete(ignored);
1615
+ }
1616
+ }
1617
+ }
1618
+ }
1619
+ add(paths_, _origAdd, _internal) {
1620
+ const { cwd } = this.options;
1621
+ this.closed = false;
1622
+ this._closePromise = undefined;
1623
+ let paths = unifyPaths(paths_);
1624
+ if (cwd) {
1625
+ paths = paths.map((path) => {
1626
+ const absPath = getAbsolutePath(path, cwd);
1627
+ return absPath;
1628
+ });
1629
+ }
1630
+ paths.forEach((path) => {
1631
+ this._removeIgnoredPath(path);
1632
+ });
1633
+ this._userIgnored = undefined;
1634
+ if (!this._readyCount)
1635
+ this._readyCount = 0;
1636
+ this._readyCount += paths.length;
1637
+ Promise.all(paths.map(async (path) => {
1638
+ const res = await this._nodeFsHandler._addToNodeFs(path, !_internal, undefined, 0, _origAdd);
1639
+ if (res)
1640
+ this._emitReady();
1641
+ return res;
1642
+ })).then((results) => {
1643
+ if (this.closed)
1644
+ return;
1645
+ results.forEach((item) => {
1646
+ if (item)
1647
+ this.add(sysPath2.dirname(item), sysPath2.basename(_origAdd || item));
1648
+ });
1649
+ });
1650
+ return this;
1651
+ }
1652
+ unwatch(paths_) {
1653
+ if (this.closed)
1654
+ return this;
1655
+ const paths = unifyPaths(paths_);
1656
+ const { cwd } = this.options;
1657
+ paths.forEach((path) => {
1658
+ if (!sysPath2.isAbsolute(path) && !this._closers.has(path)) {
1659
+ if (cwd)
1660
+ path = sysPath2.join(cwd, path);
1661
+ path = sysPath2.resolve(path);
1662
+ }
1663
+ this._closePath(path);
1664
+ this._addIgnoredPath(path);
1665
+ if (this._watched.has(path)) {
1666
+ this._addIgnoredPath({
1667
+ path,
1668
+ recursive: true
1669
+ });
1670
+ }
1671
+ this._userIgnored = undefined;
1672
+ });
1673
+ return this;
1674
+ }
1675
+ close() {
1676
+ if (this._closePromise) {
1677
+ return this._closePromise;
1678
+ }
1679
+ this.closed = true;
1680
+ this.removeAllListeners();
1681
+ const closers = [];
1682
+ this._closers.forEach((closerList) => closerList.forEach((closer) => {
1683
+ const promise = closer();
1684
+ if (promise instanceof Promise)
1685
+ closers.push(promise);
1686
+ }));
1687
+ this._streams.forEach((stream) => stream.destroy());
1688
+ this._userIgnored = undefined;
1689
+ this._readyCount = 0;
1690
+ this._readyEmitted = false;
1691
+ this._watched.forEach((dirent) => dirent.dispose());
1692
+ this._closers.clear();
1693
+ this._watched.clear();
1694
+ this._streams.clear();
1695
+ this._symlinkPaths.clear();
1696
+ this._throttled.clear();
1697
+ this._closePromise = closers.length ? Promise.all(closers).then(() => {
1698
+ return;
1699
+ }) : Promise.resolve();
1700
+ return this._closePromise;
1701
+ }
1702
+ getWatched() {
1703
+ const watchList = {};
1704
+ this._watched.forEach((entry, dir) => {
1705
+ const key = this.options.cwd ? sysPath2.relative(this.options.cwd, dir) : dir;
1706
+ const index = key || ONE_DOT;
1707
+ watchList[index] = entry.getChildren().sort();
1708
+ });
1709
+ return watchList;
1710
+ }
1711
+ emitWithAll(event, args) {
1712
+ this.emit(event, ...args);
1713
+ if (event !== EVENTS.ERROR)
1714
+ this.emit(EVENTS.ALL, event, ...args);
1715
+ }
1716
+ async _emit(event, path, stats) {
1717
+ if (this.closed)
1718
+ return;
1719
+ const opts = this.options;
1720
+ if (isWindows)
1721
+ path = sysPath2.normalize(path);
1722
+ if (opts.cwd)
1723
+ path = sysPath2.relative(opts.cwd, path);
1724
+ const args = [path];
1725
+ if (stats != null)
1726
+ args.push(stats);
1727
+ const awf = opts.awaitWriteFinish;
1728
+ let pw;
1729
+ if (awf && (pw = this._pendingWrites.get(path))) {
1730
+ pw.lastChange = new Date;
1731
+ return this;
1732
+ }
1733
+ if (opts.atomic) {
1734
+ if (event === EVENTS.UNLINK) {
1735
+ this._pendingUnlinks.set(path, [event, ...args]);
1736
+ setTimeout(() => {
1737
+ this._pendingUnlinks.forEach((entry, path2) => {
1738
+ this.emit(...entry);
1739
+ this.emit(EVENTS.ALL, ...entry);
1740
+ this._pendingUnlinks.delete(path2);
1741
+ });
1742
+ }, typeof opts.atomic === "number" ? opts.atomic : 100);
1743
+ return this;
1744
+ }
1745
+ if (event === EVENTS.ADD && this._pendingUnlinks.has(path)) {
1746
+ event = EVENTS.CHANGE;
1747
+ this._pendingUnlinks.delete(path);
1748
+ }
1749
+ }
1750
+ if (awf && (event === EVENTS.ADD || event === EVENTS.CHANGE) && this._readyEmitted) {
1751
+ const awfEmit = (err, stats2) => {
1752
+ if (err) {
1753
+ event = EVENTS.ERROR;
1754
+ args[0] = err;
1755
+ this.emitWithAll(event, args);
1756
+ } else if (stats2) {
1757
+ if (args.length > 1) {
1758
+ args[1] = stats2;
1759
+ } else {
1760
+ args.push(stats2);
1761
+ }
1762
+ this.emitWithAll(event, args);
1763
+ }
1764
+ };
1765
+ this._awaitWriteFinish(path, awf.stabilityThreshold, event, awfEmit);
1766
+ return this;
1767
+ }
1768
+ if (event === EVENTS.CHANGE) {
1769
+ const isThrottled = !this._throttle(EVENTS.CHANGE, path, 50);
1770
+ if (isThrottled)
1771
+ return this;
1772
+ }
1773
+ if (opts.alwaysStat && stats === undefined && (event === EVENTS.ADD || event === EVENTS.ADD_DIR || event === EVENTS.CHANGE)) {
1774
+ const fullPath = opts.cwd ? sysPath2.join(opts.cwd, path) : path;
1775
+ let stats2;
1776
+ try {
1777
+ stats2 = await stat3(fullPath);
1778
+ } catch (err) {}
1779
+ if (!stats2 || this.closed)
1780
+ return;
1781
+ args.push(stats2);
1782
+ }
1783
+ this.emitWithAll(event, args);
1784
+ return this;
1785
+ }
1786
+ _handleError(error) {
1787
+ const code = error && error.code;
1788
+ if (error && code !== "ENOENT" && code !== "ENOTDIR" && (!this.options.ignorePermissionErrors || code !== "EPERM" && code !== "EACCES")) {
1789
+ this.emit(EVENTS.ERROR, error);
1790
+ }
1791
+ return error || this.closed;
1792
+ }
1793
+ _throttle(actionType, path, timeout) {
1794
+ if (!this._throttled.has(actionType)) {
1795
+ this._throttled.set(actionType, new Map);
1796
+ }
1797
+ const action = this._throttled.get(actionType);
1798
+ if (!action)
1799
+ throw new Error("invalid throttle");
1800
+ const actionPath = action.get(path);
1801
+ if (actionPath) {
1802
+ actionPath.count++;
1803
+ return false;
1804
+ }
1805
+ let timeoutObject;
1806
+ const clear = () => {
1807
+ const item = action.get(path);
1808
+ const count = item ? item.count : 0;
1809
+ action.delete(path);
1810
+ clearTimeout(timeoutObject);
1811
+ if (item)
1812
+ clearTimeout(item.timeoutObject);
1813
+ return count;
1814
+ };
1815
+ timeoutObject = setTimeout(clear, timeout);
1816
+ const thr = { timeoutObject, clear, count: 0 };
1817
+ action.set(path, thr);
1818
+ return thr;
1819
+ }
1820
+ _incrReadyCount() {
1821
+ return this._readyCount++;
1822
+ }
1823
+ _awaitWriteFinish(path, threshold, event, awfEmit) {
1824
+ const awf = this.options.awaitWriteFinish;
1825
+ if (typeof awf !== "object")
1826
+ return;
1827
+ const pollInterval = awf.pollInterval;
1828
+ let timeoutHandler;
1829
+ let fullPath = path;
1830
+ if (this.options.cwd && !sysPath2.isAbsolute(path)) {
1831
+ fullPath = sysPath2.join(this.options.cwd, path);
1832
+ }
1833
+ const now = new Date;
1834
+ const writes = this._pendingWrites;
1835
+ function awaitWriteFinishFn(prevStat) {
1836
+ statcb(fullPath, (err, curStat) => {
1837
+ if (err || !writes.has(path)) {
1838
+ if (err && err.code !== "ENOENT")
1839
+ awfEmit(err);
1840
+ return;
1841
+ }
1842
+ const now2 = Number(new Date);
1843
+ if (prevStat && curStat.size !== prevStat.size) {
1844
+ writes.get(path).lastChange = now2;
1845
+ }
1846
+ const pw = writes.get(path);
1847
+ const df = now2 - pw.lastChange;
1848
+ if (df >= threshold) {
1849
+ writes.delete(path);
1850
+ awfEmit(undefined, curStat);
1851
+ } else {
1852
+ timeoutHandler = setTimeout(awaitWriteFinishFn, pollInterval, curStat);
1853
+ }
1854
+ });
1855
+ }
1856
+ if (!writes.has(path)) {
1857
+ writes.set(path, {
1858
+ lastChange: now,
1859
+ cancelWait: () => {
1860
+ writes.delete(path);
1861
+ clearTimeout(timeoutHandler);
1862
+ return event;
1863
+ }
1864
+ });
1865
+ timeoutHandler = setTimeout(awaitWriteFinishFn, pollInterval);
1866
+ }
1867
+ }
1868
+ _isIgnored(path, stats) {
1869
+ if (this.options.atomic && DOT_RE.test(path))
1870
+ return true;
1871
+ if (!this._userIgnored) {
1872
+ const { cwd } = this.options;
1873
+ const ign = this.options.ignored;
1874
+ const ignored = (ign || []).map(normalizeIgnored(cwd));
1875
+ const ignoredPaths = [...this._ignoredPaths];
1876
+ const list = [...ignoredPaths.map(normalizeIgnored(cwd)), ...ignored];
1877
+ this._userIgnored = anymatch(list, undefined);
1878
+ }
1879
+ return this._userIgnored(path, stats);
1880
+ }
1881
+ _isntIgnored(path, stat4) {
1882
+ return !this._isIgnored(path, stat4);
1883
+ }
1884
+ _getWatchHelpers(path) {
1885
+ return new WatchHelper(path, this.options.followSymlinks, this);
1886
+ }
1887
+ _getWatchedDir(directory) {
1888
+ const dir = sysPath2.resolve(directory);
1889
+ if (!this._watched.has(dir))
1890
+ this._watched.set(dir, new DirEntry(dir, this._boundRemove));
1891
+ return this._watched.get(dir);
1892
+ }
1893
+ _hasReadPermissions(stats) {
1894
+ if (this.options.ignorePermissionErrors)
1895
+ return true;
1896
+ return Boolean(Number(stats.mode) & 256);
1897
+ }
1898
+ _remove(directory, item, isDirectory) {
1899
+ const path = sysPath2.join(directory, item);
1900
+ const fullPath = sysPath2.resolve(path);
1901
+ isDirectory = isDirectory != null ? isDirectory : this._watched.has(path) || this._watched.has(fullPath);
1902
+ if (!this._throttle("remove", path, 100))
1903
+ return;
1904
+ if (!isDirectory && this._watched.size === 1) {
1905
+ this.add(directory, item, true);
1906
+ }
1907
+ const wp = this._getWatchedDir(path);
1908
+ const nestedDirectoryChildren = wp.getChildren();
1909
+ nestedDirectoryChildren.forEach((nested) => this._remove(path, nested));
1910
+ const parent = this._getWatchedDir(directory);
1911
+ const wasTracked = parent.has(item);
1912
+ parent.remove(item);
1913
+ if (this._symlinkPaths.has(fullPath)) {
1914
+ this._symlinkPaths.delete(fullPath);
1915
+ }
1916
+ let relPath = path;
1917
+ if (this.options.cwd)
1918
+ relPath = sysPath2.relative(this.options.cwd, path);
1919
+ if (this.options.awaitWriteFinish && this._pendingWrites.has(relPath)) {
1920
+ const event = this._pendingWrites.get(relPath).cancelWait();
1921
+ if (event === EVENTS.ADD)
1922
+ return;
1923
+ }
1924
+ this._watched.delete(path);
1925
+ this._watched.delete(fullPath);
1926
+ const eventName = isDirectory ? EVENTS.UNLINK_DIR : EVENTS.UNLINK;
1927
+ if (wasTracked && !this._isIgnored(path))
1928
+ this._emit(eventName, path);
1929
+ this._closePath(path);
1930
+ }
1931
+ _closePath(path) {
1932
+ this._closeFile(path);
1933
+ const dir = sysPath2.dirname(path);
1934
+ this._getWatchedDir(dir).remove(sysPath2.basename(path));
1935
+ }
1936
+ _closeFile(path) {
1937
+ const closers = this._closers.get(path);
1938
+ if (!closers)
1939
+ return;
1940
+ closers.forEach((closer) => closer());
1941
+ this._closers.delete(path);
1942
+ }
1943
+ _addPathCloser(path, closer) {
1944
+ if (!closer)
1945
+ return;
1946
+ let list = this._closers.get(path);
1947
+ if (!list) {
1948
+ list = [];
1949
+ this._closers.set(path, list);
1950
+ }
1951
+ list.push(closer);
1952
+ }
1953
+ _readdirp(root, opts) {
1954
+ if (this.closed)
1955
+ return;
1956
+ const options = { type: EVENTS.ALL, alwaysStat: true, lstat: true, ...opts, depth: 0 };
1957
+ let stream = readdirp(root, options);
1958
+ this._streams.add(stream);
1959
+ stream.once(STR_CLOSE, () => {
1960
+ stream = undefined;
1961
+ });
1962
+ stream.once(STR_END, () => {
1963
+ if (stream) {
1964
+ this._streams.delete(stream);
1965
+ stream = undefined;
1966
+ }
1967
+ });
1968
+ return stream;
1969
+ }
1970
+ }
1971
+ function watch(paths, options = {}) {
1972
+ const watcher = new FSWatcher(options);
1973
+ watcher.add(paths);
1974
+ return watcher;
1975
+ }
1976
+
1977
+ // src/daemon/watcher.ts
1978
+ import { relative as relative4 } from "path";
1979
+ function createWatcher(collections2, onEvent) {
1980
+ const paths = collections2.map((f) => f.path);
1981
+ const watcher = watch(paths, {
1982
+ ignored: [
1983
+ /(^|[/\\])\./,
1984
+ "**/node_modules/**",
1985
+ "**/*.tmp.*"
1986
+ ],
1987
+ persistent: true,
1988
+ ignoreInitial: true,
1989
+ awaitWriteFinish: {
1990
+ stabilityThreshold: 300,
1991
+ pollInterval: 100
1992
+ }
1993
+ });
1994
+ const collectionMap = new Map;
1995
+ for (const f of collections2) {
1996
+ collectionMap.set(f.path, f.name);
1997
+ }
1998
+ function toServerPath(localPath) {
1999
+ if (!localPath.endsWith(".md"))
2000
+ return null;
2001
+ for (const [collectionPath, name] of collectionMap) {
2002
+ if (localPath.startsWith(collectionPath + "/") || localPath === collectionPath) {
2003
+ const rel = relative4(collectionPath, localPath);
2004
+ return `${name}/${rel}`;
2005
+ }
2006
+ }
2007
+ return null;
2008
+ }
2009
+ watcher.on("add", (path) => {
2010
+ const sp = toServerPath(path);
2011
+ if (sp)
2012
+ onEvent({ type: "add", serverPath: sp, localPath: path });
2013
+ });
2014
+ watcher.on("change", (path) => {
2015
+ const sp = toServerPath(path);
2016
+ if (sp)
2017
+ onEvent({ type: "change", serverPath: sp, localPath: path });
2018
+ });
2019
+ watcher.on("unlink", (path) => {
2020
+ const sp = toServerPath(path);
2021
+ if (sp)
2022
+ onEvent({ type: "unlink", serverPath: sp, localPath: path });
2023
+ });
2024
+ return watcher;
2025
+ }
2026
+
2027
+ // src/daemon/syncer.ts
2028
+ import { readdir as readdir3, stat as stat4, readFile } from "fs/promises";
2029
+ import { join as join4, relative as relative5 } from "path";
2030
+
2031
+ // src/daemon/queue.ts
2032
+ var MIN_BACKOFF = 1000;
2033
+ var MAX_BACKOFF = 60000;
2034
+
2035
+ class RetryQueue {
2036
+ items = [];
2037
+ flushing = false;
2038
+ backoff = MIN_BACKOFF;
2039
+ flushTimer = null;
2040
+ client;
2041
+ onStatus;
2042
+ constructor(client, onStatus = () => {}) {
2043
+ this.client = client;
2044
+ this.onStatus = onStatus;
2045
+ }
2046
+ enqueue(op) {
2047
+ this.items.push(op);
2048
+ this.scheduleFlush(0);
2049
+ }
2050
+ get pending() {
2051
+ return this.items.length;
2052
+ }
2053
+ stop() {
2054
+ if (this.flushTimer) {
2055
+ clearTimeout(this.flushTimer);
2056
+ this.flushTimer = null;
2057
+ }
2058
+ }
2059
+ scheduleFlush(delayMs) {
2060
+ if (this.flushing || this.flushTimer)
2061
+ return;
2062
+ if (this.items.length === 0)
2063
+ return;
2064
+ this.flushTimer = setTimeout(() => {
2065
+ this.flushTimer = null;
2066
+ this.flush();
2067
+ }, delayMs);
2068
+ }
2069
+ async flush() {
2070
+ if (this.flushing || this.items.length === 0)
2071
+ return;
2072
+ this.flushing = true;
2073
+ while (this.items.length > 0) {
2074
+ const op = this.items[0];
2075
+ try {
2076
+ if (op.type === "put" && op.content !== null) {
2077
+ await this.client.putFile(op.contributorId, op.serverPath, op.content);
2078
+ } else if (op.type === "delete") {
2079
+ await this.client.deleteFile(op.contributorId, op.serverPath);
2080
+ }
2081
+ this.items.shift();
2082
+ this.backoff = MIN_BACKOFF;
2083
+ } catch (e) {
2084
+ if (e instanceof ApiError && e.status >= 400 && e.status < 500) {
2085
+ this.onStatus(`Dropping failed op: ${op.type} ${op.serverPath} (${e.status})`);
2086
+ this.items.shift();
2087
+ continue;
2088
+ }
2089
+ const errMsg = e instanceof Error ? e.message : String(e);
2090
+ this.onStatus(`Server unreachable (${errMsg}), ${this.items.length} op(s) queued. Retry in ${this.backoff / 1000}s.`);
2091
+ this.flushing = false;
2092
+ this.scheduleFlush(this.backoff);
2093
+ this.backoff = Math.min(this.backoff * 2, MAX_BACKOFF);
2094
+ return;
2095
+ }
2096
+ }
2097
+ this.flushing = false;
2098
+ if (this.items.length === 0) {
2099
+ this.onStatus("Queue flushed \u2014 all synced.");
2100
+ }
2101
+ }
2102
+ }
2103
+
2104
+ // src/daemon/syncer.ts
2105
+ class Syncer {
2106
+ client;
2107
+ contributorId;
2108
+ collections;
2109
+ queue;
2110
+ log;
2111
+ constructor(opts) {
2112
+ this.client = opts.client;
2113
+ this.contributorId = opts.contributorId;
2114
+ this.collections = opts.collections;
2115
+ this.log = opts.onLog;
2116
+ this.queue = new RetryQueue(opts.client, opts.onLog);
2117
+ }
2118
+ setCollections(collections2) {
2119
+ this.collections = [...collections2];
2120
+ }
2121
+ async initialSync() {
2122
+ let uploaded = 0;
2123
+ let skipped = 0;
2124
+ let deleted = 0;
2125
+ for (const collection of [...this.collections]) {
2126
+ const result = await this.syncCollection(collection);
2127
+ uploaded += result.uploaded;
2128
+ skipped += result.skipped;
2129
+ deleted += result.deleted;
2130
+ }
2131
+ return { uploaded, skipped, deleted };
2132
+ }
2133
+ async syncCollection(collection) {
2134
+ let uploaded = 0;
2135
+ let skipped = 0;
2136
+ let deleted = 0;
2137
+ this.log(`Syncing '${collection.name}' (${collection.path})...`);
2138
+ try {
2139
+ const { files: serverFiles } = await this.client.listFiles(this.contributorId, collection.name + "/");
2140
+ const serverMap = new Map;
2141
+ for (const f of serverFiles) {
2142
+ serverMap.set(f.path, f.modifiedAt);
2143
+ }
2144
+ const localFiles = await walkMd(collection.path);
2145
+ const localServerPaths = new Set;
2146
+ for (const localFile of localFiles) {
2147
+ const relPath = toPosixPath(relative5(collection.path, localFile.path));
2148
+ const serverPath = `${collection.name}/${relPath}`;
2149
+ localServerPaths.add(serverPath);
2150
+ const serverMod = serverMap.get(serverPath);
2151
+ if (serverMod) {
2152
+ const serverDate = new Date(serverMod).getTime();
2153
+ const localDate = localFile.mtimeMs;
2154
+ if (localDate <= serverDate) {
2155
+ skipped++;
2156
+ continue;
2157
+ }
2158
+ }
2159
+ const content = await readFile(localFile.path, "utf-8");
2160
+ try {
2161
+ await this.client.putFile(this.contributorId, serverPath, content);
2162
+ uploaded++;
2163
+ } catch {
2164
+ this.queue.enqueue({
2165
+ type: "put",
2166
+ contributorId: this.contributorId,
2167
+ serverPath,
2168
+ content,
2169
+ queuedAt: new Date().toISOString()
2170
+ });
2171
+ }
2172
+ }
2173
+ for (const f of serverFiles) {
2174
+ if (localServerPaths.has(f.path))
2175
+ continue;
2176
+ try {
2177
+ await this.client.deleteFile(this.contributorId, f.path);
2178
+ deleted++;
2179
+ } catch {
2180
+ this.queue.enqueue({
2181
+ type: "delete",
2182
+ contributorId: this.contributorId,
2183
+ serverPath: f.path,
2184
+ content: null,
2185
+ queuedAt: new Date().toISOString()
2186
+ });
2187
+ }
2188
+ }
2189
+ this.log(` '${collection.name}': ${uploaded} uploaded, ${skipped} up-to-date, ${deleted} deleted`);
2190
+ } catch (e) {
2191
+ this.log(` '${collection.name}': sync failed (${e.message})`);
2192
+ }
2193
+ return { uploaded, skipped, deleted };
2194
+ }
2195
+ async purgeCollection(collection) {
2196
+ let deleted = 0;
2197
+ let queued = 0;
2198
+ this.log(`Removing '${collection.name}' files from server...`);
2199
+ try {
2200
+ const { files: serverFiles } = await this.client.listFiles(this.contributorId, collection.name + "/");
2201
+ for (const f of serverFiles) {
2202
+ try {
2203
+ await this.client.deleteFile(this.contributorId, f.path);
2204
+ deleted++;
2205
+ } catch {
2206
+ this.queue.enqueue({
2207
+ type: "delete",
2208
+ contributorId: this.contributorId,
2209
+ serverPath: f.path,
2210
+ content: null,
2211
+ queuedAt: new Date().toISOString()
2212
+ });
2213
+ queued++;
2214
+ }
2215
+ }
2216
+ this.log(` '${collection.name}': ${deleted} deleted, ${queued} queued`);
2217
+ } catch (e) {
2218
+ this.log(` '${collection.name}': remove failed (${e.message})`);
2219
+ }
2220
+ return { deleted, queued };
2221
+ }
2222
+ async handleEvent(event) {
2223
+ if (event.type === "add" || event.type === "change") {
2224
+ const content = await readFile(event.localPath, "utf-8");
2225
+ this.log(`PUT ${event.serverPath} (${content.length} bytes)`);
2226
+ this.queue.enqueue({
2227
+ type: "put",
2228
+ contributorId: this.contributorId,
2229
+ serverPath: event.serverPath,
2230
+ content,
2231
+ queuedAt: new Date().toISOString()
2232
+ });
2233
+ } else if (event.type === "unlink") {
2234
+ this.log(`DELETE ${event.serverPath}`);
2235
+ this.queue.enqueue({
2236
+ type: "delete",
2237
+ contributorId: this.contributorId,
2238
+ serverPath: event.serverPath,
2239
+ content: null,
2240
+ queuedAt: new Date().toISOString()
2241
+ });
2242
+ }
2243
+ }
2244
+ stop() {
2245
+ this.queue.stop();
2246
+ }
2247
+ get pendingOps() {
2248
+ return this.queue.pending;
2249
+ }
2250
+ }
2251
+ function toPosixPath(path) {
2252
+ return path.split("\\").join("/");
2253
+ }
2254
+ async function walkMd(dir) {
2255
+ const results = [];
2256
+ await walkDirRecursive(dir, results);
2257
+ return results;
2258
+ }
2259
+ async function walkDirRecursive(dir, results) {
2260
+ const entries = await readdir3(dir, { withFileTypes: true });
2261
+ for (const entry of entries) {
2262
+ if (entry.name.startsWith(".") || entry.name === "node_modules")
2263
+ continue;
2264
+ const full = join4(dir, entry.name);
2265
+ if (entry.isDirectory()) {
2266
+ await walkDirRecursive(full, results);
2267
+ } else if (entry.isFile() && entry.name.endsWith(".md")) {
2268
+ const s = await stat4(full);
2269
+ results.push({ path: full, mtimeMs: s.mtimeMs });
2270
+ }
2271
+ }
2272
+ }
2273
+
2274
+ // src/commands/start.ts
2275
+ async function start(args) {
2276
+ const daemonize = args.includes("-d") || args.includes("--daemon");
2277
+ if (daemonize) {
2278
+ return startBackground();
2279
+ }
2280
+ return startForeground();
2281
+ }
2282
+ async function startForeground() {
2283
+ let config = loadConfig();
2284
+ let { config: normalizedConfig, removedOverlappingCollections } = normalizeConfigCollections(config);
2285
+ if (removedOverlappingCollections.length > 0) {
2286
+ config = normalizedConfig;
2287
+ }
2288
+ const client = createClient(config.server, config.token);
2289
+ try {
2290
+ await client.health();
2291
+ } catch {
2292
+ console.error(`Cannot reach server at ${config.server}`);
2293
+ process.exit(1);
2294
+ }
2295
+ const log = (msg) => {
2296
+ const ts = new Date().toISOString().slice(11, 19);
2297
+ console.log(`[${ts}] ${msg}`);
2298
+ };
2299
+ let lastOverlapWarning = "";
2300
+ const maybeLogOverlapWarning = (removed) => {
2301
+ const summary = removed.map((c) => `${c.name} (${c.path})`).sort().join(", ");
2302
+ if (!summary) {
2303
+ lastOverlapWarning = "";
2304
+ return;
2305
+ }
2306
+ if (summary === lastOverlapWarning)
2307
+ return;
2308
+ lastOverlapWarning = summary;
2309
+ log(`Ignoring overlapping collections in config: ${summary}`);
2310
+ };
2311
+ maybeLogOverlapWarning(removedOverlappingCollections);
2312
+ log("Seedvault daemon starting...");
2313
+ log(` Server: ${config.server}`);
2314
+ log(` Contributor: ${config.contributorId}`);
2315
+ if (config.collections.length === 0) {
2316
+ log(" Collections: none");
2317
+ log(" Waiting for collections to be added...");
2318
+ } else {
2319
+ log(` Collections: ${config.collections.map((f) => f.name).join(", ")}`);
2320
+ }
2321
+ writeFileSync2(getPidPath(), String(process.pid));
2322
+ const syncer = new Syncer({
2323
+ client,
2324
+ contributorId: config.contributorId,
2325
+ collections: config.collections,
2326
+ onLog: log
2327
+ });
2328
+ if (config.collections.length > 0) {
2329
+ log("Running initial sync...");
2330
+ try {
2331
+ const { uploaded, skipped, deleted } = await syncer.initialSync();
2332
+ log(`Initial sync complete: ${uploaded} uploaded, ${skipped} skipped, ${deleted} deleted`);
2333
+ } catch (e) {
2334
+ log(`Initial sync failed: ${e.message}`);
2335
+ log("Will continue watching for changes...");
2336
+ }
2337
+ }
2338
+ const onWatcherEvent = (event) => {
2339
+ syncer.handleEvent(event).catch((e) => {
2340
+ log(`Error handling ${event.type} for ${event.serverPath}: ${e.message}`);
2341
+ });
2342
+ };
2343
+ let watcher = null;
2344
+ const rebuildWatcher = async (collections2) => {
2345
+ if (watcher) {
2346
+ await watcher.close();
2347
+ watcher = null;
2348
+ }
2349
+ if (collections2.length === 0) {
2350
+ log("No collections configured. Daemon idle.");
2351
+ return;
2352
+ }
2353
+ watcher = createWatcher(collections2, onWatcherEvent);
2354
+ log(`Watching ${collections2.length} collection(s): ${collections2.map((f) => f.name).join(", ")}`);
2355
+ };
2356
+ await rebuildWatcher(config.collections);
2357
+ let reloadingCollections = false;
2358
+ const pollTimer = setInterval(() => {
2359
+ if (reloadingCollections)
2360
+ return;
2361
+ let nextConfig;
2362
+ try {
2363
+ nextConfig = loadConfig();
2364
+ } catch (e) {
2365
+ log(`Failed to read config: ${e.message}`);
2366
+ return;
2367
+ }
2368
+ ({ config: normalizedConfig, removedOverlappingCollections } = normalizeConfigCollections(nextConfig));
2369
+ maybeLogOverlapWarning(removedOverlappingCollections);
2370
+ reloadingCollections = true;
2371
+ (async () => {
2372
+ try {
2373
+ const { nextConfig: reconciledConfig, added, removed } = reconcileCollections(config, normalizedConfig);
2374
+ if (added.length === 0 && removed.length === 0)
2375
+ return;
2376
+ log(`Collections changed: +${added.map((c) => c.name).join(", ") || "none"}, -${removed.map((c) => c.name).join(", ") || "none"}`);
2377
+ config = reconciledConfig;
2378
+ syncer.setCollections(reconciledConfig.collections);
2379
+ await rebuildWatcher(reconciledConfig.collections);
2380
+ for (const collection of removed) {
2381
+ await syncer.purgeCollection(collection);
2382
+ }
2383
+ for (const collection of added) {
2384
+ await syncer.syncCollection(collection);
2385
+ }
2386
+ } catch (e) {
2387
+ log(`Failed to reload collections from config: ${e.message}`);
2388
+ } finally {
2389
+ reloadingCollections = false;
2390
+ }
2391
+ })();
2392
+ }, 1500);
2393
+ log("Daemon running. Press Ctrl+C to stop.");
2394
+ const shutdown = () => {
2395
+ log("Shutting down...");
2396
+ clearInterval(pollTimer);
2397
+ if (watcher)
2398
+ watcher.close();
2399
+ syncer.stop();
2400
+ try {
2401
+ unlinkSync(getPidPath());
2402
+ } catch {}
2403
+ process.exit(0);
2404
+ };
2405
+ process.on("SIGINT", shutdown);
2406
+ process.on("SIGTERM", shutdown);
2407
+ }
2408
+ async function startBackground() {
2409
+ loadConfig();
2410
+ const entryPoint = import.meta.dir + "/../index.ts";
2411
+ const logPath = getConfigDir() + "/daemon.log";
2412
+ const child = Bun.spawn({
2413
+ cmd: ["bun", "run", entryPoint, "start"],
2414
+ stdin: "ignore",
2415
+ stdout: Bun.file(logPath),
2416
+ stderr: Bun.file(logPath),
2417
+ env: { ...process.env }
2418
+ });
2419
+ const pid = child.pid;
2420
+ writeFileSync2(getPidPath(), String(pid));
2421
+ console.log(`Daemon started in background (PID ${pid}).`);
2422
+ console.log(` Log: ${logPath}`);
2423
+ console.log(` Run 'sv status' to check, 'sv stop' to stop.`);
2424
+ child.unref();
2425
+ }
2426
+ function keyByName(collections2) {
2427
+ const map = new Map;
2428
+ for (const collection of collections2) {
2429
+ map.set(collection.name, collection);
2430
+ }
2431
+ return map;
2432
+ }
2433
+ function reconcileCollections(prev, next) {
2434
+ const prevByName = keyByName(prev.collections);
2435
+ const nextByName = keyByName(next.collections);
2436
+ const added = [];
2437
+ const removed = [];
2438
+ for (const [name, prevCollection] of prevByName) {
2439
+ const nextCollection = nextByName.get(name);
2440
+ if (!nextCollection) {
2441
+ removed.push(prevCollection);
2442
+ continue;
2443
+ }
2444
+ if (nextCollection.path !== prevCollection.path) {
2445
+ removed.push(prevCollection);
2446
+ added.push(nextCollection);
2447
+ }
2448
+ }
2449
+ for (const [name, nextCollection] of nextByName) {
2450
+ if (!prevByName.has(name)) {
2451
+ added.push(nextCollection);
2452
+ }
2453
+ }
2454
+ return { nextConfig: next, added, removed };
2455
+ }
2456
+
2457
+ // src/commands/stop.ts
2458
+ import { readFileSync as readFileSync2, unlinkSync as unlinkSync2, existsSync as existsSync3 } from "fs";
2459
+ async function stop() {
2460
+ const pidPath = getPidPath();
2461
+ if (!existsSync3(pidPath)) {
2462
+ console.log("No daemon is running (no PID file found).");
2463
+ return;
2464
+ }
2465
+ const pid = parseInt(readFileSync2(pidPath, "utf-8").trim(), 10);
2466
+ if (isNaN(pid)) {
2467
+ console.error("Invalid PID file. Removing it.");
2468
+ unlinkSync2(pidPath);
2469
+ return;
2470
+ }
2471
+ try {
2472
+ process.kill(pid, 0);
2473
+ } catch {
2474
+ console.log(`Daemon (PID ${pid}) is not running. Cleaning up PID file.`);
2475
+ unlinkSync2(pidPath);
2476
+ return;
2477
+ }
2478
+ try {
2479
+ process.kill(pid, "SIGTERM");
2480
+ console.log(`Sent SIGTERM to daemon (PID ${pid}).`);
2481
+ await Bun.sleep(500);
2482
+ try {
2483
+ process.kill(pid, 0);
2484
+ console.log("Daemon still running. Send SIGKILL with: kill -9 " + pid);
2485
+ } catch {
2486
+ console.log("Daemon stopped.");
2487
+ try {
2488
+ unlinkSync2(pidPath);
2489
+ } catch {}
2490
+ }
2491
+ } catch (e) {
2492
+ console.error(`Failed to stop daemon: ${e.message}`);
2493
+ }
2494
+ }
2495
+
2496
+ // src/commands/status.ts
2497
+ import { readFileSync as readFileSync3, existsSync as existsSync4 } from "fs";
2498
+ async function status() {
2499
+ if (!configExists()) {
2500
+ console.log("Not configured. Run 'sv init' first.");
2501
+ return;
2502
+ }
2503
+ const config = loadConfig();
2504
+ console.log(`Seedvault Status
2505
+ `);
2506
+ console.log(` Server: ${config.server}`);
2507
+ console.log(` Contributor: ${config.contributorId}`);
2508
+ const pidPath = getPidPath();
2509
+ if (existsSync4(pidPath)) {
2510
+ const pid = parseInt(readFileSync3(pidPath, "utf-8").trim(), 10);
2511
+ let alive = false;
2512
+ try {
2513
+ process.kill(pid, 0);
2514
+ alive = true;
2515
+ } catch {}
2516
+ if (alive) {
2517
+ console.log(` Daemon: running (PID ${pid})`);
2518
+ } else {
2519
+ console.log(` Daemon: not running (stale PID file)`);
2520
+ }
2521
+ } else {
2522
+ console.log(" Daemon: not running");
2523
+ }
2524
+ if (config.collections.length === 0) {
2525
+ console.log(" Collections: none configured");
2526
+ } else {
2527
+ console.log(` Collections: ${config.collections.length}`);
2528
+ for (const f of config.collections) {
2529
+ console.log(` - ${f.name} -> ${f.path}`);
2530
+ }
2531
+ }
2532
+ const client = createClient(config.server, config.token);
2533
+ try {
2534
+ await client.health();
2535
+ console.log(" Server: reachable");
2536
+ } catch {
2537
+ console.log(" Server: unreachable");
2538
+ }
2539
+ console.log();
2540
+ }
2541
+
2542
+ // src/commands/ls.ts
2543
+ async function ls(args) {
2544
+ const config = loadConfig();
2545
+ const client = createClient(config.server, config.token);
2546
+ const prefix = args[0] || undefined;
2547
+ const { files } = await client.listFiles(config.contributorId, prefix);
2548
+ if (files.length === 0) {
2549
+ console.log(prefix ? `No files matching '${prefix}'.` : "No files in your contributor.");
2550
+ return;
2551
+ }
2552
+ const maxPath = Math.max(...files.map((f) => f.path.length));
2553
+ for (const f of files) {
2554
+ const size = formatSize(f.size);
2555
+ const date = new Date(f.modifiedAt).toLocaleString();
2556
+ console.log(` ${f.path.padEnd(maxPath + 2)} ${size.padStart(8)} ${date}`);
2557
+ }
2558
+ console.log(`
2559
+ ${files.length} file(s)`);
2560
+ }
2561
+ function formatSize(bytes) {
2562
+ if (bytes < 1024)
2563
+ return `${bytes} B`;
2564
+ if (bytes < 1024 * 1024)
2565
+ return `${(bytes / 1024).toFixed(1)} KB`;
2566
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
2567
+ }
2568
+
2569
+ // src/commands/cat.ts
2570
+ async function cat(args) {
2571
+ if (args.length === 0) {
2572
+ console.error("Usage: sv cat <path>");
2573
+ process.exit(1);
2574
+ }
2575
+ const filePath = args[0];
2576
+ const config = loadConfig();
2577
+ const client = createClient(config.server, config.token);
2578
+ try {
2579
+ const content = await client.getFile(config.contributorId, filePath);
2580
+ process.stdout.write(content);
2581
+ } catch (e) {
2582
+ if (e instanceof ApiError && e.status === 404) {
2583
+ console.error(`File not found: ${filePath}`);
2584
+ process.exit(1);
2585
+ }
2586
+ throw e;
2587
+ }
2588
+ }
2589
+
2590
+ // src/commands/contributors.ts
2591
+ async function contributors() {
2592
+ const config = loadConfig();
2593
+ const client = createClient(config.server, config.token);
2594
+ const { contributors: contributors2 } = await client.listContributors();
2595
+ if (contributors2.length === 0) {
2596
+ console.log("No contributors in the vault.");
2597
+ return;
2598
+ }
2599
+ console.log(`Contributors:
2600
+ `);
2601
+ for (const contributor of contributors2) {
2602
+ const you = contributor.id === config.contributorId ? " (you)" : "";
2603
+ console.log(` ${contributor.name}${you}`);
2604
+ console.log(` ID: ${contributor.id}`);
2605
+ console.log(` Created: ${new Date(contributor.createdAt).toLocaleString()}`);
2606
+ console.log();
2607
+ }
2608
+ }
2609
+
2610
+ // src/commands/invite.ts
2611
+ async function invite() {
2612
+ const config = loadConfig();
2613
+ const client = createClient(config.server, config.token);
2614
+ try {
2615
+ const result = await client.createInvite();
2616
+ console.log(`Invite code: ${result.invite}`);
2617
+ console.log(`
2618
+ Share this with the person you want to invite.`);
2619
+ console.log(`They can sign up with:`);
2620
+ console.log(` sv init --server ${config.server} --name <name> --invite ${result.invite}`);
2621
+ } catch (e) {
2622
+ if (e instanceof ApiError && e.status === 403) {
2623
+ console.error("Only the operator can generate invite codes.");
2624
+ process.exit(1);
2625
+ }
2626
+ throw e;
2627
+ }
2628
+ }
2629
+
2630
+ // src/index.ts
2631
+ var USAGE = `
2632
+ Seedvault CLI
2633
+
2634
+ Usage: sv <command> [options]
2635
+
2636
+ Setup:
2637
+ init Interactive first-time setup
2638
+ init --server URL --token T Non-interactive (existing token)
2639
+ init --server URL --name N Non-interactive (signup)
2640
+
2641
+ Collections:
2642
+ add <path> [--name N] Add a collection path
2643
+ remove <name> Remove a collection by name
2644
+ collections List configured collections
2645
+
2646
+ Daemon:
2647
+ start Start syncing (foreground)
2648
+ start -d Start syncing (background)
2649
+ stop Stop the daemon
2650
+ status Show sync status
2651
+
2652
+ Files:
2653
+ ls [prefix] List files in your contributor
2654
+ cat <path> Read a file from the server
2655
+
2656
+ Vault:
2657
+ contributors List all contributors
2658
+ invite Generate an invite code (operator only)
2659
+ `.trim();
2660
+ async function main() {
2661
+ const [cmd, ...args] = process.argv.slice(2);
2662
+ if (!cmd || cmd === "--help" || cmd === "-h") {
2663
+ console.log(USAGE);
2664
+ return;
2665
+ }
2666
+ if (cmd === "--version" || cmd === "-v") {
2667
+ console.log("0.1.2");
2668
+ return;
2669
+ }
2670
+ try {
2671
+ switch (cmd) {
2672
+ case "init":
2673
+ return await init(args);
2674
+ case "add":
2675
+ return await add(args);
2676
+ case "remove":
2677
+ return await remove(args);
2678
+ case "collections":
2679
+ return await collections();
2680
+ case "start":
2681
+ return await start(args);
2682
+ case "stop":
2683
+ return await stop();
2684
+ case "status":
2685
+ return await status();
2686
+ case "ls":
2687
+ return await ls(args);
2688
+ case "cat":
2689
+ return await cat(args);
2690
+ case "contributors":
2691
+ return await contributors();
2692
+ case "invite":
2693
+ return await invite();
2694
+ default:
2695
+ console.error(`Unknown command: ${cmd}
2696
+ `);
2697
+ console.log(USAGE);
2698
+ process.exit(1);
2699
+ }
2700
+ } catch (e) {
2701
+ console.error(`Error: ${e.message}`);
2702
+ process.exit(1);
2703
+ }
2704
+ }
2705
+ main();