shfs 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,15 @@
1
+ # shfs
2
+
3
+ To install dependencies:
4
+
5
+ ```bash
6
+ bun install
7
+ ```
8
+
9
+ To run:
10
+
11
+ ```bash
12
+ bun run index.ts
13
+ ```
14
+
15
+ This project was created using `bun init` in bun v1.3.5. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime.
@@ -0,0 +1,21 @@
1
+ //#region src/stream.d.ts
2
+ type Stream<T> = AsyncIterable<T>;
3
+ //#endregion
4
+ //#region src/fs/fs.d.ts
5
+ interface FS {
6
+ readFile(path: string): Promise<Uint8Array>;
7
+ readLines(path: string): Stream<string>;
8
+ writeFile(path: string, content: Uint8Array): Promise<void>;
9
+ deleteFile(path: string): Promise<void>;
10
+ readdir(path: string): Stream<string>;
11
+ mkdir(path: string, recursive?: boolean): Promise<void>;
12
+ stat(path: string): Promise<{
13
+ isDirectory: boolean;
14
+ size: number;
15
+ mtime: Date;
16
+ }>;
17
+ exists(path: string): Promise<boolean>;
18
+ }
19
+ //#endregion
20
+ export { Stream as n, FS as t };
21
+ //# sourceMappingURL=fs-DSmBon4m.d.mts.map
package/dist/fs.d.mts ADDED
@@ -0,0 +1,25 @@
1
+ import { n as Stream, t as FS } from "./fs-DSmBon4m.mjs";
2
+
3
+ //#region src/fs/memory.d.ts
4
+ declare class MemoryFS implements FS {
5
+ private readonly files;
6
+ private readonly directories;
7
+ private readonly fileMetadata;
8
+ constructor();
9
+ setFile(path: string, content: string | Uint8Array): void;
10
+ readFile(path: string): Promise<Uint8Array>;
11
+ readLines(path: string): Stream<string>;
12
+ writeFile(path: string, content: Uint8Array): Promise<void>;
13
+ deleteFile(path: string): Promise<void>;
14
+ readdir(glob: string): Stream<string>;
15
+ mkdir(path: string, recursive?: boolean): Promise<void>;
16
+ stat(path: string): Promise<{
17
+ isDirectory: boolean;
18
+ size: number;
19
+ mtime: Date;
20
+ }>;
21
+ exists(path: string): Promise<boolean>;
22
+ }
23
+ //#endregion
24
+ export { type FS, MemoryFS };
25
+ //# sourceMappingURL=fs.d.mts.map
package/dist/fs.mjs ADDED
@@ -0,0 +1,95 @@
1
+ //#region src/fs/memory.ts
2
+ const TRAILING_SLASH_REGEX = /\/$/;
3
+ var MemoryFS = class {
4
+ files = /* @__PURE__ */ new Map();
5
+ directories = /* @__PURE__ */ new Set();
6
+ fileMetadata = /* @__PURE__ */ new Map();
7
+ constructor() {
8
+ this.directories.add("/");
9
+ this.fileMetadata.set("/", {
10
+ mtime: /* @__PURE__ */ new Date(),
11
+ isDirectory: true
12
+ });
13
+ }
14
+ setFile(path, content) {
15
+ const encoded = typeof content === "string" ? new TextEncoder().encode(content) : content;
16
+ this.files.set(path, encoded);
17
+ this.fileMetadata.set(path, {
18
+ mtime: /* @__PURE__ */ new Date(),
19
+ isDirectory: false
20
+ });
21
+ }
22
+ async readFile(path) {
23
+ const content = this.files.get(path);
24
+ if (!content) throw new Error(`File not found: ${path}`);
25
+ return content;
26
+ }
27
+ async *readLines(path) {
28
+ const content = this.files.get(path);
29
+ if (!content) throw new Error(`File not found: ${path}`);
30
+ yield* new TextDecoder().decode(content).split("\n").filter((_, i, arr) => !(i === arr.length - 1 && arr[i] === ""));
31
+ }
32
+ async writeFile(path, content) {
33
+ this.files.set(path, content);
34
+ }
35
+ async deleteFile(path) {
36
+ if (!this.files.has(path)) throw new Error(`File not found: ${path}`);
37
+ this.files.delete(path);
38
+ }
39
+ async *readdir(glob) {
40
+ const pattern = glob.replace(/\*\*/g, ".*").replace(/\*/g, "[^/]*").replace(/\?/g, "[^/]").replace(/\./g, "\\.");
41
+ const regex = /* @__PURE__ */ new RegExp(`^${pattern}$`);
42
+ const paths = Array.from(this.files.keys()).filter((path) => regex.test(path)).sort();
43
+ for (const path of paths) yield path;
44
+ }
45
+ async mkdir(path, recursive = false) {
46
+ if (this.directories.has(path) || this.files.has(path)) throw new Error(`Directory already exists: ${path}`);
47
+ if (recursive) {
48
+ const parts = path.split("/").filter(Boolean);
49
+ let current = "";
50
+ for (const part of parts) {
51
+ current += `/${part}`;
52
+ if (!(this.directories.has(current) || this.files.has(current))) {
53
+ this.directories.add(current);
54
+ this.fileMetadata.set(current, {
55
+ mtime: /* @__PURE__ */ new Date(),
56
+ isDirectory: true
57
+ });
58
+ }
59
+ }
60
+ } else {
61
+ const parentPath = path.substring(0, path.lastIndexOf("/")) || "/";
62
+ if (parentPath !== "/" && !this.directories.has(parentPath) && !this.files.has(parentPath)) throw new Error(`No such file or directory: ${parentPath}`);
63
+ this.directories.add(path);
64
+ this.fileMetadata.set(path, {
65
+ mtime: /* @__PURE__ */ new Date(),
66
+ isDirectory: true
67
+ });
68
+ }
69
+ }
70
+ async stat(path) {
71
+ const normalizedPath = path.replace(TRAILING_SLASH_REGEX, "") || "/";
72
+ if (this.directories.has(normalizedPath)) return {
73
+ isDirectory: true,
74
+ size: 0,
75
+ mtime: this.fileMetadata.get(normalizedPath)?.mtime || /* @__PURE__ */ new Date()
76
+ };
77
+ if (this.files.has(normalizedPath)) {
78
+ const content = this.files.get(normalizedPath);
79
+ if (content === void 0) throw new Error(`No such file or directory: ${path}`);
80
+ return {
81
+ isDirectory: false,
82
+ size: content.byteLength,
83
+ mtime: this.fileMetadata.get(normalizedPath)?.mtime || /* @__PURE__ */ new Date()
84
+ };
85
+ }
86
+ throw new Error(`No such file or directory: ${path}`);
87
+ }
88
+ async exists(path) {
89
+ return this.files.has(path) || this.directories.has(path);
90
+ }
91
+ };
92
+
93
+ //#endregion
94
+ export { MemoryFS };
95
+ //# sourceMappingURL=fs.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fs.mjs","names":[],"sources":["../src/fs/memory.ts"],"sourcesContent":["import type { Stream } from '../stream';\nimport type { FS } from './fs';\n\nexport type { FS } from './fs';\n\nconst TRAILING_SLASH_REGEX = /\\/$/;\n\nexport class MemoryFS implements FS {\n\tprivate readonly files = new Map<string, Uint8Array>();\n\tprivate readonly directories = new Set<string>();\n\tprivate readonly fileMetadata = new Map<\n\t\tstring,\n\t\t{ mtime: Date; isDirectory: boolean }\n\t>();\n\n\tconstructor() {\n\t\t// Initialize root directory\n\t\tthis.directories.add('/');\n\t\tthis.fileMetadata.set('/', { mtime: new Date(), isDirectory: true });\n\t}\n\n\tsetFile(path: string, content: string | Uint8Array): void {\n\t\tconst encoded =\n\t\t\ttypeof content === 'string'\n\t\t\t\t? new TextEncoder().encode(content)\n\t\t\t\t: content;\n\t\tthis.files.set(path, encoded);\n\t\tthis.fileMetadata.set(path, { mtime: new Date(), isDirectory: false });\n\t}\n\n\tasync readFile(path: string): Promise<Uint8Array> {\n\t\tconst content = this.files.get(path);\n\t\tif (!content) {\n\t\t\tthrow new Error(`File not found: ${path}`);\n\t\t}\n\t\treturn content;\n\t}\n\n\tasync *readLines(path: string): Stream<string> {\n\t\tconst content = this.files.get(path);\n\t\tif (!content) {\n\t\t\tthrow new Error(`File not found: ${path}`);\n\t\t}\n\t\tconst text = new TextDecoder().decode(content);\n\t\tconst lines = text\n\t\t\t.split('\\n')\n\t\t\t.filter((_, i, arr) => !(i === arr.length - 1 && arr[i] === ''));\n\t\tyield* lines;\n\t}\n\n\tasync writeFile(path: string, content: Uint8Array): Promise<void> {\n\t\tthis.files.set(path, content);\n\t}\n\n\tasync deleteFile(path: string): Promise<void> {\n\t\tif (!this.files.has(path)) {\n\t\t\tthrow new Error(`File not found: ${path}`);\n\t\t}\n\t\tthis.files.delete(path);\n\t}\n\n\tasync *readdir(glob: string): Stream<string> {\n\t\tconst pattern = glob\n\t\t\t.replace(/\\*\\*/g, '.*')\n\t\t\t.replace(/\\*/g, '[^/]*')\n\t\t\t.replace(/\\?/g, '[^/]')\n\t\t\t.replace(/\\./g, '\\\\.');\n\n\t\tconst regex = new RegExp(`^${pattern}$`);\n\n\t\tconst paths = Array.from(this.files.keys())\n\t\t\t.filter((path) => regex.test(path))\n\t\t\t.sort();\n\n\t\tfor (const path of paths) {\n\t\t\tyield path;\n\t\t}\n\t}\n\n\tasync mkdir(path: string, recursive = false): Promise<void> {\n\t\tif (this.directories.has(path) || this.files.has(path)) {\n\t\t\tthrow new Error(`Directory already exists: ${path}`);\n\t\t}\n\n\t\tif (recursive) {\n\t\t\t// Create all parent directories\n\t\t\tconst parts = path.split('/').filter(Boolean);\n\t\t\tlet current = '';\n\t\t\tfor (const part of parts) {\n\t\t\t\tcurrent += `/${part}`;\n\t\t\t\tif (\n\t\t\t\t\t!(this.directories.has(current) || this.files.has(current))\n\t\t\t\t) {\n\t\t\t\t\tthis.directories.add(current);\n\t\t\t\t\tthis.fileMetadata.set(current, {\n\t\t\t\t\t\tmtime: new Date(),\n\t\t\t\t\t\tisDirectory: true,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\t// Check if parent directory exists\n\t\t\tconst parentPath = path.substring(0, path.lastIndexOf('/')) || '/';\n\t\t\tif (\n\t\t\t\tparentPath !== '/' &&\n\t\t\t\t!this.directories.has(parentPath) &&\n\t\t\t\t!this.files.has(parentPath)\n\t\t\t) {\n\t\t\t\tthrow new Error(`No such file or directory: ${parentPath}`);\n\t\t\t}\n\t\t\tthis.directories.add(path);\n\t\t\tthis.fileMetadata.set(path, {\n\t\t\t\tmtime: new Date(),\n\t\t\t\tisDirectory: true,\n\t\t\t});\n\t\t}\n\t}\n\n\tasync stat(\n\t\tpath: string\n\t): Promise<{ isDirectory: boolean; size: number; mtime: Date }> {\n\t\t// Normalize path by removing trailing slash\n\t\tconst normalizedPath = path.replace(TRAILING_SLASH_REGEX, '') || '/';\n\n\t\tif (this.directories.has(normalizedPath)) {\n\t\t\treturn {\n\t\t\t\tisDirectory: true,\n\t\t\t\tsize: 0,\n\t\t\t\tmtime:\n\t\t\t\t\tthis.fileMetadata.get(normalizedPath)?.mtime || new Date(),\n\t\t\t};\n\t\t}\n\n\t\tif (this.files.has(normalizedPath)) {\n\t\t\tconst content = this.files.get(normalizedPath);\n\t\t\tif (content === undefined) {\n\t\t\t\tthrow new Error(`No such file or directory: ${path}`);\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tisDirectory: false,\n\t\t\t\tsize: content.byteLength,\n\t\t\t\tmtime:\n\t\t\t\t\tthis.fileMetadata.get(normalizedPath)?.mtime || new Date(),\n\t\t\t};\n\t\t}\n\n\t\tthrow new Error(`No such file or directory: ${path}`);\n\t}\n\n\tasync exists(path: string): Promise<boolean> {\n\t\treturn this.files.has(path) || this.directories.has(path);\n\t}\n}\n"],"mappings":"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"}
@@ -0,0 +1,46 @@
1
+ import { t as FS } from "./fs-DSmBon4m.mjs";
2
+
3
+ //#region src/record.d.ts
4
+ interface FileRecord {
5
+ kind: 'file';
6
+ path: string;
7
+ }
8
+ interface LineRecord {
9
+ kind: 'line';
10
+ text: string;
11
+ file?: string;
12
+ lineNum?: number;
13
+ }
14
+ interface JsonRecord {
15
+ kind: 'json';
16
+ value: unknown;
17
+ }
18
+ /**
19
+ * Record is the unit of data flowing through pipelines.
20
+ * Commands operate on records, not bytes.
21
+ */
22
+ type Record = FileRecord | LineRecord | JsonRecord;
23
+ //#endregion
24
+ //#region src/shell/shell.d.ts
25
+ declare class Shell {
26
+ private readonly fs;
27
+ constructor(fs: FS);
28
+ $: (strings: TemplateStringsArray, ...exprs: unknown[]) => {
29
+ json(): Promise<unknown[]>;
30
+ lines(): Promise<string[]>;
31
+ raw(): Promise<Record[]>;
32
+ stdout(): Promise<void>;
33
+ text(): Promise<string>;
34
+ };
35
+ exec(strings: TemplateStringsArray, ...exprs: unknown[]): {
36
+ json(): Promise<unknown[]>;
37
+ lines(): Promise<string[]>;
38
+ raw(): Promise<Record[]>;
39
+ stdout(): Promise<void>;
40
+ text(): Promise<string>;
41
+ };
42
+ private _exec;
43
+ }
44
+ //#endregion
45
+ export { Shell };
46
+ //# sourceMappingURL=index.d.mts.map