@archildata/just-bash 0.8.18 → 0.8.19
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/index.cjs +633 -678
- package/dist/index.d.cts +131 -136
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +210 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +2 -0
- package/dist/shell.mjs +319 -0
- package/dist/shell.mjs.map +1 -0
- package/dist/src-zhIdm_Vq.mjs +622 -0
- package/dist/src-zhIdm_Vq.mjs.map +1 -0
- package/package.json +19 -18
- package/dist/index.d.ts +0 -215
- package/dist/index.js +0 -652
- package/dist/shell.js +0 -313
package/dist/index.cjs
CHANGED
|
@@ -1,691 +1,646 @@
|
|
|
1
|
-
"
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
//#region \0rolldown/runtime.js
|
|
2
3
|
var __create = Object.create;
|
|
3
4
|
var __defProp = Object.defineProperty;
|
|
4
5
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
6
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
7
|
var __getProtoOf = Object.getPrototypeOf;
|
|
7
8
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
-
var __export = (target, all) => {
|
|
9
|
-
for (var name in all)
|
|
10
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
-
};
|
|
12
9
|
var __copyProps = (to, from, except, desc) => {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
11
|
+
key = keys[i];
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
13
|
+
get: ((k) => from[k]).bind(null, key),
|
|
14
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
19
18
|
};
|
|
20
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
)
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
const { parentInodeId, name } = await this.resolveParent(path);
|
|
481
|
-
await this.client.unlink(parentInodeId, name, { user: this.user });
|
|
482
|
-
}
|
|
483
|
-
async cp(src, dest, options) {
|
|
484
|
-
const srcResolved = await this.resolveFollow(src);
|
|
485
|
-
if (srcResolved.attributes.inodeType === "Directory") {
|
|
486
|
-
if (!options?.recursive) {
|
|
487
|
-
throw new Error(`EISDIR: illegal operation on a directory, cp '${src}'`);
|
|
488
|
-
}
|
|
489
|
-
await this.mkdir(dest, { recursive: true });
|
|
490
|
-
const entries = await this.readdir(src);
|
|
491
|
-
for (const entry of entries) {
|
|
492
|
-
await this.cp(
|
|
493
|
-
this.resolvePath(src, entry),
|
|
494
|
-
this.resolvePath(dest, entry),
|
|
495
|
-
options
|
|
496
|
-
);
|
|
497
|
-
}
|
|
498
|
-
} else {
|
|
499
|
-
const content = await this.readFileBuffer(src);
|
|
500
|
-
await this.writeFile(dest, content);
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
async mv(src, dest) {
|
|
504
|
-
const { parentInodeId: srcParentInodeId, name: srcName } = await this.resolveParent(src);
|
|
505
|
-
const { parentInodeId: destParentInodeId, name: destName } = await this.resolveParent(dest);
|
|
506
|
-
await this.client.rename(
|
|
507
|
-
srcParentInodeId,
|
|
508
|
-
srcName,
|
|
509
|
-
destParentInodeId,
|
|
510
|
-
destName,
|
|
511
|
-
{ user: this.user }
|
|
512
|
-
);
|
|
513
|
-
}
|
|
514
|
-
async symlink(target, path) {
|
|
515
|
-
const { parentInodeId, name } = await this.resolveParent(path);
|
|
516
|
-
await this.client.create(
|
|
517
|
-
parentInodeId,
|
|
518
|
-
name,
|
|
519
|
-
{
|
|
520
|
-
inodeType: "Symlink",
|
|
521
|
-
uid: this.user?.uid ?? 0,
|
|
522
|
-
gid: this.user?.gid ?? 0,
|
|
523
|
-
mode: 511,
|
|
524
|
-
symlinkTarget: target
|
|
525
|
-
},
|
|
526
|
-
{ user: this.user }
|
|
527
|
-
);
|
|
528
|
-
}
|
|
529
|
-
async link(existingPath, newPath) {
|
|
530
|
-
throw new Error(
|
|
531
|
-
"Hard link operations not yet implemented. The archil-node bindings need to expose link for hard links."
|
|
532
|
-
);
|
|
533
|
-
}
|
|
534
|
-
async chmod(path, mode) {
|
|
535
|
-
const { inodeId } = await this.resolveFollow(path);
|
|
536
|
-
await this.client.setattr(inodeId, { mode }, { user: this.user ?? { uid: 0, gid: 0 } });
|
|
537
|
-
}
|
|
538
|
-
async utimes(path, atime, mtime) {
|
|
539
|
-
debug("utimes path=%s atime=%s mtime=%s", path, atime.toISOString(), mtime.toISOString());
|
|
540
|
-
const { inodeId } = await this.resolveFollow(path);
|
|
541
|
-
debug("utimes resolved path=%s inodeId=%d", path, inodeId);
|
|
542
|
-
const atimeMs = atime.getTime();
|
|
543
|
-
const mtimeMs = mtime.getTime();
|
|
544
|
-
debug("utimes calling setattr inodeId=%d atimeMs=%d mtimeMs=%d", inodeId, atimeMs, mtimeMs);
|
|
545
|
-
await this.client.setattr(
|
|
546
|
-
inodeId,
|
|
547
|
-
{ atimeMs, mtimeMs },
|
|
548
|
-
{ user: this.user ?? { uid: 0, gid: 0 } }
|
|
549
|
-
);
|
|
550
|
-
debug("utimes setattr succeeded inodeId=%d", inodeId);
|
|
551
|
-
}
|
|
552
|
-
// ========================================================================
|
|
553
|
-
// Utility Methods
|
|
554
|
-
// ========================================================================
|
|
555
|
-
getAllPaths() {
|
|
556
|
-
return [];
|
|
557
|
-
}
|
|
558
|
-
/**
|
|
559
|
-
* Resolve a path to its inode ID (public wrapper for delegation operations)
|
|
560
|
-
*/
|
|
561
|
-
async resolveInodeId(path) {
|
|
562
|
-
const { inodeId } = await this.resolve(path);
|
|
563
|
-
return inodeId;
|
|
564
|
-
}
|
|
19
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
20
|
+
value: mod,
|
|
21
|
+
enumerable: true
|
|
22
|
+
}) : target, mod));
|
|
23
|
+
//#endregion
|
|
24
|
+
let debug = require("debug");
|
|
25
|
+
debug = __toESM(debug, 1);
|
|
26
|
+
let _archildata_native = require("@archildata/native");
|
|
27
|
+
let just_bash = require("just-bash");
|
|
28
|
+
//#region src/ArchilFs.ts
|
|
29
|
+
/**
|
|
30
|
+
* Archil filesystem adapter for just-bash
|
|
31
|
+
*
|
|
32
|
+
* Implements the IFileSystem interface from just-bash using the ArchilClient
|
|
33
|
+
* from @archildata/native for direct protocol access to Archil distributed filesystems.
|
|
34
|
+
*/
|
|
35
|
+
const debug$1 = (0, debug.default)("archil:fs");
|
|
36
|
+
/**
|
|
37
|
+
* ArchilFs implements the just-bash IFileSystem interface using Archil protocol.
|
|
38
|
+
*
|
|
39
|
+
* This adapter provides:
|
|
40
|
+
* - Path-to-inode resolution
|
|
41
|
+
* - Full filesystem operations via Archil protocol
|
|
42
|
+
* - Optional user context for permission checks
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```typescript
|
|
46
|
+
* import { ArchilClient } from '@archildata/native';
|
|
47
|
+
* import { ArchilFs } from '@archildata/just-bash';
|
|
48
|
+
*
|
|
49
|
+
* const client = await ArchilClient.connect({
|
|
50
|
+
* region: 'aws-us-east-1',
|
|
51
|
+
* diskName: 'myaccount/mydisk',
|
|
52
|
+
* authToken: 'adt_xxx',
|
|
53
|
+
* });
|
|
54
|
+
*
|
|
55
|
+
* const fs = await ArchilFs.create(client);
|
|
56
|
+
*
|
|
57
|
+
* // Use with just-bash
|
|
58
|
+
* import { Bash } from 'just-bash';
|
|
59
|
+
* const bash = new Bash({ fs });
|
|
60
|
+
* await bash.run('ls -la /');
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
63
|
+
var ArchilFs = class ArchilFs {
|
|
64
|
+
client;
|
|
65
|
+
user;
|
|
66
|
+
rootInodeId = 1;
|
|
67
|
+
constructor(client, options) {
|
|
68
|
+
this.client = client;
|
|
69
|
+
this.user = options?.user;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Create an ArchilFs adapter, optionally rooted at a subdirectory.
|
|
73
|
+
*
|
|
74
|
+
* The subdirectory path is resolved eagerly: if any component does not
|
|
75
|
+
* exist or is not a directory, this method throws immediately.
|
|
76
|
+
*
|
|
77
|
+
* @param client - Connected ArchilClient instance
|
|
78
|
+
* @param options - Optional configuration
|
|
79
|
+
* @param options.user - Unix user context for permission checks
|
|
80
|
+
* @param options.subdirectory - Subdirectory to treat as the root of the filesystem
|
|
81
|
+
*/
|
|
82
|
+
static async create(client, options) {
|
|
83
|
+
const fs = new ArchilFs(client, { user: options?.user });
|
|
84
|
+
if (options?.subdirectory) {
|
|
85
|
+
const resolved = await fs.resolveFollow(options.subdirectory);
|
|
86
|
+
if (resolved.attributes.inodeType !== "Directory") throw new Error(`ENOTDIR: subdirectory '${options.subdirectory}' is not a directory`);
|
|
87
|
+
fs.rootInodeId = resolved.inodeId;
|
|
88
|
+
debug$1("resolved subdirectory '%s' to inode %d", options.subdirectory, fs.rootInodeId);
|
|
89
|
+
}
|
|
90
|
+
return fs;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Normalize a path (remove . and .., ensure leading /)
|
|
94
|
+
*/
|
|
95
|
+
normalizePath(path) {
|
|
96
|
+
if (!path || path === "") return "/";
|
|
97
|
+
if (!path.startsWith("/")) path = "/" + path;
|
|
98
|
+
const parts = path.split("/").filter((p) => p !== "" && p !== ".");
|
|
99
|
+
const result = [];
|
|
100
|
+
for (const part of parts) if (part === "..") result.pop();
|
|
101
|
+
else result.push(part);
|
|
102
|
+
return "/" + result.join("/");
|
|
103
|
+
}
|
|
104
|
+
static MAX_SYMLINKS = 40;
|
|
105
|
+
/**
|
|
106
|
+
* Resolve a path to its inode ID, walking the directory tree.
|
|
107
|
+
* Follows symlinks on intermediate components but NOT on the final
|
|
108
|
+
* component (matching lstat/readlink semantics).
|
|
109
|
+
*/
|
|
110
|
+
async resolve(path, symlinkDepth = 0) {
|
|
111
|
+
const parts = this.normalizePath(path).split("/").filter((p) => p !== "");
|
|
112
|
+
let currentInodeId = this.rootInodeId;
|
|
113
|
+
let resolvedPath = "/";
|
|
114
|
+
for (let i = 0; i < parts.length; i++) {
|
|
115
|
+
const part = parts[i];
|
|
116
|
+
const response = await this.client.lookupInode(currentInodeId, part, { user: this.user });
|
|
117
|
+
if (response === null) throw new Error(`ENOENT: no such file or directory, '${path}'`);
|
|
118
|
+
if (!(i === parts.length - 1) && response.attributes.inodeType === "Symlink" && response.attributes.symlinkTarget) {
|
|
119
|
+
if (symlinkDepth >= ArchilFs.MAX_SYMLINKS) throw new Error(`ELOOP: too many levels of symbolic links, '${path}'`);
|
|
120
|
+
const targetPath = response.attributes.symlinkTarget.startsWith("/") ? response.attributes.symlinkTarget : this.resolvePath(resolvedPath, response.attributes.symlinkTarget);
|
|
121
|
+
const remaining = parts.slice(i + 1).join("/");
|
|
122
|
+
const fullPath = remaining ? targetPath + "/" + remaining : targetPath;
|
|
123
|
+
return this.resolve(fullPath, symlinkDepth + 1);
|
|
124
|
+
}
|
|
125
|
+
resolvedPath = resolvedPath === "/" ? "/" + part : resolvedPath + "/" + part;
|
|
126
|
+
currentInodeId = response.inodeId;
|
|
127
|
+
}
|
|
128
|
+
const attributes = await this.client.getAttributes(currentInodeId, { user: this.user });
|
|
129
|
+
return {
|
|
130
|
+
inodeId: currentInodeId,
|
|
131
|
+
attributes
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Resolve a path, following symlinks on ALL components including the
|
|
136
|
+
* final one (like stat(2)). Use resolve() when you need lstat semantics.
|
|
137
|
+
*/
|
|
138
|
+
async resolveFollow(path, symlinkDepth = 0) {
|
|
139
|
+
if (symlinkDepth >= ArchilFs.MAX_SYMLINKS) throw new Error(`ELOOP: too many levels of symbolic links, '${path}'`);
|
|
140
|
+
const resolved = await this.resolve(path, symlinkDepth);
|
|
141
|
+
if (resolved.attributes.inodeType === "Symlink" && resolved.attributes.symlinkTarget) {
|
|
142
|
+
const targetPath = resolved.attributes.symlinkTarget.startsWith("/") ? resolved.attributes.symlinkTarget : this.resolvePath(path, "..", resolved.attributes.symlinkTarget);
|
|
143
|
+
return this.resolveFollow(targetPath, symlinkDepth + 1);
|
|
144
|
+
}
|
|
145
|
+
return resolved;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Resolve parent directory and get child name
|
|
149
|
+
*/
|
|
150
|
+
async resolveParent(path) {
|
|
151
|
+
debug$1("resolveParent raw path=%j (bytes: %o)", path, Buffer.from(path));
|
|
152
|
+
const normalizedPath = this.normalizePath(path);
|
|
153
|
+
const lastSlash = normalizedPath.lastIndexOf("/");
|
|
154
|
+
const parentPath = lastSlash === 0 ? "/" : normalizedPath.substring(0, lastSlash);
|
|
155
|
+
const name = normalizedPath.substring(lastSlash + 1);
|
|
156
|
+
debug$1("resolveParent extracted name=%j (bytes: %o)", name, Buffer.from(name));
|
|
157
|
+
const { inodeId: parentInodeId } = await this.resolve(parentPath);
|
|
158
|
+
return {
|
|
159
|
+
parentInodeId,
|
|
160
|
+
name
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Convert InodeAttributes to FsStat
|
|
165
|
+
*/
|
|
166
|
+
toStat(attrs) {
|
|
167
|
+
return {
|
|
168
|
+
isFile: attrs.inodeType === "File",
|
|
169
|
+
isDirectory: attrs.inodeType === "Directory",
|
|
170
|
+
isSymbolicLink: attrs.inodeType === "Symlink",
|
|
171
|
+
mode: attrs.mode,
|
|
172
|
+
size: Number(attrs.size),
|
|
173
|
+
mtime: new Date(attrs.mtimeMs)
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
static DIR_PAGE_SIZE = 1e3;
|
|
177
|
+
/**
|
|
178
|
+
* Read all directory entries using the paginated API.
|
|
179
|
+
*/
|
|
180
|
+
async readAllDirectoryEntries(inodeId) {
|
|
181
|
+
const handle = await this.client.openDirectory(inodeId, { user: this.user });
|
|
182
|
+
try {
|
|
183
|
+
const allEntries = [];
|
|
184
|
+
let cursor;
|
|
185
|
+
for (;;) {
|
|
186
|
+
const page = await this.client.readDirectory(inodeId, handle, ArchilFs.DIR_PAGE_SIZE, cursor, { user: this.user });
|
|
187
|
+
allEntries.push(...page.entries);
|
|
188
|
+
if (!page.nextCursor) break;
|
|
189
|
+
cursor = page.nextCursor;
|
|
190
|
+
}
|
|
191
|
+
return allEntries;
|
|
192
|
+
} finally {
|
|
193
|
+
this.client.closeDirectory(inodeId, handle);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
resolvePath(base, ...paths) {
|
|
197
|
+
debug$1("resolvePath base=%s paths=%o", base, paths);
|
|
198
|
+
let result = base;
|
|
199
|
+
for (const p of paths) if (p.startsWith("/")) result = p;
|
|
200
|
+
else result = result.endsWith("/") ? result + p : result + "/" + p;
|
|
201
|
+
const normalized = this.normalizePath(result);
|
|
202
|
+
debug$1("resolvePath result=%s", normalized);
|
|
203
|
+
return normalized;
|
|
204
|
+
}
|
|
205
|
+
async readFile(path, encoding) {
|
|
206
|
+
debug$1("readFile path=%s encoding=%s", path, encoding);
|
|
207
|
+
try {
|
|
208
|
+
const buffer = await this.readFileBuffer(path);
|
|
209
|
+
debug$1("readFile got buffer length=%d", buffer.length);
|
|
210
|
+
const enc = encoding || "utf-8";
|
|
211
|
+
let result;
|
|
212
|
+
if (enc === "base64" || enc === "hex" || enc === "binary") result = Buffer.from(buffer).toString(enc);
|
|
213
|
+
else result = new TextDecoder(enc).decode(buffer);
|
|
214
|
+
debug$1("readFile decoded to string length=%d", result.length);
|
|
215
|
+
return result;
|
|
216
|
+
} catch (err) {
|
|
217
|
+
debug$1("readFile FAILED: %O", err);
|
|
218
|
+
throw err;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
async readFileBuffer(path) {
|
|
222
|
+
debug$1("readFileBuffer path=%s", path);
|
|
223
|
+
const { inodeId, attributes } = await this.resolveFollow(path);
|
|
224
|
+
debug$1("readFileBuffer resolved inodeId=%d type=%s size=%d", inodeId, attributes.inodeType, attributes.size);
|
|
225
|
+
if (attributes.inodeType !== "File") throw new Error(`EISDIR: illegal operation on a directory, read '${path}'`);
|
|
226
|
+
const size = Number(attributes.size);
|
|
227
|
+
if (size === 0) {
|
|
228
|
+
debug$1("readFileBuffer file is empty, returning empty buffer");
|
|
229
|
+
return /* @__PURE__ */ new Uint8Array(0);
|
|
230
|
+
}
|
|
231
|
+
if (size <= _archildata_native.MAXIMUM_READ_SIZE) {
|
|
232
|
+
const buffer = await this.client.readInode(inodeId, 0, size, { user: this.user });
|
|
233
|
+
return new Uint8Array(buffer);
|
|
234
|
+
}
|
|
235
|
+
const result = new Uint8Array(size);
|
|
236
|
+
let offset = 0;
|
|
237
|
+
while (offset < size) {
|
|
238
|
+
const chunkSize = Math.min(_archildata_native.MAXIMUM_READ_SIZE, size - offset);
|
|
239
|
+
const chunk = await this.client.readInode(inodeId, offset, chunkSize, { user: this.user });
|
|
240
|
+
result.set(new Uint8Array(chunk), offset);
|
|
241
|
+
offset += chunkSize;
|
|
242
|
+
}
|
|
243
|
+
return result;
|
|
244
|
+
}
|
|
245
|
+
async readdir(path) {
|
|
246
|
+
debug$1("readdir path=%s", path);
|
|
247
|
+
const { inodeId, attributes } = await this.resolveFollow(path);
|
|
248
|
+
if (attributes.inodeType !== "Directory") throw new Error(`ENOTDIR: not a directory, scandir '${path}'`);
|
|
249
|
+
const entries = await this.readAllDirectoryEntries(inodeId);
|
|
250
|
+
for (const e of entries) debug$1("readdir entry name=%j (bytes: %o)", e.name, Buffer.from(e.name));
|
|
251
|
+
return entries.map((e) => e.name).filter((name) => name !== "." && name !== "..");
|
|
252
|
+
}
|
|
253
|
+
async readdirWithFileTypes(path) {
|
|
254
|
+
const { inodeId, attributes } = await this.resolveFollow(path);
|
|
255
|
+
if (attributes.inodeType !== "Directory") throw new Error(`ENOTDIR: not a directory, scandir '${path}'`);
|
|
256
|
+
return (await this.readAllDirectoryEntries(inodeId)).filter((e) => e.name !== "." && e.name !== "..").map((e) => ({
|
|
257
|
+
name: e.name,
|
|
258
|
+
isFile: e.inodeType === "File",
|
|
259
|
+
isDirectory: e.inodeType === "Directory",
|
|
260
|
+
isSymbolicLink: e.inodeType === "Symlink"
|
|
261
|
+
}));
|
|
262
|
+
}
|
|
263
|
+
async stat(path) {
|
|
264
|
+
debug$1("stat path=%s", path);
|
|
265
|
+
const { attributes } = await this.resolveFollow(path);
|
|
266
|
+
return this.toStat(attributes);
|
|
267
|
+
}
|
|
268
|
+
async lstat(path) {
|
|
269
|
+
debug$1("lstat path=%s", path);
|
|
270
|
+
const { attributes } = await this.resolve(path);
|
|
271
|
+
return this.toStat(attributes);
|
|
272
|
+
}
|
|
273
|
+
async exists(path) {
|
|
274
|
+
debug$1("exists path=%s", path);
|
|
275
|
+
try {
|
|
276
|
+
await this.resolve(path);
|
|
277
|
+
debug$1("exists path=%s -> true", path);
|
|
278
|
+
return true;
|
|
279
|
+
} catch {
|
|
280
|
+
debug$1("exists path=%s -> false", path);
|
|
281
|
+
return false;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
async readlink(path) {
|
|
285
|
+
const { attributes } = await this.resolve(path);
|
|
286
|
+
if (attributes.inodeType !== "Symlink") throw new Error(`EINVAL: invalid argument, readlink '${path}'`);
|
|
287
|
+
return attributes.symlinkTarget || "";
|
|
288
|
+
}
|
|
289
|
+
async realpath(path, symlinkDepth = 0) {
|
|
290
|
+
const parts = this.normalizePath(path).split("/").filter((p) => p !== "");
|
|
291
|
+
let resolvedPath = "/";
|
|
292
|
+
let currentInodeId = this.rootInodeId;
|
|
293
|
+
for (const part of parts) {
|
|
294
|
+
const response = await this.client.lookupInode(currentInodeId, part, { user: this.user });
|
|
295
|
+
if (response === null) throw new Error(`ENOENT: no such file or directory, realpath '${path}'`);
|
|
296
|
+
const attrs = response.attributes;
|
|
297
|
+
if (attrs.inodeType === "Symlink" && attrs.symlinkTarget) {
|
|
298
|
+
if (symlinkDepth >= ArchilFs.MAX_SYMLINKS) throw new Error(`ELOOP: too many levels of symbolic links, '${path}'`);
|
|
299
|
+
const targetPath = attrs.symlinkTarget.startsWith("/") ? attrs.symlinkTarget : this.resolvePath(resolvedPath, attrs.symlinkTarget);
|
|
300
|
+
const resolved = await this.realpath(targetPath, symlinkDepth + 1);
|
|
301
|
+
resolvedPath = resolved;
|
|
302
|
+
const { inodeId } = await this.resolve(resolved);
|
|
303
|
+
currentInodeId = inodeId;
|
|
304
|
+
} else {
|
|
305
|
+
resolvedPath = resolvedPath === "/" ? "/" + part : resolvedPath + "/" + part;
|
|
306
|
+
currentInodeId = response.inodeId;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
return resolvedPath;
|
|
310
|
+
}
|
|
311
|
+
async writeFile(path, content) {
|
|
312
|
+
debug$1("writeFile path=%s contentLength=%d", path, content.length);
|
|
313
|
+
const data = typeof content === "string" ? new TextEncoder().encode(content) : content;
|
|
314
|
+
let resolved;
|
|
315
|
+
try {
|
|
316
|
+
resolved = await this.resolveFollow(path);
|
|
317
|
+
} catch (err) {
|
|
318
|
+
if (err instanceof Error && err.message.includes("ENOENT")) resolved = null;
|
|
319
|
+
else throw err;
|
|
320
|
+
}
|
|
321
|
+
if (resolved !== null) {
|
|
322
|
+
debug$1("writeFile resolved existing file path=%s inodeId=%d", path, resolved.inodeId);
|
|
323
|
+
if (resolved.attributes.inodeType !== "File") throw new Error(`EISDIR: illegal operation on a directory, write '${path}'`);
|
|
324
|
+
const realPath = await this.realpath(path);
|
|
325
|
+
const { parentInodeId, name } = await this.resolveParent(realPath);
|
|
326
|
+
const tmpName = `.~tmp-${Math.random().toString(36).slice(2)}`;
|
|
327
|
+
debug$1("writeFile atomic overwrite via temp=%s", tmpName);
|
|
328
|
+
const tmpResult = await this.client.create(parentInodeId, tmpName, {
|
|
329
|
+
inodeType: "File",
|
|
330
|
+
uid: resolved.attributes.uid,
|
|
331
|
+
gid: resolved.attributes.gid,
|
|
332
|
+
mode: resolved.attributes.mode
|
|
333
|
+
}, { user: this.user });
|
|
334
|
+
try {
|
|
335
|
+
await this.client.writeData(tmpResult.inodeId, 0, Buffer.from(data), { user: this.user });
|
|
336
|
+
await this.client.rename(parentInodeId, tmpName, parentInodeId, name, { user: this.user });
|
|
337
|
+
debug$1("writeFile atomic overwrite succeeded");
|
|
338
|
+
} catch (writeErr) {
|
|
339
|
+
try {
|
|
340
|
+
await this.client.unlink(parentInodeId, tmpName, { user: this.user });
|
|
341
|
+
} catch {}
|
|
342
|
+
throw writeErr;
|
|
343
|
+
}
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
debug$1("writeFile file doesn't exist, creating: %s", path);
|
|
347
|
+
const { parentInodeId, name } = await this.resolveParent(path);
|
|
348
|
+
debug$1("writeFile resolved parent parentInodeId=%d name=%s", parentInodeId, name);
|
|
349
|
+
const result = await this.client.create(parentInodeId, name, {
|
|
350
|
+
inodeType: "File",
|
|
351
|
+
uid: this.user?.uid ?? 0,
|
|
352
|
+
gid: this.user?.gid ?? 0,
|
|
353
|
+
mode: 420
|
|
354
|
+
}, { user: this.user });
|
|
355
|
+
debug$1("writeFile create succeeded inodeId=%d", result.inodeId);
|
|
356
|
+
await this.client.writeData(result.inodeId, 0, Buffer.from(data), { user: this.user });
|
|
357
|
+
debug$1("writeFile write succeeded");
|
|
358
|
+
}
|
|
359
|
+
async appendFile(path, content) {
|
|
360
|
+
const data = typeof content === "string" ? new TextEncoder().encode(content) : content;
|
|
361
|
+
let inodeId;
|
|
362
|
+
let size;
|
|
363
|
+
try {
|
|
364
|
+
const resolved = await this.resolveFollow(path);
|
|
365
|
+
if (resolved.attributes.inodeType !== "File") throw new Error(`EISDIR: illegal operation on a directory, write '${path}'`);
|
|
366
|
+
inodeId = resolved.inodeId;
|
|
367
|
+
size = Number(resolved.attributes.size);
|
|
368
|
+
} catch (err) {
|
|
369
|
+
if (err instanceof Error && err.message.includes("ENOENT")) {
|
|
370
|
+
await this.writeFile(path, data);
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
throw err;
|
|
374
|
+
}
|
|
375
|
+
await this.client.writeData(inodeId, size, Buffer.from(data), { user: this.user });
|
|
376
|
+
}
|
|
377
|
+
async mkdir(path, options) {
|
|
378
|
+
const normalizedPath = this.normalizePath(path);
|
|
379
|
+
if (options?.recursive) {
|
|
380
|
+
const parts = normalizedPath.split("/").filter((p) => p !== "");
|
|
381
|
+
let currentPath = "";
|
|
382
|
+
for (const part of parts) {
|
|
383
|
+
currentPath += "/" + part;
|
|
384
|
+
if (await this.exists(currentPath)) continue;
|
|
385
|
+
await this.mkdirSingle(currentPath);
|
|
386
|
+
}
|
|
387
|
+
} else await this.mkdirSingle(normalizedPath);
|
|
388
|
+
}
|
|
389
|
+
async mkdirSingle(path) {
|
|
390
|
+
const { parentInodeId, name } = await this.resolveParent(path);
|
|
391
|
+
await this.client.create(parentInodeId, name, {
|
|
392
|
+
inodeType: "Directory",
|
|
393
|
+
uid: this.user?.uid ?? 0,
|
|
394
|
+
gid: this.user?.gid ?? 0,
|
|
395
|
+
mode: 493
|
|
396
|
+
}, { user: this.user });
|
|
397
|
+
}
|
|
398
|
+
async rm(path, options) {
|
|
399
|
+
let resolved;
|
|
400
|
+
try {
|
|
401
|
+
resolved = await this.resolve(path);
|
|
402
|
+
} catch (err) {
|
|
403
|
+
if (err instanceof Error && err.message.includes("ENOENT")) {
|
|
404
|
+
if (options?.force) return;
|
|
405
|
+
throw err;
|
|
406
|
+
}
|
|
407
|
+
throw err;
|
|
408
|
+
}
|
|
409
|
+
if (resolved.attributes.inodeType === "Directory") {
|
|
410
|
+
if (!options?.recursive) throw new Error(`EISDIR: illegal operation on a directory, rm '${path}'`);
|
|
411
|
+
const entries = await this.readdir(path);
|
|
412
|
+
for (const entry of entries) await this.rm(this.resolvePath(path, entry), options);
|
|
413
|
+
}
|
|
414
|
+
const { parentInodeId, name } = await this.resolveParent(path);
|
|
415
|
+
await this.client.unlink(parentInodeId, name, { user: this.user });
|
|
416
|
+
}
|
|
417
|
+
async cp(src, dest, options) {
|
|
418
|
+
if ((await this.resolveFollow(src)).attributes.inodeType === "Directory") {
|
|
419
|
+
if (!options?.recursive) throw new Error(`EISDIR: illegal operation on a directory, cp '${src}'`);
|
|
420
|
+
await this.mkdir(dest, { recursive: true });
|
|
421
|
+
const entries = await this.readdir(src);
|
|
422
|
+
for (const entry of entries) await this.cp(this.resolvePath(src, entry), this.resolvePath(dest, entry), options);
|
|
423
|
+
} else {
|
|
424
|
+
const content = await this.readFileBuffer(src);
|
|
425
|
+
await this.writeFile(dest, content);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
async mv(src, dest) {
|
|
429
|
+
const { parentInodeId: srcParentInodeId, name: srcName } = await this.resolveParent(src);
|
|
430
|
+
const { parentInodeId: destParentInodeId, name: destName } = await this.resolveParent(dest);
|
|
431
|
+
await this.client.rename(srcParentInodeId, srcName, destParentInodeId, destName, { user: this.user });
|
|
432
|
+
}
|
|
433
|
+
async symlink(target, path) {
|
|
434
|
+
const { parentInodeId, name } = await this.resolveParent(path);
|
|
435
|
+
await this.client.create(parentInodeId, name, {
|
|
436
|
+
inodeType: "Symlink",
|
|
437
|
+
uid: this.user?.uid ?? 0,
|
|
438
|
+
gid: this.user?.gid ?? 0,
|
|
439
|
+
mode: 511,
|
|
440
|
+
symlinkTarget: target
|
|
441
|
+
}, { user: this.user });
|
|
442
|
+
}
|
|
443
|
+
async link(existingPath, newPath) {
|
|
444
|
+
throw new Error("Hard link operations not yet implemented. The archil-node bindings need to expose link for hard links.");
|
|
445
|
+
}
|
|
446
|
+
async chmod(path, mode) {
|
|
447
|
+
const { inodeId } = await this.resolveFollow(path);
|
|
448
|
+
await this.client.setattr(inodeId, { mode }, { user: this.user ?? {
|
|
449
|
+
uid: 0,
|
|
450
|
+
gid: 0
|
|
451
|
+
} });
|
|
452
|
+
}
|
|
453
|
+
async utimes(path, atime, mtime) {
|
|
454
|
+
debug$1("utimes path=%s atime=%s mtime=%s", path, atime.toISOString(), mtime.toISOString());
|
|
455
|
+
const { inodeId } = await this.resolveFollow(path);
|
|
456
|
+
debug$1("utimes resolved path=%s inodeId=%d", path, inodeId);
|
|
457
|
+
const atimeMs = atime.getTime();
|
|
458
|
+
const mtimeMs = mtime.getTime();
|
|
459
|
+
debug$1("utimes calling setattr inodeId=%d atimeMs=%d mtimeMs=%d", inodeId, atimeMs, mtimeMs);
|
|
460
|
+
await this.client.setattr(inodeId, {
|
|
461
|
+
atimeMs,
|
|
462
|
+
mtimeMs
|
|
463
|
+
}, { user: this.user ?? {
|
|
464
|
+
uid: 0,
|
|
465
|
+
gid: 0
|
|
466
|
+
} });
|
|
467
|
+
debug$1("utimes setattr succeeded inodeId=%d", inodeId);
|
|
468
|
+
}
|
|
469
|
+
getAllPaths() {
|
|
470
|
+
return [];
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* Resolve a path to its inode ID (public wrapper for delegation operations)
|
|
474
|
+
*/
|
|
475
|
+
async resolveInodeId(path) {
|
|
476
|
+
const { inodeId } = await this.resolve(path);
|
|
477
|
+
return inodeId;
|
|
478
|
+
}
|
|
565
479
|
};
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
480
|
+
//#endregion
|
|
481
|
+
//#region src/commands.ts
|
|
482
|
+
/**
|
|
483
|
+
* Create the `archil` custom command for use with just-bash.
|
|
484
|
+
*
|
|
485
|
+
* Provides checkout/checkin delegation management, delegation listing, and
|
|
486
|
+
* cache control (invalidate-cache, set-cache-expiry) as a shell command that
|
|
487
|
+
* works in scripts, pipes, and interactive use.
|
|
488
|
+
*
|
|
489
|
+
* @example
|
|
490
|
+
* ```typescript
|
|
491
|
+
* import { Bash } from 'just-bash';
|
|
492
|
+
* import { ArchilFs, createArchilCommand } from '@archildata/just-bash';
|
|
493
|
+
*
|
|
494
|
+
* const fs = await ArchilFs.create(client);
|
|
495
|
+
* const bash = new Bash({
|
|
496
|
+
* fs,
|
|
497
|
+
* customCommands: [createArchilCommand(client, fs)],
|
|
498
|
+
* });
|
|
499
|
+
*
|
|
500
|
+
* await bash.exec('archil checkout /mydir');
|
|
501
|
+
* await bash.exec('echo "hello" > /mydir/file.txt');
|
|
502
|
+
* await bash.exec('archil checkin /mydir');
|
|
503
|
+
* ```
|
|
504
|
+
*/
|
|
569
505
|
function createArchilCommand(client, fs) {
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
`);
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
`);
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
}
|
|
656
|
-
const pathArg = rest.find((a, i) => i !== flagIdx && i !== flagValueIdx && !a.startsWith("-"));
|
|
657
|
-
if (!pathArg) {
|
|
658
|
-
return fail("Usage: archil set-cache-expiry <path> --readdir-expiry <seconds>\n");
|
|
659
|
-
}
|
|
660
|
-
const fullPath = fs.resolvePath(ctx.cwd, pathArg);
|
|
661
|
-
try {
|
|
662
|
-
const inodeId = await fs.resolveInodeId(fullPath);
|
|
663
|
-
client.setCacheExpiry(inodeId, expirySeconds);
|
|
664
|
-
return ok(`Set readdir cache expiry for ${fullPath} (inode ${inodeId}) to ${expirySeconds}s
|
|
665
|
-
`);
|
|
666
|
-
} catch (err) {
|
|
667
|
-
return fail(`Failed to set cache expiry for ${fullPath}: ${err instanceof Error ? err.message : err}
|
|
668
|
-
`);
|
|
669
|
-
}
|
|
670
|
-
}
|
|
671
|
-
if (subcommand === "help" || !subcommand) {
|
|
672
|
-
return ok(
|
|
673
|
-
"Archil commands:\n archil checkout [--force|-f] <path> - Acquire write delegation\n archil checkin <path> - Release write delegation\n archil list-delegations - Show held delegations\n archil invalidate-cache [<path>] - Drop every evictable cache entry\n archil set-cache-expiry <path> --readdir-expiry <secs>\n - Set readdir cache TTL on a directory\n archil help - Show this help message\n\nThe --force flag revokes any existing delegation from other clients.\n"
|
|
674
|
-
);
|
|
675
|
-
}
|
|
676
|
-
return fail(`Unknown archil command: ${subcommand}
|
|
677
|
-
Run 'archil help' for available commands
|
|
678
|
-
`);
|
|
679
|
-
});
|
|
506
|
+
return (0, just_bash.defineCommand)("archil", async (args, ctx) => {
|
|
507
|
+
const ok = (stdout) => ({
|
|
508
|
+
stdout,
|
|
509
|
+
stderr: "",
|
|
510
|
+
exitCode: 0
|
|
511
|
+
});
|
|
512
|
+
const fail = (stderr) => ({
|
|
513
|
+
stdout: "",
|
|
514
|
+
stderr,
|
|
515
|
+
exitCode: 1
|
|
516
|
+
});
|
|
517
|
+
const subcommand = args[0];
|
|
518
|
+
if (subcommand === "checkout") {
|
|
519
|
+
const rest = args.slice(1);
|
|
520
|
+
const force = rest.includes("--force") || rest.includes("-f");
|
|
521
|
+
const pathArg = rest.find((a) => !a.startsWith("-"));
|
|
522
|
+
if (!pathArg) return fail("Usage: archil checkout [--force|-f] <path>\n");
|
|
523
|
+
const fullPath = fs.resolvePath(ctx.cwd, pathArg);
|
|
524
|
+
try {
|
|
525
|
+
const inodeId = await fs.resolveInodeId(fullPath);
|
|
526
|
+
await client.checkout(inodeId, { force });
|
|
527
|
+
return ok(`Checked out: ${fullPath} (inode ${inodeId})${force ? " (forced)" : ""}\n`);
|
|
528
|
+
} catch (err) {
|
|
529
|
+
return fail(`Failed to checkout ${fullPath}: ${err instanceof Error ? err.message : err}\n`);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
if (subcommand === "checkin") {
|
|
533
|
+
const pathArg = args[1];
|
|
534
|
+
if (!pathArg) return fail("Usage: archil checkin <path>\n");
|
|
535
|
+
const fullPath = fs.resolvePath(ctx.cwd, pathArg);
|
|
536
|
+
try {
|
|
537
|
+
const inodeId = await fs.resolveInodeId(fullPath);
|
|
538
|
+
await client.checkin(inodeId);
|
|
539
|
+
return ok(`Checked in: ${fullPath} (inode ${inodeId})\n`);
|
|
540
|
+
} catch (err) {
|
|
541
|
+
return fail(`Failed to checkin ${fullPath}: ${err instanceof Error ? err.message : err}\n`);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
if (subcommand === "list-delegations" || subcommand === "delegations") try {
|
|
545
|
+
const delegations = client.listDelegations();
|
|
546
|
+
if (delegations.length === 0) return ok("No delegations held\n");
|
|
547
|
+
return ok("Delegations:\n" + delegations.map((d) => ` inode ${d.inodeId}: ${d.state}`).join("\n") + "\n");
|
|
548
|
+
} catch (err) {
|
|
549
|
+
return fail(`Failed to list delegations: ${err instanceof Error ? err.message : err}\n`);
|
|
550
|
+
}
|
|
551
|
+
if (subcommand === "invalidate-cache") {
|
|
552
|
+
const pathArg = args.slice(1).find((a) => !a.startsWith("-"));
|
|
553
|
+
let scopeLabel = "(mount-wide)";
|
|
554
|
+
if (pathArg) {
|
|
555
|
+
const fullPath = fs.resolvePath(ctx.cwd, pathArg);
|
|
556
|
+
try {
|
|
557
|
+
await fs.resolveInodeId(fullPath);
|
|
558
|
+
scopeLabel = `(via ${fullPath})`;
|
|
559
|
+
} catch (err) {
|
|
560
|
+
return fail(`Failed to resolve ${fullPath}: ${err instanceof Error ? err.message : err}\n`);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
try {
|
|
564
|
+
const stats = client.invalidateCache();
|
|
565
|
+
return ok(`Invalidated cache ${scopeLabel}: ${stats.totalEvictedInodes} inodes evicted (attr=${stats.attribute}, xattr=${stats.extendedAttribute}, dirent=${stats.dirent}, file=${stats.fileData})\n`);
|
|
566
|
+
} catch (err) {
|
|
567
|
+
return fail(`Failed to invalidate cache: ${err instanceof Error ? err.message : err}\n`);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
if (subcommand === "set-cache-expiry") {
|
|
571
|
+
const rest = args.slice(1);
|
|
572
|
+
const flagIdx = rest.indexOf("--readdir-expiry");
|
|
573
|
+
if (flagIdx === -1 || flagIdx === rest.length - 1) return fail("Usage: archil set-cache-expiry <path> --readdir-expiry <seconds>\n");
|
|
574
|
+
const flagValueIdx = flagIdx + 1;
|
|
575
|
+
const expirySeconds = Number(rest[flagValueIdx]);
|
|
576
|
+
if (!Number.isInteger(expirySeconds) || expirySeconds < 0) return fail("--readdir-expiry must be a non-negative integer (seconds)\n");
|
|
577
|
+
const pathArg = rest.find((a, i) => i !== flagIdx && i !== flagValueIdx && !a.startsWith("-"));
|
|
578
|
+
if (!pathArg) return fail("Usage: archil set-cache-expiry <path> --readdir-expiry <seconds>\n");
|
|
579
|
+
const fullPath = fs.resolvePath(ctx.cwd, pathArg);
|
|
580
|
+
try {
|
|
581
|
+
const inodeId = await fs.resolveInodeId(fullPath);
|
|
582
|
+
client.setCacheExpiry(inodeId, expirySeconds);
|
|
583
|
+
return ok(`Set readdir cache expiry for ${fullPath} (inode ${inodeId}) to ${expirySeconds}s\n`);
|
|
584
|
+
} catch (err) {
|
|
585
|
+
return fail(`Failed to set cache expiry for ${fullPath}: ${err instanceof Error ? err.message : err}\n`);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
if (subcommand === "help" || !subcommand) return ok("Archil commands:\n archil checkout [--force|-f] <path> - Acquire write delegation\n archil checkin <path> - Release write delegation\n archil list-delegations - Show held delegations\n archil invalidate-cache [<path>] - Drop every evictable cache entry\n archil set-cache-expiry <path> --readdir-expiry <secs>\n - Set readdir cache TTL on a directory\n archil help - Show this help message\n\nThe --force flag revokes any existing delegation from other clients.\n");
|
|
589
|
+
return fail(`Unknown archil command: ${subcommand}\nRun 'archil help' for available commands\n`);
|
|
590
|
+
});
|
|
680
591
|
}
|
|
681
|
-
|
|
682
|
-
|
|
592
|
+
//#endregion
|
|
593
|
+
//#region src/index.ts
|
|
594
|
+
/**
|
|
595
|
+
* @archildata/just-bash - Archil filesystem adapter for just-bash
|
|
596
|
+
*
|
|
597
|
+
* This package provides a filesystem adapter that allows just-bash to use
|
|
598
|
+
* Archil distributed filesystems as a backend.
|
|
599
|
+
*
|
|
600
|
+
* @example
|
|
601
|
+
* ```typescript
|
|
602
|
+
* import { ArchilClient } from '@archildata/native';
|
|
603
|
+
* import { ArchilFs } from '@archildata/just-bash';
|
|
604
|
+
* import { Bash } from 'just-bash';
|
|
605
|
+
*
|
|
606
|
+
* // Connect to Archil
|
|
607
|
+
* const client = await ArchilClient.connect({
|
|
608
|
+
* region: 'aws-us-east-1',
|
|
609
|
+
* diskName: 'myaccount/mydisk',
|
|
610
|
+
* authToken: 'adt_xxx',
|
|
611
|
+
* });
|
|
612
|
+
*
|
|
613
|
+
* // Create filesystem adapter
|
|
614
|
+
* const fs = await ArchilFs.create(client);
|
|
615
|
+
*
|
|
616
|
+
* // Use with just-bash
|
|
617
|
+
* const bash = new Bash({ fs });
|
|
618
|
+
* const result = await bash.run('ls -la /');
|
|
619
|
+
* console.log(result.stdout);
|
|
620
|
+
* ```
|
|
621
|
+
*
|
|
622
|
+
* @packageDocumentation
|
|
623
|
+
*/
|
|
624
|
+
/**
|
|
625
|
+
* Create an ArchilFs instance, optionally rooted at a subdirectory.
|
|
626
|
+
*
|
|
627
|
+
* @param client - Connected ArchilClient instance
|
|
628
|
+
* @param options - Optional configuration
|
|
629
|
+
* @returns Configured ArchilFs instance
|
|
630
|
+
*
|
|
631
|
+
* @example
|
|
632
|
+
* ```typescript
|
|
633
|
+
* import { ArchilClient } from '@archildata/native';
|
|
634
|
+
* import { createArchilFs } from '@archildata/just-bash';
|
|
635
|
+
*
|
|
636
|
+
* const client = await ArchilClient.connectAuthenticated({...});
|
|
637
|
+
* const fs = await createArchilFs(client, { user: { uid: 1000, gid: 1000 } });
|
|
638
|
+
* ```
|
|
639
|
+
*/
|
|
683
640
|
async function createArchilFs(client, options) {
|
|
684
|
-
|
|
641
|
+
return ArchilFs.create(client, options);
|
|
685
642
|
}
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
createArchilFs
|
|
691
|
-
});
|
|
643
|
+
//#endregion
|
|
644
|
+
exports.ArchilFs = ArchilFs;
|
|
645
|
+
exports.createArchilCommand = createArchilCommand;
|
|
646
|
+
exports.createArchilFs = createArchilFs;
|