@cloudflare/workspace 0.0.0-alpha.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 +98 -0
- package/dist/bin/wsd-linux-x64 +0 -0
- package/dist/git.d.ts +119 -0
- package/dist/git.js +271 -0
- package/dist/git.js.map +1 -0
- package/dist/index.d.ts +384 -0
- package/dist/index.js +2902 -0
- package/dist/index.js.map +1 -0
- package/dist/shared-Dj4r_9xb.js +737 -0
- package/dist/shared-Dj4r_9xb.js.map +1 -0
- package/dist/shared-RIdME5uo.d.ts +302 -0
- package/package.json +72 -0
package/README.md
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# `@cloudflare/workspace`
|
|
2
|
+
|
|
3
|
+
> [!IMPORTANT]
|
|
4
|
+
> **PREVIEW ONLY** This package is provided as a preview for feedback only.
|
|
5
|
+
> APIs are unstable and the design is subject to change.
|
|
6
|
+
>
|
|
7
|
+
> Suitable for experiments, exploration and prototypes. It is NOT suitable
|
|
8
|
+
> for production use at this time.
|
|
9
|
+
>
|
|
10
|
+
> The specification under [`docs/`](docs/) is forward-looking — read it for
|
|
11
|
+
> intent, not as description of the code today.
|
|
12
|
+
|
|
13
|
+
Durable Object-side facade for a Cloudflare Workspace. Pairs a local
|
|
14
|
+
SQLite-backed VFS (via `@cloudflare/dofs`) with a sync connection to
|
|
15
|
+
a `wsd` instance through a pluggable backend.
|
|
16
|
+
|
|
17
|
+
The public surface lives on three classes:
|
|
18
|
+
|
|
19
|
+
- `Workspace` — the host-side facade. Owns the local store, the
|
|
20
|
+
backend handle, and the push/pull bracket.
|
|
21
|
+
- `WorkspaceStub` — what `workspace.stub()` returns, designed to
|
|
22
|
+
cross the Workers-RPC boundary into another Worker or DO.
|
|
23
|
+
- `WorkspaceShell` / `ExecHandle` — the command-execution half of
|
|
24
|
+
the API.
|
|
25
|
+
|
|
26
|
+
Typical DO-side usage:
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
import { Workspace, CloudflareContainerBackend } from "@cloudflare/workspace";
|
|
30
|
+
import { DurableObject } from "cloudflare:workers";
|
|
31
|
+
|
|
32
|
+
export class WsdContainer extends DurableObject<Env> {
|
|
33
|
+
#workspace = new Workspace({
|
|
34
|
+
storage: this.ctx.storage,
|
|
35
|
+
backends: [
|
|
36
|
+
new CloudflareContainerBackend({
|
|
37
|
+
container: () => this.ctx.container!,
|
|
38
|
+
egress: this.ctx.exports.WsdEgress({ props: { id: this.ctx.id.toString() } }),
|
|
39
|
+
}),
|
|
40
|
+
],
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
async getWorkspace(): Promise<WorkspaceStub> {
|
|
44
|
+
await this.#workspace.ready();
|
|
45
|
+
return this.#workspace.stub();
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Worker-side consumption:
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
export default {
|
|
54
|
+
async fetch(request: Request, env: Env): Promise<Response> {
|
|
55
|
+
const id = env.WSD.idFromName("user-123");
|
|
56
|
+
using ws = await env.WSD.get(id).getWorkspace();
|
|
57
|
+
|
|
58
|
+
await ws.fs.writeFile("/notes.md", "hello");
|
|
59
|
+
using handle = await ws.shell.exec("ls /workspace");
|
|
60
|
+
const { exitCode, stdout } = await handle.result();
|
|
61
|
+
|
|
62
|
+
return new Response(stdout, { status: exitCode === 0 ? 200 : 500 });
|
|
63
|
+
},
|
|
64
|
+
} satisfies ExportedHandler<Env>;
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Stub disposal
|
|
68
|
+
|
|
69
|
+
capnweb does not garbage-collect remote stubs. On the long-lived
|
|
70
|
+
sessions this package depends on (Worker ↔ DO over Workers RPC,
|
|
71
|
+
DO ↔ wsd over capnweb), undisposed stubs accumulate on the peer
|
|
72
|
+
side until the session ends.
|
|
73
|
+
|
|
74
|
+
The minimum a caller needs to know:
|
|
75
|
+
|
|
76
|
+
- `using` the value returned from `env.WSD.get(id).getWorkspace()`.
|
|
77
|
+
- `using` the handle returned from `ws.shell.exec(...)`.
|
|
78
|
+
- Don't worry about `ws.fs` / `ws.shell` — those are property
|
|
79
|
+
accessors that ride with the parent.
|
|
80
|
+
- Pure-value returns (`readFile` as a string, `stat`, `readdir`,
|
|
81
|
+
etc.) carry no stubs; nothing to dispose.
|
|
82
|
+
|
|
83
|
+
Short-lived single-shot Workers (one `getWorkspace()`, a few calls,
|
|
84
|
+
return a response) tear the session down with the request, so the
|
|
85
|
+
discipline matters most on long-lived isolates that keep grabbing
|
|
86
|
+
fresh `WorkspaceStub`s or on busy `exec` workloads inside a single
|
|
87
|
+
request.
|
|
88
|
+
|
|
89
|
+
The full contract — including the boundary between the driver code
|
|
90
|
+
and direct streaming callers, and how it interacts with hibernation
|
|
91
|
+
and reconnect — is in [`docs/11_lifecycle.md`](../../docs/11_lifecycle.md#stub-disposal-contract).
|
|
92
|
+
|
|
93
|
+
Leak discovery: set `CAPNWEB_TRACK_STUBS=1` and read the snapshot
|
|
94
|
+
via `stubSnapshot()` from `@cloudflare/workspace-rpc/debug`, or
|
|
95
|
+
hit `GET /__wsd/stubs` on a wsd instance. The soak scripts at
|
|
96
|
+
[`script/wsd-stub-soak.mjs`](../../script/wsd-stub-soak.mjs) and
|
|
97
|
+
[`tests/stub-soak.test.ts`](./tests/stub-soak.test.ts) exercise both
|
|
98
|
+
boundaries.
|
|
Binary file
|
package/dist/git.d.ts
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { i as SQLiteWorkspaceProvider } from "./shared-RIdME5uo.js";
|
|
2
|
+
|
|
3
|
+
//#region src/git/adapter.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* The shape isomorphic-git wants as its `fs` argument: an object
|
|
6
|
+
* whose `.promises` is an own, enumerable property and implements
|
|
7
|
+
* the `node:fs/promises` subset listed in `PromiseFsClient`.
|
|
8
|
+
*
|
|
9
|
+
* `promises` is typed as `object` rather than enumerating every
|
|
10
|
+
* method @platformatic/vfs forwards. The full surface is wider
|
|
11
|
+
* than this package consumes and pinning it here would either
|
|
12
|
+
* leak @platformatic/vfs into our public types or force tedious
|
|
13
|
+
* structural shadowing. Callers that want a typed file API can
|
|
14
|
+
* cast `client.promises` to `import('@platformatic/vfs').VirtualFileSystem['promises']`.
|
|
15
|
+
*/
|
|
16
|
+
interface IsomorphicGitFSClient {
|
|
17
|
+
promises: object;
|
|
18
|
+
}
|
|
19
|
+
//#endregion
|
|
20
|
+
//#region src/git/clone.d.ts
|
|
21
|
+
type ProgressCallback = (e: {
|
|
22
|
+
phase: string;
|
|
23
|
+
loaded: number;
|
|
24
|
+
total?: number;
|
|
25
|
+
}) => void;
|
|
26
|
+
type MessageCallback = (msg: string) => void;
|
|
27
|
+
interface GitCloneOptions {
|
|
28
|
+
/** Repository URL. HTTPS only — isomorphic-git has no SSH transport. */
|
|
29
|
+
url: string;
|
|
30
|
+
/** Working-tree directory inside the VFS. Defaults to `/`. */
|
|
31
|
+
dir?: string;
|
|
32
|
+
/** Branch, tag, or commit. Defaults to the remote's default branch. */
|
|
33
|
+
ref?: string;
|
|
34
|
+
/**
|
|
35
|
+
* If set, only these paths are written into the working tree.
|
|
36
|
+
* `.git/` is fully populated regardless (subject to `depth`).
|
|
37
|
+
* Globs are not supported; pass directory or file paths.
|
|
38
|
+
*/
|
|
39
|
+
paths?: string[];
|
|
40
|
+
/**
|
|
41
|
+
* Shallow-clone depth. Defaults to 1. Pass `0` or `Infinity` for
|
|
42
|
+
* full history. Even with depth=1, every blob reachable from the
|
|
43
|
+
* tip tree is fetched — see the package README for why.
|
|
44
|
+
*/
|
|
45
|
+
depth?: number;
|
|
46
|
+
/** Fetch only the requested ref's branch. Default: true. */
|
|
47
|
+
singleBranch?: boolean;
|
|
48
|
+
/** Skip tags. Default: true. */
|
|
49
|
+
noTags?: boolean;
|
|
50
|
+
/** Extra HTTP headers, e.g. `Authorization` for a private repo. */
|
|
51
|
+
headers?: Record<string, string>;
|
|
52
|
+
/** CORS proxy URL. Usually unneeded inside a Worker. */
|
|
53
|
+
corsProxy?: string;
|
|
54
|
+
/** Progress callback. Forwarded to isomorphic-git. */
|
|
55
|
+
onProgress?: ProgressCallback;
|
|
56
|
+
/** Sideband message callback. Forwarded to isomorphic-git. */
|
|
57
|
+
onMessage?: MessageCallback;
|
|
58
|
+
}
|
|
59
|
+
//#endregion
|
|
60
|
+
//#region src/git/diff.d.ts
|
|
61
|
+
/**
|
|
62
|
+
* Status-matrix row as emitted by isomorphic-git's `statusMatrix`:
|
|
63
|
+
* `[filepath, headStatus, workdirStatus, stageStatus]`.
|
|
64
|
+
* - 0 = absent
|
|
65
|
+
* - 1 = same as HEAD
|
|
66
|
+
* - 2 = differs from HEAD
|
|
67
|
+
* - 3 = differs from HEAD and stage (rarely meaningful here)
|
|
68
|
+
*/
|
|
69
|
+
type StatusRow = [string, number, number, number];
|
|
70
|
+
interface GitDiffOptions {
|
|
71
|
+
/** Working-tree directory inside the VFS. Defaults to `/`. */
|
|
72
|
+
dir?: string;
|
|
73
|
+
/** Ref to diff against. Defaults to `HEAD`. */
|
|
74
|
+
ref?: string;
|
|
75
|
+
}
|
|
76
|
+
//#endregion
|
|
77
|
+
//#region src/git/index.d.ts
|
|
78
|
+
/** Duck-typed workspace handle. Only `.provider()` is required. */
|
|
79
|
+
interface WorkspaceLike {
|
|
80
|
+
provider(): SQLiteWorkspaceProvider;
|
|
81
|
+
}
|
|
82
|
+
/** Methods returned by `createGitClient`. */
|
|
83
|
+
interface GitClient {
|
|
84
|
+
/** Shallow-clone a remote into the bound workspace. */
|
|
85
|
+
clone(options: GitCloneOptions): Promise<void>;
|
|
86
|
+
/** Unified diff between a ref (default HEAD) and the working tree. */
|
|
87
|
+
diff(options?: GitDiffOptions): Promise<string>;
|
|
88
|
+
}
|
|
89
|
+
interface CreateGitClientOptions {
|
|
90
|
+
/** Workspace whose provider backs the git operations. */
|
|
91
|
+
ws: WorkspaceLike;
|
|
92
|
+
/**
|
|
93
|
+
* Test seam for substituting the @platformatic/vfs adapter.
|
|
94
|
+
* Production callers do not pass this.
|
|
95
|
+
*/
|
|
96
|
+
adapter?: (provider: SQLiteWorkspaceProvider) => Promise<IsomorphicGitFSClient>;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Build a git client bound to a workspace.
|
|
100
|
+
*
|
|
101
|
+
* The FsClient is constructed lazily on first use and reused
|
|
102
|
+
* across subsequent calls — `@platformatic/vfs.create()` is cheap
|
|
103
|
+
* but not free, and the workspace provider is stable for the
|
|
104
|
+
* lifetime of the client.
|
|
105
|
+
*
|
|
106
|
+
* An isomorphic-git pack/index cache is also created per client
|
|
107
|
+
* and threaded into every isogit call. Without this, each
|
|
108
|
+
* `readBlob` / `statusMatrix` / `walk` re-parses the packfile
|
|
109
|
+
* from the SQLite-backed VFS — fine for tiny repos, catastrophic
|
|
110
|
+
* for anything with a real history (the difference between a
|
|
111
|
+
* sub-second `diff` and one that hangs for minutes).
|
|
112
|
+
*/
|
|
113
|
+
declare function createGitClient({
|
|
114
|
+
ws,
|
|
115
|
+
adapter
|
|
116
|
+
}: CreateGitClientOptions): GitClient;
|
|
117
|
+
//#endregion
|
|
118
|
+
export { CreateGitClientOptions, GitClient, type GitCloneOptions, type GitDiffOptions, type MessageCallback, type ProgressCallback, type StatusRow, WorkspaceLike, createGitClient };
|
|
119
|
+
//# sourceMappingURL=git.d.ts.map
|
package/dist/git.js
ADDED
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
//#region src/git/adapter.ts
|
|
2
|
+
/**
|
|
3
|
+
* Methods on `SQLiteWorkspaceProvider` forwarded through the
|
|
4
|
+
* wrapping `VirtualProvider`. Listed explicitly (rather than walked
|
|
5
|
+
* off the prototype) so the surface is stable across dofs releases:
|
|
6
|
+
* a new method on the provider does not silently widen the wrapper.
|
|
7
|
+
*/
|
|
8
|
+
const FORWARDED_METHODS = [
|
|
9
|
+
"open",
|
|
10
|
+
"openSync",
|
|
11
|
+
"stat",
|
|
12
|
+
"statSync",
|
|
13
|
+
"lstat",
|
|
14
|
+
"lstatSync",
|
|
15
|
+
"readdir",
|
|
16
|
+
"readdirSync",
|
|
17
|
+
"mkdir",
|
|
18
|
+
"mkdirSync",
|
|
19
|
+
"rmdir",
|
|
20
|
+
"rmdirSync",
|
|
21
|
+
"unlink",
|
|
22
|
+
"unlinkSync",
|
|
23
|
+
"rename",
|
|
24
|
+
"renameSync",
|
|
25
|
+
"readFile",
|
|
26
|
+
"readFileSync",
|
|
27
|
+
"writeFile",
|
|
28
|
+
"writeFileSync",
|
|
29
|
+
"appendFile",
|
|
30
|
+
"appendFileSync",
|
|
31
|
+
"exists",
|
|
32
|
+
"existsSync",
|
|
33
|
+
"copyFile",
|
|
34
|
+
"copyFileSync",
|
|
35
|
+
"internalModuleStat",
|
|
36
|
+
"realpath",
|
|
37
|
+
"realpathSync",
|
|
38
|
+
"access",
|
|
39
|
+
"accessSync",
|
|
40
|
+
"readlink",
|
|
41
|
+
"readlinkSync",
|
|
42
|
+
"symlink",
|
|
43
|
+
"symlinkSync",
|
|
44
|
+
"watch",
|
|
45
|
+
"watchAsync",
|
|
46
|
+
"watchFile",
|
|
47
|
+
"unwatchFile",
|
|
48
|
+
"closeSync",
|
|
49
|
+
"readSync",
|
|
50
|
+
"writeSync",
|
|
51
|
+
"fstatSync",
|
|
52
|
+
"truncateSync",
|
|
53
|
+
"ftruncateSync"
|
|
54
|
+
];
|
|
55
|
+
/**
|
|
56
|
+
* Build an isomorphic-git-compatible FsClient from a
|
|
57
|
+
* SQLiteWorkspaceProvider.
|
|
58
|
+
*
|
|
59
|
+
* Lazily imports `@platformatic/vfs`. If the peer dep is missing,
|
|
60
|
+
* the thrown error names the package so the failure mode stays
|
|
61
|
+
* one stack frame away from obvious.
|
|
62
|
+
*/
|
|
63
|
+
async function workspaceIsomorphicGitClient(provider) {
|
|
64
|
+
let create;
|
|
65
|
+
let VirtualProvider;
|
|
66
|
+
try {
|
|
67
|
+
({create, VirtualProvider} = await import("@platformatic/vfs"));
|
|
68
|
+
} catch (cause) {
|
|
69
|
+
throw new Error("@cloudflare/workspace/git requires @platformatic/vfs as an optional peer dependency. Install it, or pass `fs` to clone() explicitly.", { cause });
|
|
70
|
+
}
|
|
71
|
+
class SQLiteVirtualProvider extends VirtualProvider {
|
|
72
|
+
#inner;
|
|
73
|
+
constructor(inner) {
|
|
74
|
+
super();
|
|
75
|
+
this.#inner = inner;
|
|
76
|
+
}
|
|
77
|
+
get readonly() {
|
|
78
|
+
return this.#inner.readonly;
|
|
79
|
+
}
|
|
80
|
+
get supportsSymlinks() {
|
|
81
|
+
return this.#inner.supportsSymlinks;
|
|
82
|
+
}
|
|
83
|
+
get supportsWatch() {
|
|
84
|
+
return this.#inner.supportsWatch;
|
|
85
|
+
}
|
|
86
|
+
/** Used by the forwarding loop below. */
|
|
87
|
+
get inner() {
|
|
88
|
+
return this.#inner;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
for (const name of FORWARDED_METHODS) Object.defineProperty(SQLiteVirtualProvider.prototype, name, {
|
|
92
|
+
value(...args) {
|
|
93
|
+
const inner = this.inner;
|
|
94
|
+
const fn = inner[name];
|
|
95
|
+
if (typeof fn !== "function") throw new Error(`SQLiteWorkspaceProvider.${String(name)} is not a function`);
|
|
96
|
+
return fn.apply(inner, args);
|
|
97
|
+
},
|
|
98
|
+
writable: true,
|
|
99
|
+
configurable: true
|
|
100
|
+
});
|
|
101
|
+
return { promises: create(new SQLiteVirtualProvider(provider), { moduleHooks: false }).promises };
|
|
102
|
+
}
|
|
103
|
+
//#endregion
|
|
104
|
+
//#region src/git/clone.ts
|
|
105
|
+
async function cloneWith(opts) {
|
|
106
|
+
const dir = opts.dir ?? "/";
|
|
107
|
+
const ref = opts.ref;
|
|
108
|
+
const depthRaw = opts.depth ?? 1;
|
|
109
|
+
const depth = depthRaw > 0 && Number.isFinite(depthRaw) ? depthRaw : void 0;
|
|
110
|
+
await opts.git.clone({
|
|
111
|
+
fs: opts.fs,
|
|
112
|
+
http: opts.http,
|
|
113
|
+
dir,
|
|
114
|
+
url: opts.url,
|
|
115
|
+
ref,
|
|
116
|
+
corsProxy: opts.corsProxy,
|
|
117
|
+
headers: opts.headers,
|
|
118
|
+
depth,
|
|
119
|
+
singleBranch: opts.singleBranch ?? true,
|
|
120
|
+
noTags: opts.noTags ?? true,
|
|
121
|
+
noCheckout: true,
|
|
122
|
+
cache: opts.cache,
|
|
123
|
+
onProgress: opts.onProgress,
|
|
124
|
+
onMessage: opts.onMessage
|
|
125
|
+
});
|
|
126
|
+
await opts.git.checkout({
|
|
127
|
+
fs: opts.fs,
|
|
128
|
+
dir,
|
|
129
|
+
ref: ref ?? "HEAD",
|
|
130
|
+
filepaths: opts.paths,
|
|
131
|
+
force: true,
|
|
132
|
+
cache: opts.cache
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
//#endregion
|
|
136
|
+
//#region src/git/diff.ts
|
|
137
|
+
async function diffWith(opts) {
|
|
138
|
+
const dir = opts.dir ?? "/";
|
|
139
|
+
const ref = opts.ref ?? "HEAD";
|
|
140
|
+
let head;
|
|
141
|
+
try {
|
|
142
|
+
head = await opts.git.resolveRef({
|
|
143
|
+
fs: opts.fs,
|
|
144
|
+
dir,
|
|
145
|
+
ref
|
|
146
|
+
});
|
|
147
|
+
} catch {
|
|
148
|
+
return "";
|
|
149
|
+
}
|
|
150
|
+
const status = await opts.git.statusMatrix({
|
|
151
|
+
fs: opts.fs,
|
|
152
|
+
dir,
|
|
153
|
+
ref,
|
|
154
|
+
cache: opts.cache
|
|
155
|
+
});
|
|
156
|
+
const chunks = [];
|
|
157
|
+
for (const [filepath, headStatus, workdirStatus] of status) {
|
|
158
|
+
if (workdirStatus === 1) continue;
|
|
159
|
+
const headText = headStatus === 1 ? await readBlobAsText(opts.git, opts.fs, dir, head, filepath, opts.cache) : "";
|
|
160
|
+
const workdirText = workdirStatus === 2 ? await readWorkdirAsText(opts.readFile, dir, filepath) : "";
|
|
161
|
+
const patch = opts.createPatch(filepath, headText, workdirText, "", "");
|
|
162
|
+
if (patch.trim().length > 0) chunks.push(patch);
|
|
163
|
+
}
|
|
164
|
+
return chunks.join("\n");
|
|
165
|
+
}
|
|
166
|
+
async function readBlobAsText(git, fs, dir, oid, filepath, cache) {
|
|
167
|
+
try {
|
|
168
|
+
const { blob } = await git.readBlob({
|
|
169
|
+
fs,
|
|
170
|
+
dir,
|
|
171
|
+
oid,
|
|
172
|
+
filepath,
|
|
173
|
+
cache
|
|
174
|
+
});
|
|
175
|
+
return new TextDecoder("utf-8", {
|
|
176
|
+
fatal: false,
|
|
177
|
+
ignoreBOM: false
|
|
178
|
+
}).decode(blob);
|
|
179
|
+
} catch {
|
|
180
|
+
return "";
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
async function readWorkdirAsText(readFile, dir, filepath) {
|
|
184
|
+
try {
|
|
185
|
+
const data = await readFile(`${dir}/${filepath}`);
|
|
186
|
+
if (typeof data === "string") return data;
|
|
187
|
+
return new TextDecoder("utf-8", {
|
|
188
|
+
fatal: false,
|
|
189
|
+
ignoreBOM: false
|
|
190
|
+
}).decode(data);
|
|
191
|
+
} catch {
|
|
192
|
+
return "";
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
//#endregion
|
|
196
|
+
//#region src/git/index.ts
|
|
197
|
+
/**
|
|
198
|
+
* Build a git client bound to a workspace.
|
|
199
|
+
*
|
|
200
|
+
* The FsClient is constructed lazily on first use and reused
|
|
201
|
+
* across subsequent calls — `@platformatic/vfs.create()` is cheap
|
|
202
|
+
* but not free, and the workspace provider is stable for the
|
|
203
|
+
* lifetime of the client.
|
|
204
|
+
*
|
|
205
|
+
* An isomorphic-git pack/index cache is also created per client
|
|
206
|
+
* and threaded into every isogit call. Without this, each
|
|
207
|
+
* `readBlob` / `statusMatrix` / `walk` re-parses the packfile
|
|
208
|
+
* from the SQLite-backed VFS — fine for tiny repos, catastrophic
|
|
209
|
+
* for anything with a real history (the difference between a
|
|
210
|
+
* sub-second `diff` and one that hangs for minutes).
|
|
211
|
+
*/
|
|
212
|
+
function createGitClient({ ws, adapter = workspaceIsomorphicGitClient }) {
|
|
213
|
+
let fsPromise;
|
|
214
|
+
const fs = () => {
|
|
215
|
+
if (!fsPromise) fsPromise = adapter(ws.provider());
|
|
216
|
+
return fsPromise;
|
|
217
|
+
};
|
|
218
|
+
const cache = {};
|
|
219
|
+
return {
|
|
220
|
+
async clone(options) {
|
|
221
|
+
await cloneWith({
|
|
222
|
+
...options,
|
|
223
|
+
fs: await fs(),
|
|
224
|
+
git: await loadIsomorphicGit(),
|
|
225
|
+
http: await loadDefaultHTTP(),
|
|
226
|
+
cache
|
|
227
|
+
});
|
|
228
|
+
},
|
|
229
|
+
async diff(options = {}) {
|
|
230
|
+
const f = await fs();
|
|
231
|
+
return diffWith({
|
|
232
|
+
...options,
|
|
233
|
+
fs: f,
|
|
234
|
+
git: await loadIsomorphicGit(),
|
|
235
|
+
createPatch: await loadCreatePatch(),
|
|
236
|
+
readFile: readFileFrom(f),
|
|
237
|
+
cache
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
async function loadIsomorphicGit() {
|
|
243
|
+
try {
|
|
244
|
+
const mod = await import("isomorphic-git");
|
|
245
|
+
return mod.default ?? mod;
|
|
246
|
+
} catch (cause) {
|
|
247
|
+
throw new Error("@cloudflare/workspace/git requires isomorphic-git as an optional peer dependency. Install isomorphic-git.", { cause });
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
async function loadDefaultHTTP() {
|
|
251
|
+
try {
|
|
252
|
+
return (await import("isomorphic-git/http/web")).default;
|
|
253
|
+
} catch (cause) {
|
|
254
|
+
throw new Error("Failed to load isomorphic-git/http/web. Install isomorphic-git.", { cause });
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
async function loadCreatePatch() {
|
|
258
|
+
try {
|
|
259
|
+
return (await import("./shared-Dj4r_9xb.js")).createPatch;
|
|
260
|
+
} catch (cause) {
|
|
261
|
+
throw new Error("@cloudflare/workspace/git requires `diff` as an optional peer dependency. Install `diff`.", { cause });
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
function readFileFrom(fs) {
|
|
265
|
+
const promises = fs.promises;
|
|
266
|
+
return (path) => promises.readFile(path);
|
|
267
|
+
}
|
|
268
|
+
//#endregion
|
|
269
|
+
export { createGitClient };
|
|
270
|
+
|
|
271
|
+
//# sourceMappingURL=git.js.map
|
package/dist/git.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"git.js","names":["#inner"],"sources":["../src/git/adapter.ts","../src/git/clone.ts","../src/git/diff.ts","../src/git/index.ts"],"sourcesContent":["// Bridge from `@cloudflare/dofs`'s SQLiteWorkspaceProvider to an\n// FsClient that isomorphic-git can consume directly.\n//\n// Mirrors the recipe in `examples/think/src/tools/git/vfs.ts`\n// (which mirrors the trick wsd uses for its FUSE mount):\n//\n// 1. SQLiteWorkspaceProvider already implements the full node:fs\n// surface @platformatic/vfs's VirtualFileSystem needs,\n// including symlink / readlink.\n// 2. It does not extend @platformatic/vfs.VirtualProvider, and\n// `create()` checks `provider instanceof VirtualProvider` —\n// failing silently into an in-memory store on a miss. The\n// dofs provider therefore has to be wrapped in a\n// VirtualProvider subclass with an explicit forwarding list.\n// 3. `vfs.promises` is declared as a class getter. isomorphic-git\n// probes `Object.getOwnPropertyDescriptor(fs, 'promises')`,\n// does not see an enumerable own property, and falls through\n// to its callback-style branch — which then crashes.\n// Re-exposing `promises` as an own property on a plain object\n// fixes the detection.\n//\n// `@platformatic/vfs` is imported lazily so the base\n// `@cloudflare/workspace` bundle has no static reference to it.\n\nimport type { SQLiteWorkspaceProvider } from \"@cloudflare/dofs\";\n\n/**\n * The shape isomorphic-git wants as its `fs` argument: an object\n * whose `.promises` is an own, enumerable property and implements\n * the `node:fs/promises` subset listed in `PromiseFsClient`.\n *\n * `promises` is typed as `object` rather than enumerating every\n * method @platformatic/vfs forwards. The full surface is wider\n * than this package consumes and pinning it here would either\n * leak @platformatic/vfs into our public types or force tedious\n * structural shadowing. Callers that want a typed file API can\n * cast `client.promises` to `import('@platformatic/vfs').VirtualFileSystem['promises']`.\n */\nexport interface IsomorphicGitFSClient {\n promises: object;\n}\n\n/**\n * Methods on `SQLiteWorkspaceProvider` forwarded through the\n * wrapping `VirtualProvider`. Listed explicitly (rather than walked\n * off the prototype) so the surface is stable across dofs releases:\n * a new method on the provider does not silently widen the wrapper.\n */\nconst FORWARDED_METHODS = [\n \"open\",\n \"openSync\",\n \"stat\",\n \"statSync\",\n \"lstat\",\n \"lstatSync\",\n \"readdir\",\n \"readdirSync\",\n \"mkdir\",\n \"mkdirSync\",\n \"rmdir\",\n \"rmdirSync\",\n \"unlink\",\n \"unlinkSync\",\n \"rename\",\n \"renameSync\",\n \"readFile\",\n \"readFileSync\",\n \"writeFile\",\n \"writeFileSync\",\n \"appendFile\",\n \"appendFileSync\",\n \"exists\",\n \"existsSync\",\n \"copyFile\",\n \"copyFileSync\",\n \"internalModuleStat\",\n \"realpath\",\n \"realpathSync\",\n \"access\",\n \"accessSync\",\n \"readlink\",\n \"readlinkSync\",\n \"symlink\",\n \"symlinkSync\",\n \"watch\",\n \"watchAsync\",\n \"watchFile\",\n \"unwatchFile\",\n // fd-style extensions @platformatic/vfs's router uses on\n // read/write paths going through `open`.\n \"closeSync\",\n \"readSync\",\n \"writeSync\",\n \"fstatSync\",\n \"truncateSync\",\n \"ftruncateSync\",\n] as const;\n\n/**\n * Build an isomorphic-git-compatible FsClient from a\n * SQLiteWorkspaceProvider.\n *\n * Lazily imports `@platformatic/vfs`. If the peer dep is missing,\n * the thrown error names the package so the failure mode stays\n * one stack frame away from obvious.\n */\nexport async function workspaceIsomorphicGitClient(\n provider: SQLiteWorkspaceProvider,\n): Promise<IsomorphicGitFSClient> {\n let create: typeof import(\"@platformatic/vfs\").create;\n let VirtualProvider: typeof import(\"@platformatic/vfs\").VirtualProvider;\n try {\n ({ create, VirtualProvider } = await import(\"@platformatic/vfs\"));\n } catch (cause) {\n throw new Error(\n \"@cloudflare/workspace/git requires @platformatic/vfs as an optional peer dependency. \" +\n \"Install it, or pass `fs` to clone() explicitly.\",\n { cause },\n );\n }\n\n class SQLiteVirtualProvider extends VirtualProvider {\n readonly #inner: SQLiteWorkspaceProvider;\n constructor(inner: SQLiteWorkspaceProvider) {\n super();\n this.#inner = inner;\n }\n // VirtualProvider's defaults are false. The dofs provider\n // declares the real values as instance properties; the\n // forwarding loop below skips accessors, so re-expose them.\n override get readonly(): boolean {\n return this.#inner.readonly;\n }\n override get supportsSymlinks(): boolean {\n return this.#inner.supportsSymlinks;\n }\n override get supportsWatch(): boolean {\n return this.#inner.supportsWatch;\n }\n /** Used by the forwarding loop below. */\n get inner(): SQLiteWorkspaceProvider {\n return this.#inner;\n }\n }\n\n for (const name of FORWARDED_METHODS) {\n Object.defineProperty(SQLiteVirtualProvider.prototype, name, {\n value(this: SQLiteVirtualProvider, ...args: unknown[]): unknown {\n // biome-ignore lint/suspicious/noExplicitAny: dispatch table\n const inner = this.inner as any;\n const fn = inner[name];\n if (typeof fn !== \"function\") {\n throw new Error(`SQLiteWorkspaceProvider.${String(name)} is not a function`);\n }\n return fn.apply(inner, args);\n },\n writable: true,\n configurable: true,\n });\n }\n\n // `moduleHooks: false` keeps @platformatic/vfs from installing\n // Node module-resolution hooks, which depend on `node:module` and\n // are pointless inside workerd.\n const vfs = create(new SQLiteVirtualProvider(provider), { moduleHooks: false });\n // Re-expose `.promises` as an enumerable own property so\n // isomorphic-git's FileSystem detection picks the promise branch.\n return { promises: vfs.promises };\n}\n","// Clone a remote repository into a workspace-backed filesystem.\n//\n// Subset-of-files support comes from isomorphic-git's two-step\n// model: clone with `noCheckout: true` to populate `.git/`, then\n// `checkout({ filepaths })` to materialize a chosen subset of the\n// working tree. The wire protocol still ships every blob reachable\n// from the tip tree — isomorphic-git does not speak the v2\n// `filter` capability — but disk writes are limited to `paths`.\n//\n// `cloneWith` is the testable core: it takes a pre-built\n// IsomorphicGitClient and HTTP transport. The public `clone()` in\n// ./index.ts resolves those from dynamic imports of `isomorphic-git`\n// so the peer dep stays optional and the base bundle pays no cost.\n\n/**\n * The subset of `isomorphic-git`'s API consumed here. Declared\n * structurally to avoid pulling the package's `.d.ts` into this\n * package's public types, and to let tests pass plain spies.\n */\nexport interface IsomorphicGitClient {\n clone(args: {\n fs: object;\n http: object;\n dir: string;\n url: string;\n ref?: string;\n corsProxy?: string;\n headers?: Record<string, string>;\n depth?: number;\n singleBranch?: boolean;\n noTags?: boolean;\n noCheckout?: boolean;\n cache?: object;\n onProgress?: ProgressCallback;\n onMessage?: MessageCallback;\n }): Promise<void>;\n\n checkout(args: {\n fs: object;\n dir: string;\n ref: string;\n filepaths?: string[];\n force?: boolean;\n cache?: object;\n }): Promise<void>;\n}\n\nexport type ProgressCallback = (e: { phase: string; loaded: number; total?: number }) => void;\nexport type MessageCallback = (msg: string) => void;\n\nexport interface GitCloneOptions {\n /** Repository URL. HTTPS only — isomorphic-git has no SSH transport. */\n url: string;\n /** Working-tree directory inside the VFS. Defaults to `/`. */\n dir?: string;\n /** Branch, tag, or commit. Defaults to the remote's default branch. */\n ref?: string;\n /**\n * If set, only these paths are written into the working tree.\n * `.git/` is fully populated regardless (subject to `depth`).\n * Globs are not supported; pass directory or file paths.\n */\n paths?: string[];\n /**\n * Shallow-clone depth. Defaults to 1. Pass `0` or `Infinity` for\n * full history. Even with depth=1, every blob reachable from the\n * tip tree is fetched — see the package README for why.\n */\n depth?: number;\n /** Fetch only the requested ref's branch. Default: true. */\n singleBranch?: boolean;\n /** Skip tags. Default: true. */\n noTags?: boolean;\n /** Extra HTTP headers, e.g. `Authorization` for a private repo. */\n headers?: Record<string, string>;\n /** CORS proxy URL. Usually unneeded inside a Worker. */\n corsProxy?: string;\n /** Progress callback. Forwarded to isomorphic-git. */\n onProgress?: ProgressCallback;\n /** Sideband message callback. Forwarded to isomorphic-git. */\n onMessage?: MessageCallback;\n}\n\n/**\n * Dependency-injected form. Used directly by tests and by the\n * public `clone()` wrapper. Splitting this out keeps the import\n * graph clean: this file has no runtime dependency on\n * `isomorphic-git` or `@platformatic/vfs`.\n */\nexport interface CloneWithDeps extends GitCloneOptions {\n git: IsomorphicGitClient;\n http: object;\n fs: object;\n /**\n * isomorphic-git's pack/index cache. Pass the same object to\n * every isogit call against this repo so the packfile is parsed\n * once. Without this, each `readBlob` / `walk` / `statusMatrix`\n * re-parses the pack from the SQLite-backed VFS (~tens of MB),\n * which is the difference between sub-second diffs and minutes.\n */\n cache?: object;\n}\n\nexport async function cloneWith(opts: CloneWithDeps): Promise<void> {\n const dir = opts.dir ?? \"/\";\n const ref = opts.ref;\n const depthRaw = opts.depth ?? 1;\n // depth=0 and depth=Infinity both mean \"no shallow limit\" on this\n // surface. isomorphic-git interprets `undefined` that way.\n const depth = depthRaw > 0 && Number.isFinite(depthRaw) ? depthRaw : undefined;\n\n await opts.git.clone({\n fs: opts.fs,\n http: opts.http,\n dir,\n url: opts.url,\n ref,\n corsProxy: opts.corsProxy,\n headers: opts.headers,\n depth,\n singleBranch: opts.singleBranch ?? true,\n noTags: opts.noTags ?? true,\n noCheckout: true,\n cache: opts.cache,\n onProgress: opts.onProgress,\n onMessage: opts.onMessage,\n });\n\n // The working tree is empty after a noCheckout clone, so `force`\n // is safe — nothing real can conflict — and matches caller intent\n // (\"populate this workspace from the remote\").\n await opts.git.checkout({\n fs: opts.fs,\n dir,\n ref: ref ?? \"HEAD\",\n filepaths: opts.paths,\n force: true,\n cache: opts.cache,\n });\n}\n","// Unified-diff between a git ref (default HEAD) and the working\n// tree, the way `git diff HEAD` would, but in pure JS via\n// isomorphic-git + the `diff` package.\n//\n// `diffWith` is the testable core: takes a pre-built\n// IsomorphicGitDiffClient, an FsClient, a `createPatch` function,\n// and a `readFile` function. The public `diff()` in ./index.ts\n// resolves those from dynamic imports of `isomorphic-git` and\n// `diff` so both stay optional peer deps.\n\nimport type { IsomorphicGitFSClient } from \"./adapter.js\";\n\n/**\n * Status-matrix row as emitted by isomorphic-git's `statusMatrix`:\n * `[filepath, headStatus, workdirStatus, stageStatus]`.\n * - 0 = absent\n * - 1 = same as HEAD\n * - 2 = differs from HEAD\n * - 3 = differs from HEAD and stage (rarely meaningful here)\n */\nexport type StatusRow = [string, number, number, number];\n\n/** Subset of isomorphic-git's API used to compute a working-tree diff. */\nexport interface IsomorphicGitDiffClient {\n resolveRef(args: { fs: object; dir: string; ref: string }): Promise<string>;\n statusMatrix(args: {\n fs: object;\n dir: string;\n ref?: string;\n cache?: object;\n }): Promise<StatusRow[]>;\n readBlob(args: {\n fs: object;\n dir: string;\n oid: string;\n filepath: string;\n cache?: object;\n }): Promise<{ blob: Uint8Array; oid: string }>;\n}\n\n/** Signature compatible with the `diff` package's `createPatch`. */\nexport type CreatePatchFn = (\n fileName: string,\n oldStr: string,\n newStr: string,\n oldHeader?: string,\n newHeader?: string,\n) => string;\n\n/** Signature compatible with `fs.promises.readFile` (binary mode). */\nexport type ReadFileFn = (path: string) => Promise<Uint8Array | string>;\n\nexport interface GitDiffOptions {\n /** Working-tree directory inside the VFS. Defaults to `/`. */\n dir?: string;\n /** Ref to diff against. Defaults to `HEAD`. */\n ref?: string;\n}\n\n/**\n * Dependency-injected form. Used directly by tests and by the\n * public `diff()` wrapper.\n */\nexport interface DiffWithDeps extends GitDiffOptions {\n git: IsomorphicGitDiffClient;\n fs: IsomorphicGitFSClient | object;\n createPatch: CreatePatchFn;\n readFile: ReadFileFn;\n /**\n * isomorphic-git's pack/index cache. Shared with the surrounding\n * GitClient so the packfile is parsed once across clone, diff,\n * and any other isogit call. See clone.ts's CloneWithDeps.cache.\n */\n cache?: object;\n}\n\nexport async function diffWith(opts: DiffWithDeps): Promise<string> {\n const dir = opts.dir ?? \"/\";\n const ref = opts.ref ?? \"HEAD\";\n\n let head: string;\n try {\n head = await opts.git.resolveRef({ fs: opts.fs, dir, ref });\n } catch {\n // Ref unresolvable (e.g. workspace never cloned). Empty\n // string is a more useful signal than an exception for the\n // common \"diff after maybe-no-op\" call site.\n return \"\";\n }\n\n // Pass `ref` through so the matrix is computed against the\n // requested commit rather than always HEAD. Without this the\n // `ref` argument would only affect blob reads, leaving the\n // status walk silently skewed.\n const status = await opts.git.statusMatrix({ fs: opts.fs, dir, ref, cache: opts.cache });\n const chunks: string[] = [];\n for (const [filepath, headStatus, workdirStatus] of status) {\n // workdirStatus: 0 absent, 1 == HEAD, 2 differs. Skip\n // unchanged rows up front to avoid the blob/file reads.\n if (workdirStatus === 1) continue;\n\n const headText =\n headStatus === 1\n ? await readBlobAsText(opts.git, opts.fs, dir, head, filepath, opts.cache)\n : \"\";\n const workdirText =\n workdirStatus === 2 ? await readWorkdirAsText(opts.readFile, dir, filepath) : \"\";\n const patch = opts.createPatch(filepath, headText, workdirText, \"\", \"\");\n if (patch.trim().length > 0) chunks.push(patch);\n }\n return chunks.join(\"\\n\");\n}\n\nasync function readBlobAsText(\n git: IsomorphicGitDiffClient,\n fs: object,\n dir: string,\n oid: string,\n filepath: string,\n cache: object | undefined,\n): Promise<string> {\n try {\n const { blob } = await git.readBlob({ fs, dir, oid, filepath, cache });\n // Best-effort UTF-8 decode. A real diff tool would skip\n // binaries entirely; for the general case here a noisy diff\n // beats a thrown error.\n return new TextDecoder(\"utf-8\", { fatal: false, ignoreBOM: false }).decode(blob);\n } catch {\n return \"\";\n }\n}\n\nasync function readWorkdirAsText(\n readFile: ReadFileFn,\n dir: string,\n filepath: string,\n): Promise<string> {\n try {\n const data = await readFile(`${dir}/${filepath}`);\n if (typeof data === \"string\") return data;\n return new TextDecoder(\"utf-8\", { fatal: false, ignoreBOM: false }).decode(data);\n } catch {\n return \"\";\n }\n}\n","// Public surface of @cloudflare/workspace/git.\n//\n// `createGitClient({ ws })` is the one entry point. It binds a\n// workspace handle once and returns `{ clone, diff }` methods that\n// don't repeat the workspace argument. Internally each method\n// lazy-loads its optional peer deps (`isomorphic-git`, the http\n// transport, and for `diff` the `diff` package) and delegates to\n// `cloneWith` / `diffWith`.\n//\n// `cloneWith` and `diffWith` are still exported for callers that\n// bring their own FsClient — tests, custom adapters, running\n// against node:fs directly — but the workspace-bound path is the\n// only one most consumers need.\n\nimport type { SQLiteWorkspaceProvider } from \"@cloudflare/dofs\";\n\nimport { type IsomorphicGitFSClient, workspaceIsomorphicGitClient } from \"./adapter.js\";\nimport { cloneWith, type GitCloneOptions, type IsomorphicGitClient } from \"./clone.js\";\nimport {\n type CreatePatchFn,\n diffWith,\n type GitDiffOptions,\n type IsomorphicGitDiffClient,\n type ReadFileFn,\n} from \"./diff.js\";\n\nexport type { GitCloneOptions, MessageCallback, ProgressCallback } from \"./clone.js\";\nexport type { GitDiffOptions, StatusRow } from \"./diff.js\";\n\n/** Duck-typed workspace handle. Only `.provider()` is required. */\nexport interface WorkspaceLike {\n provider(): SQLiteWorkspaceProvider;\n}\n\n/** Methods returned by `createGitClient`. */\nexport interface GitClient {\n /** Shallow-clone a remote into the bound workspace. */\n clone(options: GitCloneOptions): Promise<void>;\n /** Unified diff between a ref (default HEAD) and the working tree. */\n diff(options?: GitDiffOptions): Promise<string>;\n}\n\nexport interface CreateGitClientOptions {\n /** Workspace whose provider backs the git operations. */\n ws: WorkspaceLike;\n /**\n * Test seam for substituting the @platformatic/vfs adapter.\n * Production callers do not pass this.\n */\n adapter?: (provider: SQLiteWorkspaceProvider) => Promise<IsomorphicGitFSClient>;\n}\n\n/**\n * Build a git client bound to a workspace.\n *\n * The FsClient is constructed lazily on first use and reused\n * across subsequent calls — `@platformatic/vfs.create()` is cheap\n * but not free, and the workspace provider is stable for the\n * lifetime of the client.\n *\n * An isomorphic-git pack/index cache is also created per client\n * and threaded into every isogit call. Without this, each\n * `readBlob` / `statusMatrix` / `walk` re-parses the packfile\n * from the SQLite-backed VFS — fine for tiny repos, catastrophic\n * for anything with a real history (the difference between a\n * sub-second `diff` and one that hangs for minutes).\n */\nexport function createGitClient({\n ws,\n adapter = workspaceIsomorphicGitClient,\n}: CreateGitClientOptions): GitClient {\n let fsPromise: Promise<IsomorphicGitFSClient> | undefined;\n const fs = () => {\n if (!fsPromise) fsPromise = adapter(ws.provider());\n return fsPromise;\n };\n const cache: Record<string, unknown> = {};\n\n return {\n async clone(options) {\n await cloneWith({\n ...options,\n fs: await fs(),\n git: await loadIsomorphicGit<IsomorphicGitClient>(),\n http: await loadDefaultHTTP(),\n cache,\n });\n },\n async diff(options = {}) {\n const f = await fs();\n return diffWith({\n ...options,\n fs: f,\n git: await loadIsomorphicGit<IsomorphicGitDiffClient>(),\n createPatch: await loadCreatePatch(),\n readFile: readFileFrom(f),\n cache,\n });\n },\n };\n}\n\n// isomorphic-git ships both named exports and a default export\n// (CJS interop). Callers pull the subset they need via the generic.\nasync function loadIsomorphicGit<T>(): Promise<T> {\n try {\n const mod = await import(\"isomorphic-git\");\n return (mod.default ?? mod) as unknown as T;\n } catch (cause) {\n throw new Error(\n \"@cloudflare/workspace/git requires isomorphic-git as an optional peer dependency. \" +\n \"Install isomorphic-git.\",\n { cause },\n );\n }\n}\n\nasync function loadDefaultHTTP(): Promise<object> {\n try {\n const mod = await import(\"isomorphic-git/http/web\");\n return mod.default;\n } catch (cause) {\n throw new Error(\"Failed to load isomorphic-git/http/web. Install isomorphic-git.\", { cause });\n }\n}\n\nasync function loadCreatePatch(): Promise<CreatePatchFn> {\n try {\n const mod = await import(\"diff\");\n return mod.createPatch;\n } catch (cause) {\n throw new Error(\n \"@cloudflare/workspace/git requires `diff` as an optional peer dependency. \" +\n \"Install `diff`.\",\n { cause },\n );\n }\n}\n\nfunction readFileFrom(fs: IsomorphicGitFSClient): ReadFileFn {\n // `fs.promises` is typed as `object` on the public surface; the\n // underlying @platformatic/vfs handle exposes `readFile`. Narrow\n // locally rather than widening the exported type.\n const promises = fs.promises as { readFile: ReadFileFn };\n return (path) => promises.readFile(path);\n}\n"],"mappings":"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"}
|