@archildata/just-bash 0.1.9 → 0.1.11
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/README.md +1 -3
- package/package.json +4 -4
- package/dist/index.d.mts +0 -185
- package/dist/index.mjs +0 -522
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @archildata/just-bash
|
|
2
2
|
|
|
3
|
-
Archil filesystem adapter for [just-bash](https://
|
|
3
|
+
Archil filesystem adapter for [just-bash](https://www.npmjs.com/package/just-bash) - run bash commands against Archil distributed filesystems.
|
|
4
4
|
|
|
5
5
|
Launch an interactive shell against your Archil disk:
|
|
6
6
|
|
|
@@ -130,7 +130,6 @@ new ArchilFs(client: ArchilClient, options?: { user?: UnixUser })
|
|
|
130
130
|
|
|
131
131
|
#### Utilities
|
|
132
132
|
- `resolveInodeId(path)` - Get inode ID for delegation operations
|
|
133
|
-
- `clearCache()` - Clear path cache
|
|
134
133
|
|
|
135
134
|
## Debugging
|
|
136
135
|
|
|
@@ -146,7 +145,6 @@ DEBUG=archil:* node myapp.js
|
|
|
146
145
|
|
|
147
146
|
## Performance
|
|
148
147
|
|
|
149
|
-
- **Path-to-inode caching** - Avoids repeated directory lookups
|
|
150
148
|
- **Chunked reads** - Large files read in 4 MiB chunks
|
|
151
149
|
- **Native protocol** - Direct Archil protocol access, no FUSE overhead
|
|
152
150
|
|
package/package.json
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@archildata/just-bash",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.11",
|
|
4
4
|
"description": "Archil filesystem adapter for just-bash",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"main": "dist/index.
|
|
6
|
+
"main": "dist/index.cjs",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
8
8
|
"exports": {
|
|
9
9
|
".": {
|
|
10
10
|
"types": "./dist/index.d.ts",
|
|
11
|
-
"import": "./dist/index.
|
|
12
|
-
"require": "./dist/index.
|
|
11
|
+
"import": "./dist/index.js",
|
|
12
|
+
"require": "./dist/index.cjs"
|
|
13
13
|
}
|
|
14
14
|
},
|
|
15
15
|
"bin": {
|
package/dist/index.d.mts
DELETED
|
@@ -1,185 +0,0 @@
|
|
|
1
|
-
import * as _archil_node from '@archil/node';
|
|
2
|
-
import { ArchilClient, UnixUser } from '@archil/node';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Archil filesystem adapter for just-bash
|
|
6
|
-
*
|
|
7
|
-
* Implements the IFileSystem interface from just-bash using the ArchilClient
|
|
8
|
-
* from @archil/node for direct protocol access to Archil distributed filesystems.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
type BufferEncoding = "utf8" | "utf-8" | "ascii" | "binary" | "base64" | "hex" | "latin1";
|
|
12
|
-
type FileContent = string | Uint8Array;
|
|
13
|
-
interface FsStat {
|
|
14
|
-
isFile(): boolean;
|
|
15
|
-
isDirectory(): boolean;
|
|
16
|
-
isSymlink(): boolean;
|
|
17
|
-
mode: number;
|
|
18
|
-
size: number;
|
|
19
|
-
mtimeMs: number;
|
|
20
|
-
atimeMs: number;
|
|
21
|
-
ctimeMs: number;
|
|
22
|
-
birthtimeMs: number;
|
|
23
|
-
}
|
|
24
|
-
interface DirentEntry {
|
|
25
|
-
name: string;
|
|
26
|
-
isFile(): boolean;
|
|
27
|
-
isDirectory(): boolean;
|
|
28
|
-
isSymlink(): boolean;
|
|
29
|
-
}
|
|
30
|
-
interface IFileSystem {
|
|
31
|
-
readFile(path: string, encoding?: BufferEncoding): Promise<string>;
|
|
32
|
-
readFileBuffer(path: string): Promise<Uint8Array>;
|
|
33
|
-
readdir(path: string): Promise<string[]>;
|
|
34
|
-
readdirWithFileTypes?(path: string): Promise<DirentEntry[]>;
|
|
35
|
-
stat(path: string): Promise<FsStat>;
|
|
36
|
-
lstat(path: string): Promise<FsStat>;
|
|
37
|
-
exists(path: string): Promise<boolean>;
|
|
38
|
-
readlink(path: string): Promise<string>;
|
|
39
|
-
realpath(path: string): Promise<string>;
|
|
40
|
-
writeFile(path: string, content: FileContent): Promise<void>;
|
|
41
|
-
appendFile(path: string, content: FileContent): Promise<void>;
|
|
42
|
-
mkdir(path: string, options?: {
|
|
43
|
-
recursive?: boolean;
|
|
44
|
-
}): Promise<void>;
|
|
45
|
-
rm(path: string, options?: {
|
|
46
|
-
recursive?: boolean;
|
|
47
|
-
force?: boolean;
|
|
48
|
-
}): Promise<void>;
|
|
49
|
-
cp(src: string, dest: string, options?: {
|
|
50
|
-
recursive?: boolean;
|
|
51
|
-
}): Promise<void>;
|
|
52
|
-
mv(src: string, dest: string): Promise<void>;
|
|
53
|
-
symlink(target: string, path: string): Promise<void>;
|
|
54
|
-
link(existingPath: string, newPath: string): Promise<void>;
|
|
55
|
-
chmod(path: string, mode: number): Promise<void>;
|
|
56
|
-
utimes(path: string, atime: number, mtime: number): Promise<void>;
|
|
57
|
-
resolvePath(base: string, ...paths: string[]): string;
|
|
58
|
-
getAllPaths?(): Promise<string[]>;
|
|
59
|
-
}
|
|
60
|
-
/**
|
|
61
|
-
* ArchilFs implements the just-bash IFileSystem interface using Archil protocol.
|
|
62
|
-
*
|
|
63
|
-
* This adapter provides:
|
|
64
|
-
* - Path-to-inode resolution with caching
|
|
65
|
-
* - Full filesystem operations via Archil protocol
|
|
66
|
-
* - Optional user context for permission checks
|
|
67
|
-
*
|
|
68
|
-
* @example
|
|
69
|
-
* ```typescript
|
|
70
|
-
* import { ArchilClient } from '@archil/node';
|
|
71
|
-
* import { ArchilFs } from '@archil/just-bash';
|
|
72
|
-
*
|
|
73
|
-
* const client = await ArchilClient.connectAuthenticated({
|
|
74
|
-
* mountServer: 'mount.archil.com:443',
|
|
75
|
-
* mountName: 'myaccount/mydisk',
|
|
76
|
-
* });
|
|
77
|
-
*
|
|
78
|
-
* const fs = new ArchilFs(client);
|
|
79
|
-
*
|
|
80
|
-
* // Use with just-bash
|
|
81
|
-
* import { Bash } from 'just-bash';
|
|
82
|
-
* const bash = new Bash({ fs });
|
|
83
|
-
* await bash.run('ls -la /');
|
|
84
|
-
* ```
|
|
85
|
-
*/
|
|
86
|
-
declare class ArchilFs implements IFileSystem {
|
|
87
|
-
private client;
|
|
88
|
-
private inodeCache;
|
|
89
|
-
private user?;
|
|
90
|
-
private rootInodeId;
|
|
91
|
-
/**
|
|
92
|
-
* Create a new ArchilFs adapter
|
|
93
|
-
*
|
|
94
|
-
* @param client - Connected ArchilClient instance
|
|
95
|
-
* @param options - Optional configuration
|
|
96
|
-
* @param options.user - Unix user context for permission checks
|
|
97
|
-
*/
|
|
98
|
-
constructor(client: ArchilClient, options?: {
|
|
99
|
-
user?: UnixUser;
|
|
100
|
-
});
|
|
101
|
-
/**
|
|
102
|
-
* Normalize a path (remove . and .., ensure leading /)
|
|
103
|
-
*/
|
|
104
|
-
private normalizePath;
|
|
105
|
-
/**
|
|
106
|
-
* Resolve a path to its inode ID, walking the directory tree
|
|
107
|
-
*/
|
|
108
|
-
private resolve;
|
|
109
|
-
/**
|
|
110
|
-
* Resolve parent directory and get child name
|
|
111
|
-
*/
|
|
112
|
-
private resolveParent;
|
|
113
|
-
/**
|
|
114
|
-
* Invalidate cache entries for a path and its descendants
|
|
115
|
-
*/
|
|
116
|
-
private invalidateCache;
|
|
117
|
-
/**
|
|
118
|
-
* Convert InodeAttributes to FsStat
|
|
119
|
-
*/
|
|
120
|
-
private toStat;
|
|
121
|
-
resolvePath(base: string, ...paths: string[]): string;
|
|
122
|
-
readFile(path: string, encoding?: BufferEncoding): Promise<string>;
|
|
123
|
-
readFileBuffer(path: string): Promise<Uint8Array>;
|
|
124
|
-
readdir(path: string): Promise<string[]>;
|
|
125
|
-
readdirWithFileTypes(path: string): Promise<DirentEntry[]>;
|
|
126
|
-
stat(path: string): Promise<FsStat>;
|
|
127
|
-
lstat(path: string): Promise<FsStat>;
|
|
128
|
-
exists(path: string): Promise<boolean>;
|
|
129
|
-
readlink(path: string): Promise<string>;
|
|
130
|
-
realpath(path: string): Promise<string>;
|
|
131
|
-
writeFile(path: string, content: FileContent): Promise<void>;
|
|
132
|
-
appendFile(path: string, content: FileContent): Promise<void>;
|
|
133
|
-
mkdir(path: string, options?: {
|
|
134
|
-
recursive?: boolean;
|
|
135
|
-
}): Promise<void>;
|
|
136
|
-
private mkdirSingle;
|
|
137
|
-
rm(path: string, options?: {
|
|
138
|
-
recursive?: boolean;
|
|
139
|
-
force?: boolean;
|
|
140
|
-
}): Promise<void>;
|
|
141
|
-
cp(src: string, dest: string, options?: {
|
|
142
|
-
recursive?: boolean;
|
|
143
|
-
}): Promise<void>;
|
|
144
|
-
mv(src: string, dest: string): Promise<void>;
|
|
145
|
-
symlink(target: string, path: string): Promise<void>;
|
|
146
|
-
link(existingPath: string, newPath: string): Promise<void>;
|
|
147
|
-
chmod(path: string, mode: number): Promise<void>;
|
|
148
|
-
utimes(path: string, atime: number, mtime: number): Promise<void>;
|
|
149
|
-
getAllPaths(): Promise<string[]>;
|
|
150
|
-
/**
|
|
151
|
-
* Clear the inode cache
|
|
152
|
-
*/
|
|
153
|
-
clearCache(): void;
|
|
154
|
-
/**
|
|
155
|
-
* Get cache statistics
|
|
156
|
-
*/
|
|
157
|
-
getCacheStats(): {
|
|
158
|
-
size: number;
|
|
159
|
-
paths: string[];
|
|
160
|
-
};
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* Create an ArchilFs instance from an existing client
|
|
165
|
-
*
|
|
166
|
-
* This is a convenience function for creating the filesystem adapter.
|
|
167
|
-
*
|
|
168
|
-
* @param client - Connected ArchilClient instance
|
|
169
|
-
* @param options - Optional configuration
|
|
170
|
-
* @returns Configured ArchilFs instance
|
|
171
|
-
*
|
|
172
|
-
* @example
|
|
173
|
-
* ```typescript
|
|
174
|
-
* import { ArchilClient } from '@archil/node';
|
|
175
|
-
* import { createArchilFs } from '@archil/just-bash';
|
|
176
|
-
*
|
|
177
|
-
* const client = await ArchilClient.connectAuthenticated({...});
|
|
178
|
-
* const fs = createArchilFs(client, { user: { uid: 1000, gid: 1000 } });
|
|
179
|
-
* ```
|
|
180
|
-
*/
|
|
181
|
-
declare function createArchilFs(client: _archil_node.ArchilClient, options?: {
|
|
182
|
-
user?: _archil_node.UnixUser;
|
|
183
|
-
}): ArchilFs;
|
|
184
|
-
|
|
185
|
-
export { ArchilFs, type BufferEncoding, type DirentEntry, type FileContent, type FsStat, type IFileSystem, createArchilFs };
|
package/dist/index.mjs
DELETED
|
@@ -1,522 +0,0 @@
|
|
|
1
|
-
var __defProp = Object.defineProperty;
|
|
2
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
-
var __esm = (fn, res) => function __init() {
|
|
6
|
-
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
7
|
-
};
|
|
8
|
-
var __export = (target, all) => {
|
|
9
|
-
for (var name in all)
|
|
10
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
-
};
|
|
12
|
-
var __copyProps = (to, from, except, desc) => {
|
|
13
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
-
for (let key of __getOwnPropNames(from))
|
|
15
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
-
}
|
|
18
|
-
return to;
|
|
19
|
-
};
|
|
20
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
21
|
-
|
|
22
|
-
// src/ArchilFs.ts
|
|
23
|
-
var ArchilFs_exports = {};
|
|
24
|
-
__export(ArchilFs_exports, {
|
|
25
|
-
ArchilFs: () => ArchilFs
|
|
26
|
-
});
|
|
27
|
-
var ArchilFs;
|
|
28
|
-
var init_ArchilFs = __esm({
|
|
29
|
-
"src/ArchilFs.ts"() {
|
|
30
|
-
"use strict";
|
|
31
|
-
ArchilFs = class {
|
|
32
|
-
client;
|
|
33
|
-
inodeCache;
|
|
34
|
-
user;
|
|
35
|
-
rootInodeId = 1;
|
|
36
|
-
/**
|
|
37
|
-
* Create a new ArchilFs adapter
|
|
38
|
-
*
|
|
39
|
-
* @param client - Connected ArchilClient instance
|
|
40
|
-
* @param options - Optional configuration
|
|
41
|
-
* @param options.user - Unix user context for permission checks
|
|
42
|
-
*/
|
|
43
|
-
constructor(client, options) {
|
|
44
|
-
this.client = client;
|
|
45
|
-
this.user = options?.user;
|
|
46
|
-
this.inodeCache = /* @__PURE__ */ new Map();
|
|
47
|
-
this.inodeCache.set("/", this.rootInodeId);
|
|
48
|
-
}
|
|
49
|
-
// ========================================================================
|
|
50
|
-
// Path Resolution
|
|
51
|
-
// ========================================================================
|
|
52
|
-
/**
|
|
53
|
-
* Normalize a path (remove . and .., ensure leading /)
|
|
54
|
-
*/
|
|
55
|
-
normalizePath(path) {
|
|
56
|
-
if (!path || path === "") {
|
|
57
|
-
return "/";
|
|
58
|
-
}
|
|
59
|
-
if (!path.startsWith("/")) {
|
|
60
|
-
path = "/" + path;
|
|
61
|
-
}
|
|
62
|
-
const parts = path.split("/").filter((p) => p !== "" && p !== ".");
|
|
63
|
-
const result = [];
|
|
64
|
-
for (const part of parts) {
|
|
65
|
-
if (part === "..") {
|
|
66
|
-
result.pop();
|
|
67
|
-
} else {
|
|
68
|
-
result.push(part);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
return "/" + result.join("/");
|
|
72
|
-
}
|
|
73
|
-
/**
|
|
74
|
-
* Resolve a path to its inode ID, walking the directory tree
|
|
75
|
-
*/
|
|
76
|
-
async resolve(path) {
|
|
77
|
-
const normalizedPath = this.normalizePath(path);
|
|
78
|
-
const cachedInode = this.inodeCache.get(normalizedPath);
|
|
79
|
-
if (cachedInode !== void 0) {
|
|
80
|
-
const attributes2 = await this.client.getAttributes(cachedInode, this.user);
|
|
81
|
-
return { inodeId: cachedInode, attributes: attributes2 };
|
|
82
|
-
}
|
|
83
|
-
const parts = normalizedPath.split("/").filter((p) => p !== "");
|
|
84
|
-
let currentInodeId = this.rootInodeId;
|
|
85
|
-
let currentPath = "";
|
|
86
|
-
for (const part of parts) {
|
|
87
|
-
currentPath += "/" + part;
|
|
88
|
-
const cached = this.inodeCache.get(currentPath);
|
|
89
|
-
if (cached !== void 0) {
|
|
90
|
-
currentInodeId = cached;
|
|
91
|
-
continue;
|
|
92
|
-
}
|
|
93
|
-
const response = await this.client.lookupInode(currentInodeId, part, this.user);
|
|
94
|
-
if (response.inodeId === -1) {
|
|
95
|
-
throw new Error(`ENOENT: no such file or directory, '${path}'`);
|
|
96
|
-
}
|
|
97
|
-
currentInodeId = response.inodeId;
|
|
98
|
-
this.inodeCache.set(currentPath, currentInodeId);
|
|
99
|
-
}
|
|
100
|
-
const attributes = await this.client.getAttributes(currentInodeId, this.user);
|
|
101
|
-
return { inodeId: currentInodeId, attributes };
|
|
102
|
-
}
|
|
103
|
-
/**
|
|
104
|
-
* Resolve parent directory and get child name
|
|
105
|
-
*/
|
|
106
|
-
async resolveParent(path) {
|
|
107
|
-
const normalizedPath = this.normalizePath(path);
|
|
108
|
-
const lastSlash = normalizedPath.lastIndexOf("/");
|
|
109
|
-
const parentPath = lastSlash === 0 ? "/" : normalizedPath.substring(0, lastSlash);
|
|
110
|
-
const name = normalizedPath.substring(lastSlash + 1);
|
|
111
|
-
const { inodeId: parentInodeId } = await this.resolve(parentPath);
|
|
112
|
-
return { parentInodeId, name };
|
|
113
|
-
}
|
|
114
|
-
/**
|
|
115
|
-
* Invalidate cache entries for a path and its descendants
|
|
116
|
-
*/
|
|
117
|
-
invalidateCache(path) {
|
|
118
|
-
const normalizedPath = this.normalizePath(path);
|
|
119
|
-
for (const cachedPath of this.inodeCache.keys()) {
|
|
120
|
-
if (cachedPath === normalizedPath || cachedPath.startsWith(normalizedPath + "/")) {
|
|
121
|
-
this.inodeCache.delete(cachedPath);
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
/**
|
|
126
|
-
* Convert InodeAttributes to FsStat
|
|
127
|
-
*/
|
|
128
|
-
toStat(attrs) {
|
|
129
|
-
const isFile = attrs.inodeType === "File";
|
|
130
|
-
const isDirectory = attrs.inodeType === "Directory";
|
|
131
|
-
const isSymlink = attrs.inodeType === "Symlink";
|
|
132
|
-
return {
|
|
133
|
-
isFile: () => isFile,
|
|
134
|
-
isDirectory: () => isDirectory,
|
|
135
|
-
isSymlink: () => isSymlink,
|
|
136
|
-
mode: attrs.mode,
|
|
137
|
-
size: Number(attrs.size),
|
|
138
|
-
mtimeMs: attrs.mtimeMs,
|
|
139
|
-
atimeMs: attrs.atimeMs,
|
|
140
|
-
ctimeMs: attrs.ctimeMs,
|
|
141
|
-
birthtimeMs: attrs.btimeMs
|
|
142
|
-
};
|
|
143
|
-
}
|
|
144
|
-
// ========================================================================
|
|
145
|
-
// IFileSystem Implementation - Read Operations
|
|
146
|
-
// ========================================================================
|
|
147
|
-
resolvePath(base, ...paths) {
|
|
148
|
-
let result = base;
|
|
149
|
-
for (const p of paths) {
|
|
150
|
-
if (p.startsWith("/")) {
|
|
151
|
-
result = p;
|
|
152
|
-
} else {
|
|
153
|
-
result = result.endsWith("/") ? result + p : result + "/" + p;
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
return this.normalizePath(result);
|
|
157
|
-
}
|
|
158
|
-
async readFile(path, encoding) {
|
|
159
|
-
const buffer = await this.readFileBuffer(path);
|
|
160
|
-
const decoder = new TextDecoder(encoding || "utf-8");
|
|
161
|
-
return decoder.decode(buffer);
|
|
162
|
-
}
|
|
163
|
-
async readFileBuffer(path) {
|
|
164
|
-
const { inodeId, attributes } = await this.resolve(path);
|
|
165
|
-
if (attributes.inodeType !== "File") {
|
|
166
|
-
throw new Error(`EISDIR: illegal operation on a directory, read '${path}'`);
|
|
167
|
-
}
|
|
168
|
-
const size = Number(attributes.size);
|
|
169
|
-
if (size === 0) {
|
|
170
|
-
return new Uint8Array(0);
|
|
171
|
-
}
|
|
172
|
-
const MAX_CHUNK = 4 * 1024 * 1024;
|
|
173
|
-
if (size <= MAX_CHUNK) {
|
|
174
|
-
const buffer = await this.client.readInode(inodeId, 0, size, this.user);
|
|
175
|
-
return new Uint8Array(buffer);
|
|
176
|
-
}
|
|
177
|
-
const result = new Uint8Array(size);
|
|
178
|
-
let offset = 0;
|
|
179
|
-
while (offset < size) {
|
|
180
|
-
const chunkSize = Math.min(MAX_CHUNK, size - offset);
|
|
181
|
-
const chunk = await this.client.readInode(inodeId, offset, chunkSize, this.user);
|
|
182
|
-
result.set(new Uint8Array(chunk), offset);
|
|
183
|
-
offset += chunkSize;
|
|
184
|
-
}
|
|
185
|
-
return result;
|
|
186
|
-
}
|
|
187
|
-
async readdir(path) {
|
|
188
|
-
const { inodeId, attributes } = await this.resolve(path);
|
|
189
|
-
if (attributes.inodeType !== "Directory") {
|
|
190
|
-
throw new Error(`ENOTDIR: not a directory, scandir '${path}'`);
|
|
191
|
-
}
|
|
192
|
-
const entries = await this.client.readDirectory(inodeId, void 0, void 0, this.user);
|
|
193
|
-
return entries.map((e) => e.name).filter((name) => name !== "." && name !== "..");
|
|
194
|
-
}
|
|
195
|
-
async readdirWithFileTypes(path) {
|
|
196
|
-
const { inodeId, attributes } = await this.resolve(path);
|
|
197
|
-
if (attributes.inodeType !== "Directory") {
|
|
198
|
-
throw new Error(`ENOTDIR: not a directory, scandir '${path}'`);
|
|
199
|
-
}
|
|
200
|
-
const entries = await this.client.readDirectory(inodeId, void 0, void 0, this.user);
|
|
201
|
-
return entries.filter((e) => e.name !== "." && e.name !== "..").map((e) => ({
|
|
202
|
-
name: e.name,
|
|
203
|
-
isFile: () => e.inodeType === "File",
|
|
204
|
-
isDirectory: () => e.inodeType === "Directory",
|
|
205
|
-
isSymlink: () => e.inodeType === "Symlink"
|
|
206
|
-
}));
|
|
207
|
-
}
|
|
208
|
-
async stat(path) {
|
|
209
|
-
const { attributes } = await this.resolve(path);
|
|
210
|
-
if (attributes.inodeType === "Symlink" && attributes.symlinkTarget) {
|
|
211
|
-
const targetPath = attributes.symlinkTarget.startsWith("/") ? attributes.symlinkTarget : this.resolvePath(path, "..", attributes.symlinkTarget);
|
|
212
|
-
return this.stat(targetPath);
|
|
213
|
-
}
|
|
214
|
-
return this.toStat(attributes);
|
|
215
|
-
}
|
|
216
|
-
async lstat(path) {
|
|
217
|
-
const { attributes } = await this.resolve(path);
|
|
218
|
-
return this.toStat(attributes);
|
|
219
|
-
}
|
|
220
|
-
async exists(path) {
|
|
221
|
-
try {
|
|
222
|
-
await this.resolve(path);
|
|
223
|
-
return true;
|
|
224
|
-
} catch {
|
|
225
|
-
return false;
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
async readlink(path) {
|
|
229
|
-
const { attributes } = await this.resolve(path);
|
|
230
|
-
if (attributes.inodeType !== "Symlink") {
|
|
231
|
-
throw new Error(`EINVAL: invalid argument, readlink '${path}'`);
|
|
232
|
-
}
|
|
233
|
-
return attributes.symlinkTarget || "";
|
|
234
|
-
}
|
|
235
|
-
async realpath(path) {
|
|
236
|
-
const normalizedPath = this.normalizePath(path);
|
|
237
|
-
const parts = normalizedPath.split("/").filter((p) => p !== "");
|
|
238
|
-
let resolvedPath = "/";
|
|
239
|
-
let currentInodeId = this.rootInodeId;
|
|
240
|
-
for (const part of parts) {
|
|
241
|
-
const response = await this.client.lookupInode(currentInodeId, part, this.user);
|
|
242
|
-
if (response.inodeId === -1) {
|
|
243
|
-
throw new Error(`ENOENT: no such file or directory, realpath '${path}'`);
|
|
244
|
-
}
|
|
245
|
-
const attrs = response.attributes;
|
|
246
|
-
if (attrs.inodeType === "Symlink" && attrs.symlinkTarget) {
|
|
247
|
-
const targetPath = attrs.symlinkTarget.startsWith("/") ? attrs.symlinkTarget : this.resolvePath(resolvedPath, attrs.symlinkTarget);
|
|
248
|
-
const resolved = await this.realpath(targetPath);
|
|
249
|
-
resolvedPath = resolved;
|
|
250
|
-
const { inodeId } = await this.resolve(resolved);
|
|
251
|
-
currentInodeId = inodeId;
|
|
252
|
-
} else {
|
|
253
|
-
resolvedPath = resolvedPath === "/" ? "/" + part : resolvedPath + "/" + part;
|
|
254
|
-
currentInodeId = response.inodeId;
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
return resolvedPath;
|
|
258
|
-
}
|
|
259
|
-
// ========================================================================
|
|
260
|
-
// IFileSystem Implementation - Write Operations
|
|
261
|
-
// ========================================================================
|
|
262
|
-
async writeFile(path, content) {
|
|
263
|
-
const data = typeof content === "string" ? new TextEncoder().encode(content) : content;
|
|
264
|
-
let inodeId;
|
|
265
|
-
let isNewFile = false;
|
|
266
|
-
try {
|
|
267
|
-
const resolved = await this.resolve(path);
|
|
268
|
-
inodeId = resolved.inodeId;
|
|
269
|
-
if (resolved.attributes.inodeType !== "File") {
|
|
270
|
-
throw new Error(`EISDIR: illegal operation on a directory, write '${path}'`);
|
|
271
|
-
}
|
|
272
|
-
await this.client.checkout(inodeId, false, this.user);
|
|
273
|
-
} catch (err) {
|
|
274
|
-
if (err instanceof Error && err.message.includes("EISDIR")) {
|
|
275
|
-
throw err;
|
|
276
|
-
}
|
|
277
|
-
const { parentInodeId, name } = await this.resolveParent(path);
|
|
278
|
-
const range = await this.client.reserveInodes(1);
|
|
279
|
-
inodeId = range.start;
|
|
280
|
-
const now = Date.now();
|
|
281
|
-
await this.client.conditionalCreate(
|
|
282
|
-
parentInodeId,
|
|
283
|
-
name,
|
|
284
|
-
inodeId,
|
|
285
|
-
{
|
|
286
|
-
inodeId: Number(inodeId),
|
|
287
|
-
inodeType: "File",
|
|
288
|
-
size: 0,
|
|
289
|
-
uid: this.user?.uid ?? 0,
|
|
290
|
-
gid: this.user?.gid ?? 0,
|
|
291
|
-
mode: 420,
|
|
292
|
-
nlink: 1,
|
|
293
|
-
ctimeMs: now,
|
|
294
|
-
atimeMs: now,
|
|
295
|
-
mtimeMs: now,
|
|
296
|
-
btimeMs: now,
|
|
297
|
-
rdev: void 0,
|
|
298
|
-
symlinkTarget: void 0
|
|
299
|
-
},
|
|
300
|
-
this.user
|
|
301
|
-
);
|
|
302
|
-
this.inodeCache.set(this.normalizePath(path), inodeId);
|
|
303
|
-
isNewFile = true;
|
|
304
|
-
}
|
|
305
|
-
try {
|
|
306
|
-
await this.client.writeData(inodeId, 0, Buffer.from(data), this.user);
|
|
307
|
-
} finally {
|
|
308
|
-
if (!isNewFile) {
|
|
309
|
-
try {
|
|
310
|
-
await this.client.checkin(inodeId, this.user);
|
|
311
|
-
} catch {
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
async appendFile(path, content) {
|
|
317
|
-
let existing;
|
|
318
|
-
try {
|
|
319
|
-
existing = await this.readFileBuffer(path);
|
|
320
|
-
} catch {
|
|
321
|
-
existing = new Uint8Array(0);
|
|
322
|
-
}
|
|
323
|
-
const data = typeof content === "string" ? new TextEncoder().encode(content) : content;
|
|
324
|
-
const combined = new Uint8Array(existing.length + data.length);
|
|
325
|
-
combined.set(existing, 0);
|
|
326
|
-
combined.set(data, existing.length);
|
|
327
|
-
await this.writeFile(path, combined);
|
|
328
|
-
}
|
|
329
|
-
async mkdir(path, options) {
|
|
330
|
-
const normalizedPath = this.normalizePath(path);
|
|
331
|
-
if (options?.recursive) {
|
|
332
|
-
const parts = normalizedPath.split("/").filter((p) => p !== "");
|
|
333
|
-
let currentPath = "";
|
|
334
|
-
for (const part of parts) {
|
|
335
|
-
currentPath += "/" + part;
|
|
336
|
-
const exists = await this.exists(currentPath);
|
|
337
|
-
if (exists) {
|
|
338
|
-
continue;
|
|
339
|
-
}
|
|
340
|
-
await this.mkdirSingle(currentPath);
|
|
341
|
-
}
|
|
342
|
-
} else {
|
|
343
|
-
await this.mkdirSingle(normalizedPath);
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
async mkdirSingle(path) {
|
|
347
|
-
const { parentInodeId, name } = await this.resolveParent(path);
|
|
348
|
-
const range = await this.client.reserveInodes(1);
|
|
349
|
-
const inodeId = range.start;
|
|
350
|
-
const now = Date.now();
|
|
351
|
-
await this.client.conditionalCreate(
|
|
352
|
-
parentInodeId,
|
|
353
|
-
name,
|
|
354
|
-
inodeId,
|
|
355
|
-
{
|
|
356
|
-
inodeId: Number(inodeId),
|
|
357
|
-
inodeType: "Directory",
|
|
358
|
-
size: 4096,
|
|
359
|
-
uid: this.user?.uid ?? 0,
|
|
360
|
-
gid: this.user?.gid ?? 0,
|
|
361
|
-
mode: 493,
|
|
362
|
-
nlink: 2,
|
|
363
|
-
ctimeMs: now,
|
|
364
|
-
atimeMs: now,
|
|
365
|
-
mtimeMs: now,
|
|
366
|
-
btimeMs: now,
|
|
367
|
-
rdev: void 0,
|
|
368
|
-
symlinkTarget: void 0
|
|
369
|
-
},
|
|
370
|
-
this.user
|
|
371
|
-
);
|
|
372
|
-
this.inodeCache.set(this.normalizePath(path), inodeId);
|
|
373
|
-
}
|
|
374
|
-
async rm(path, options) {
|
|
375
|
-
let resolved;
|
|
376
|
-
try {
|
|
377
|
-
resolved = await this.resolve(path);
|
|
378
|
-
} catch {
|
|
379
|
-
if (options?.force) {
|
|
380
|
-
return;
|
|
381
|
-
}
|
|
382
|
-
throw new Error(`ENOENT: no such file or directory, rm '${path}'`);
|
|
383
|
-
}
|
|
384
|
-
if (resolved.attributes.inodeType === "Directory") {
|
|
385
|
-
if (!options?.recursive) {
|
|
386
|
-
throw new Error(`EISDIR: illegal operation on a directory, rm '${path}'`);
|
|
387
|
-
}
|
|
388
|
-
const entries = await this.readdir(path);
|
|
389
|
-
for (const entry of entries) {
|
|
390
|
-
await this.rm(this.resolvePath(path, entry), options);
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
const { parentInodeId, name } = await this.resolveParent(path);
|
|
394
|
-
await this.client.unlink(parentInodeId, name, this.user);
|
|
395
|
-
this.invalidateCache(path);
|
|
396
|
-
}
|
|
397
|
-
async cp(src, dest, options) {
|
|
398
|
-
const srcResolved = await this.resolve(src);
|
|
399
|
-
if (srcResolved.attributes.inodeType === "Directory") {
|
|
400
|
-
if (!options?.recursive) {
|
|
401
|
-
throw new Error(`EISDIR: illegal operation on a directory, cp '${src}'`);
|
|
402
|
-
}
|
|
403
|
-
await this.mkdir(dest, { recursive: true });
|
|
404
|
-
const entries = await this.readdir(src);
|
|
405
|
-
for (const entry of entries) {
|
|
406
|
-
await this.cp(
|
|
407
|
-
this.resolvePath(src, entry),
|
|
408
|
-
this.resolvePath(dest, entry),
|
|
409
|
-
options
|
|
410
|
-
);
|
|
411
|
-
}
|
|
412
|
-
} else {
|
|
413
|
-
const content = await this.readFileBuffer(src);
|
|
414
|
-
await this.writeFile(dest, content);
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
async mv(src, dest) {
|
|
418
|
-
const srcResolved = await this.resolve(src);
|
|
419
|
-
if (srcResolved.attributes.inodeType === "Directory") {
|
|
420
|
-
await this.cp(src, dest, { recursive: true });
|
|
421
|
-
} else {
|
|
422
|
-
const content = await this.readFileBuffer(src);
|
|
423
|
-
await this.writeFile(dest, content);
|
|
424
|
-
}
|
|
425
|
-
await this.rm(src, { recursive: true, force: true });
|
|
426
|
-
}
|
|
427
|
-
async symlink(target, path) {
|
|
428
|
-
const { parentInodeId, name } = await this.resolveParent(path);
|
|
429
|
-
const range = await this.client.reserveInodes(1);
|
|
430
|
-
const inodeId = range.start;
|
|
431
|
-
const now = Date.now();
|
|
432
|
-
await this.client.conditionalCreate(
|
|
433
|
-
parentInodeId,
|
|
434
|
-
name,
|
|
435
|
-
inodeId,
|
|
436
|
-
{
|
|
437
|
-
inodeId: Number(inodeId),
|
|
438
|
-
inodeType: "Symlink",
|
|
439
|
-
size: target.length,
|
|
440
|
-
uid: this.user?.uid ?? 0,
|
|
441
|
-
gid: this.user?.gid ?? 0,
|
|
442
|
-
mode: 511,
|
|
443
|
-
nlink: 1,
|
|
444
|
-
ctimeMs: now,
|
|
445
|
-
atimeMs: now,
|
|
446
|
-
mtimeMs: now,
|
|
447
|
-
btimeMs: now,
|
|
448
|
-
rdev: void 0,
|
|
449
|
-
symlinkTarget: target
|
|
450
|
-
},
|
|
451
|
-
this.user
|
|
452
|
-
);
|
|
453
|
-
this.inodeCache.set(this.normalizePath(path), inodeId);
|
|
454
|
-
}
|
|
455
|
-
async link(existingPath, newPath) {
|
|
456
|
-
throw new Error(
|
|
457
|
-
"Hard link operations not yet implemented. The archil-node bindings need to expose link for hard links."
|
|
458
|
-
);
|
|
459
|
-
}
|
|
460
|
-
async chmod(path, mode) {
|
|
461
|
-
throw new Error(
|
|
462
|
-
"chmod not yet implemented. The archil-node bindings need to expose setattr for permission changes."
|
|
463
|
-
);
|
|
464
|
-
}
|
|
465
|
-
async utimes(path, atime, mtime) {
|
|
466
|
-
throw new Error(
|
|
467
|
-
"utimes not yet implemented. The archil-node bindings need to expose setattr for timestamp changes."
|
|
468
|
-
);
|
|
469
|
-
}
|
|
470
|
-
// ========================================================================
|
|
471
|
-
// Utility Methods
|
|
472
|
-
// ========================================================================
|
|
473
|
-
async getAllPaths() {
|
|
474
|
-
const paths = [];
|
|
475
|
-
const walk = async (dirPath) => {
|
|
476
|
-
paths.push(dirPath);
|
|
477
|
-
try {
|
|
478
|
-
const entries = await this.readdirWithFileTypes(dirPath);
|
|
479
|
-
for (const entry of entries) {
|
|
480
|
-
const fullPath = this.resolvePath(dirPath, entry.name);
|
|
481
|
-
if (entry.isDirectory()) {
|
|
482
|
-
await walk(fullPath);
|
|
483
|
-
} else {
|
|
484
|
-
paths.push(fullPath);
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
} catch {
|
|
488
|
-
}
|
|
489
|
-
};
|
|
490
|
-
await walk("/");
|
|
491
|
-
return paths;
|
|
492
|
-
}
|
|
493
|
-
/**
|
|
494
|
-
* Clear the inode cache
|
|
495
|
-
*/
|
|
496
|
-
clearCache() {
|
|
497
|
-
this.inodeCache.clear();
|
|
498
|
-
this.inodeCache.set("/", this.rootInodeId);
|
|
499
|
-
}
|
|
500
|
-
/**
|
|
501
|
-
* Get cache statistics
|
|
502
|
-
*/
|
|
503
|
-
getCacheStats() {
|
|
504
|
-
return {
|
|
505
|
-
size: this.inodeCache.size,
|
|
506
|
-
paths: Array.from(this.inodeCache.keys())
|
|
507
|
-
};
|
|
508
|
-
}
|
|
509
|
-
};
|
|
510
|
-
}
|
|
511
|
-
});
|
|
512
|
-
|
|
513
|
-
// src/index.ts
|
|
514
|
-
init_ArchilFs();
|
|
515
|
-
function createArchilFs(client, options) {
|
|
516
|
-
const { ArchilFs: ArchilFs2 } = (init_ArchilFs(), __toCommonJS(ArchilFs_exports));
|
|
517
|
-
return new ArchilFs2(client, options);
|
|
518
|
-
}
|
|
519
|
-
export {
|
|
520
|
-
ArchilFs,
|
|
521
|
-
createArchilFs
|
|
522
|
-
};
|