@stablemodels/durable-bash 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.
@@ -0,0 +1,20 @@
1
+ Review all changes in the current branch compared to main and automatically bump the package version.
2
+
3
+ ## Steps
4
+
5
+ 1. **Diff analysis**: Run `git diff main...HEAD` to see all changes in this branch. Also run `git log --oneline main..HEAD` to understand the commit history.
6
+
7
+ 2. **Determine version bump**:
8
+ - **patch** (e.g. 0.1.0 → 0.1.1): Bug fixes, documentation changes, refactors, test additions, CI changes — anything that doesn't change the public API.
9
+ - **minor** (e.g. 0.1.0 → 0.2.0): New features, new exports, new methods on public classes, new options added to existing methods — any additive change to the public API.
10
+ - Never bump major unless explicitly told to.
11
+
12
+ 3. **Review the changes**: Provide a concise summary of what changed, organized by category (features, fixes, docs, tests, CI, etc.). Flag any concerns.
13
+
14
+ 4. **Bump the version**: Run `npm version <patch|minor> --no-git-tag-version` to update package.json. Do NOT create a git tag — the version in package.json is what npm publish uses.
15
+
16
+ 5. **Verify**: Run `bun test && bun run check && bun run build` to make sure everything still passes.
17
+
18
+ 6. **Commit**: Stage the package.json change and commit with message: `chore: bump version to <new-version>`.
19
+
20
+ 7. **Report**: Show the old version, new version, bump type, and the reasoning for the bump decision.
@@ -0,0 +1,20 @@
1
+ name: CI
2
+
3
+ on:
4
+ pull_request:
5
+ branches: [main]
6
+ push:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/checkout@v4
14
+ - uses: oven-sh/setup-bun@v2
15
+ with:
16
+ bun-version: latest
17
+ - run: bun install --frozen-lockfile
18
+ - run: bun run check
19
+ - run: bun run build
20
+ - run: bun test
@@ -0,0 +1,28 @@
1
+ name: Publish to NPM
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+
7
+ jobs:
8
+ publish:
9
+ runs-on: ubuntu-latest
10
+ permissions:
11
+ contents: read
12
+ id-token: write
13
+ steps:
14
+ - uses: actions/checkout@v4
15
+ - uses: oven-sh/setup-bun@v2
16
+ with:
17
+ bun-version: latest
18
+ - uses: actions/setup-node@v4
19
+ with:
20
+ node-version: 20
21
+ registry-url: https://registry.npmjs.org
22
+ - run: bun install --frozen-lockfile
23
+ - run: bun run check
24
+ - run: bun run build
25
+ - run: bun test
26
+ - run: npm publish --provenance --access public
27
+ env:
28
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
package/CLAUDE.md ADDED
@@ -0,0 +1,62 @@
1
+ # durable-bash
2
+
3
+ Cloudflare Durable Object-backed `IFileSystem` adapter for `just-bash`.
4
+
5
+ ## Architecture
6
+
7
+ Two core classes, one adapter pattern:
8
+
9
+ - **`FsObject`** (`src/fs-object.ts`) — Durable Object with SQLite storage. Single `files` table stores paths, content (BLOB), metadata, and optional symlink targets. All methods are **synchronous** and exposed via RPC. This is where all filesystem logic lives.
10
+ - **`DurableFs`** (`src/durable-fs.ts`) — Thin adapter implementing `IFileSystem` from `just-bash`. Every method delegates to `FsObject` via a DO stub. Maintains a `Set<string>` cache for the synchronous `getAllPaths()` method. Use the static `DurableFs.create(namespace, name)` factory for construction — it handles stub creation and initial cache sync.
11
+ - **`src/errors.ts`** — `FsError` class and factory functions: `ENOENT`, `EEXIST`, `EISDIR`, `ENOTDIR`, `ENOTEMPTY`.
12
+ - **`src/types.ts`** — Wire-format types for RPC: `FsStatData`, `DirentData`.
13
+ - **`src/index.ts`** — Public API re-exports. Two entry points: `durable-bash` (adapter + errors + types) and `durable-bash/object` (DO class).
14
+
15
+ ## Important Points
16
+
17
+ - **`IFileSystem` uses boolean properties**, not methods: `stat.isFile`, not `stat.isFile()`. `FsStat` and `DirentEntry` in `durable-fs.ts` define these shapes.
18
+ - **`getAllPaths()` is synchronous** but the DO is async. Solved with eager cache sync (handled by `DurableFs.create()`) + `Set` cache. `addToCache` walks ancestor dirs so auto-created parents are included.
19
+ - **Auto-parent creation**: `writeFile("/a/b/c.txt", ...)` auto-creates `/a` and `/a/b` via `ensureParentDirs` using `INSERT OR IGNORE`.
20
+ - **Symlinks**: stored as rows with `symlink_target` set. `resolveSymlinks()` follows chains up to 20 levels. `readFile`/`stat` follow symlinks; `lstat` does not.
21
+ - **`link()` copies content** at call time (not POSIX shared-inode semantics). This is by design per `IFileSystem` contract.
22
+ - **Content stored as BLOB** — `toBytes()` encodes strings to UTF-8 `Uint8Array`; binary is stored as-is.
23
+ - Mode constants: `DIR_MODE = 0o755`, `FILE_MODE = 0o644`, `SYMLINK_MODE = 0o777`.
24
+
25
+ ## Commands
26
+
27
+ ```bash
28
+ bun test # all tests (121 tests across 4 files)
29
+ bun run test:unit # unit tests only (fs-object + durable-fs)
30
+ bun run test:integration # integration + smoke tests
31
+ bun run build # tsc → dist/
32
+ bun run check # biome lint
33
+ bun run format # biome format (tabs, write)
34
+ ```
35
+
36
+ ## Testing
37
+
38
+ Tests use `bun:test` with `bun:sqlite` to simulate DO SQLite storage. The `cloudflare:workers` module is mocked via `tests/setup.ts` (preloaded in `bunfig.toml`).
39
+
40
+ - `tests/helpers.ts` — shared `createMockState()`, `createTestFsObject()`, `createDirectStub()` (Proxy wrapping sync methods as Promises for RPC simulation)
41
+ - `tests/fs-object.test.ts` — Unit tests for FsObject
42
+ - `tests/durable-fs.test.ts` — Unit tests for DurableFs with mock stub
43
+ - `tests/integration.test.ts` — DurableFs + FsObject wired together
44
+ - `tests/just-bash.test.ts` — Bash commands through DurableFs
45
+
46
+ ## Extending the Package
47
+
48
+ **Adding a new filesystem operation:**
49
+ 1. Add the synchronous method to `FsObject` in `src/fs-object.ts`
50
+ 2. Add the async wrapper in `DurableFs` in `src/durable-fs.ts` — resolve paths with `this.resolve()`, call `this.stub.<method>()`, update cache with `addToCache`/`removeFromCache` if paths change
51
+ 3. If new wire-format types are needed, add them to `src/types.ts`
52
+ 4. Export any new public types from `src/index.ts`
53
+ 5. Add tests to `tests/fs-object.test.ts` (unit) and `tests/integration.test.ts` (e2e)
54
+
55
+ **Adding a new error code:**
56
+ 1. Add a factory function in `src/errors.ts` following the `ENOENT` pattern
57
+
58
+ **Key conventions:**
59
+ - Paths are always normalized (leading `/`, no trailing `/`, `.`/`..` resolved)
60
+ - `FsObject` methods are synchronous — no async/await inside the DO
61
+ - `DurableFs` cache must stay in sync: use `addToCache` for creates, `removeFromCache` for deletes, `sync()` for bulk operations like `cp`/`mv`
62
+ - Biome enforces tab indentation and recommended lint rules
package/README.md ADDED
@@ -0,0 +1,97 @@
1
+ # durable-bash
2
+
3
+ Cloudflare Durable Object-backed [`IFileSystem`](https://www.npmjs.com/package/just-bash) adapter for [just-bash](https://www.npmjs.com/package/just-bash). Lets bash commands persist files in a Durable Object's SQLite storage.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install @stablemodels/durable-bash
9
+ ```
10
+
11
+ Peer dependencies: `just-bash`, `@cloudflare/workers-types`
12
+
13
+ ## Usage
14
+
15
+ ```typescript
16
+ import { Bash } from "just-bash";
17
+ import { DurableFs } from "@stablemodels/durable-bash";
18
+
19
+ // In a Cloudflare Worker:
20
+ const fs = await DurableFs.create(env.FS, "my-agent");
21
+ const bash = new Bash({ fs, cwd: "/home" });
22
+ const result = await bash.exec('echo "hello" > greeting.txt && cat greeting.txt');
23
+ console.log(result.stdout); // "hello\n"
24
+ ```
25
+
26
+ ### Wrangler config
27
+
28
+ ```toml
29
+ [[durable_objects.bindings]]
30
+ name = "FS"
31
+ class_name = "FsObject"
32
+
33
+ [[migrations]]
34
+ tag = "v1"
35
+ new_sqlite_classes = ["FsObject"]
36
+ ```
37
+
38
+ Re-export the DO class from your worker:
39
+
40
+ ```typescript
41
+ export { FsObject } from "@stablemodels/durable-bash/object";
42
+ ```
43
+
44
+ ## API
45
+
46
+ `DurableFs` implements `IFileSystem` from `just-bash`:
47
+
48
+ - `readFile(path)` / `readFileBuffer(path)` — Read file contents
49
+ - `writeFile(path, content)` / `appendFile(path, content)` — Write/append
50
+ - `exists(path)` / `stat(path)` / `lstat(path)` — Check existence and metadata
51
+ - `mkdir(path, opts?)` / `readdir(path)` / `readdirWithFileTypes(path)` — Directories
52
+ - `rm(path, opts?)` / `cp(src, dest, opts?)` / `mv(src, dest)` — File operations
53
+ - `chmod(path, mode)` / `utimes(path, atime, mtime)` — Metadata changes
54
+ - `symlink(target, linkPath)` / `link(existing, new)` / `readlink(path)` / `realpath(path)` — Links
55
+ - `static create(namespace, name, cwd?)` — Factory: creates stub, syncs cache, returns ready instance
56
+ - `getAllPaths()` — Synchronous cached path list for glob matching
57
+
58
+ ## Exports
59
+
60
+ | Export | Path | Description |
61
+ |--------|------|-------------|
62
+ | `DurableFs` | `@stablemodels/durable-bash` | `IFileSystem` adapter |
63
+ | `FsObject` | `@stablemodels/durable-bash/object` | Durable Object class — re-export in your worker |
64
+ | `FsError`, `ENOENT`, `EEXIST`, `EISDIR`, `ENOTDIR`, `ENOTEMPTY` | `@stablemodels/durable-bash` | POSIX-style errors |
65
+ | `FsStatData`, `DirentData` | `@stablemodels/durable-bash` | RPC wire-format types |
66
+
67
+ ## Development
68
+
69
+ ```bash
70
+ bun install
71
+ bun test # all 121 tests
72
+ bun run test:unit # unit tests only
73
+ bun run test:integration # integration + smoke tests
74
+ bun run build # tsc → dist/
75
+ bun run check # biome lint
76
+ bun run format # biome format
77
+ ```
78
+
79
+ ### Test structure
80
+
81
+ | File | Scope |
82
+ |------|-------|
83
+ | `tests/fs-object.test.ts` | Unit tests for `FsObject` DO |
84
+ | `tests/durable-fs.test.ts` | Unit tests for `DurableFs` (mock stub) |
85
+ | `tests/integration.test.ts` | End-to-end: `DurableFs` + `FsObject` |
86
+ | `tests/just-bash.test.ts` | Bash commands through `DurableFs` |
87
+
88
+ Tests mock `cloudflare:workers` via a preload script (`tests/setup.ts`, configured in `bunfig.toml`) and use `bun:sqlite` to simulate DO SQLite storage.
89
+
90
+ ## CI/CD
91
+
92
+ - **PR checks**: Lint, build, and tests run automatically on pull requests via GitHub Actions.
93
+ - **NPM publishing**: Triggered automatically on merge to `main`. Requires an `NPM_TOKEN` secret in the repository/org settings.
94
+
95
+ ## License
96
+
97
+ MIT
package/biome.json ADDED
@@ -0,0 +1,25 @@
1
+ {
2
+ "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
3
+ "files": {
4
+ "ignore": ["dist/**"]
5
+ },
6
+ "organizeImports": {
7
+ "enabled": true
8
+ },
9
+ "linter": {
10
+ "enabled": true,
11
+ "rules": {
12
+ "recommended": true,
13
+ "suspicious": {
14
+ "noExplicitAny": "off"
15
+ },
16
+ "style": {
17
+ "noNonNullAssertion": "off"
18
+ }
19
+ }
20
+ },
21
+ "formatter": {
22
+ "enabled": true,
23
+ "indentStyle": "tab"
24
+ }
25
+ }