effect-cf 0.25.3 → 0.27.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/CHANGELOG.md +16 -0
- package/README.md +213 -1
- package/dist/Artifacts-BhWrdyqX.mjs +241 -0
- package/dist/Artifacts-DcoXcRP5.d.mts +231 -0
- package/dist/ComputerArtifacts.d.mts +55 -0
- package/dist/ComputerArtifacts.mjs +95 -0
- package/dist/ComputerWorkspace.d.mts +279 -0
- package/dist/ComputerWorkspace.mjs +545 -0
- package/dist/ComputerWorkspaceHost.d.mts +71 -0
- package/dist/ComputerWorkspaceHost.mjs +74 -0
- package/dist/ComputerWorkspaceHostRegistry-CY5Fv4Ng.mjs +8 -0
- package/dist/{DurableObjectState-CEh0DVw7.mjs → DurableObjectState-Dux8KfjG.mjs} +3 -48
- package/dist/{WorkflowBinding-D5iWzCUa.d.mts → DurableObjectState-biHrHwrh.d.mts} +7 -81
- package/dist/ErrorMessage-_lg53p3W.mjs +49 -0
- package/dist/Vitest.d.mts +2 -1
- package/dist/Vitest.mjs +2 -1
- package/dist/WorkflowBinding-zlRPCXVf.d.mts +79 -0
- package/dist/index.d.mts +80 -2
- package/dist/index.mjs +72 -123
- package/package.json +23 -1
- package/src/Artifacts.ts +519 -0
- package/src/ComputerArtifacts.ts +255 -0
- package/src/ComputerWorkspace.ts +1098 -0
- package/src/ComputerWorkspaceHost.ts +216 -0
- package/src/WebTransport.ts +123 -0
- package/src/index.ts +2 -0
- package/src/internal/ComputerWorkspaceHostRegistry.ts +10 -0
|
@@ -0,0 +1,1098 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ExecSyncResult as CloudflareExecSyncResult,
|
|
3
|
+
RuntimeExecOptions as CloudflareRuntimeExecOptions,
|
|
4
|
+
RuntimeGetOptions as CloudflareRuntimeGetOptions,
|
|
5
|
+
RuntimeKillOptions as CloudflareRuntimeKillOptions,
|
|
6
|
+
ShellValue as CloudflareShellValue,
|
|
7
|
+
SkippedEntry as CloudflareSkippedEntry,
|
|
8
|
+
ThinkWorkspaceCompatibility as CloudflareThinkWorkspaceCompatibility,
|
|
9
|
+
WorkspaceClient as CloudflareWorkspaceClient,
|
|
10
|
+
WorkspaceRuntimeEvent as CloudflareWorkspaceRuntimeEvent,
|
|
11
|
+
WorkspaceRuntimeExecHandle as CloudflareWorkspaceRuntimeExecHandle,
|
|
12
|
+
WorkspaceRuntimeResult as CloudflareWorkspaceRuntimeResult,
|
|
13
|
+
WorkspaceRuntimeValue as CloudflareWorkspaceRuntimeValue,
|
|
14
|
+
} from "@cloudflare/computer";
|
|
15
|
+
import type { ArtifactClient as CloudflareComputerArtifactClient } from "@cloudflare/computer/artifacts";
|
|
16
|
+
import type {
|
|
17
|
+
AssetsClient as CloudflareComputerAssetsClient,
|
|
18
|
+
ShareOptions as CloudflareComputerShareOptions,
|
|
19
|
+
} from "@cloudflare/computer/assets";
|
|
20
|
+
import type {
|
|
21
|
+
CommitView as CloudflareCommitView,
|
|
22
|
+
GitClient as CloudflareGitClient,
|
|
23
|
+
GitCloneOptions as CloudflareGitCloneOptions,
|
|
24
|
+
GitCommitOptions as CloudflareGitCommitOptions,
|
|
25
|
+
GitDiffOptions as CloudflareGitDiffOptions,
|
|
26
|
+
GitFetchOptions as CloudflareGitFetchOptions,
|
|
27
|
+
GitLogOptions as CloudflareGitLogOptions,
|
|
28
|
+
GitUpdateRefOptions as CloudflareGitUpdateRefOptions,
|
|
29
|
+
} from "@cloudflare/computer/git";
|
|
30
|
+
import { Context, Effect, Layer, Schema, type Scope, Stream } from "effect";
|
|
31
|
+
|
|
32
|
+
import * as ComputerArtifacts from "./ComputerArtifacts";
|
|
33
|
+
import { DurableObjectState } from "./DurableObjectState";
|
|
34
|
+
import * as HostRegistry from "./internal/ComputerWorkspaceHostRegistry";
|
|
35
|
+
|
|
36
|
+
type CloudflareWorkspaceFilesystem = CloudflareWorkspaceClient["fs"];
|
|
37
|
+
type CloudflareReadFileOptions = Parameters<CloudflareWorkspaceFilesystem["readFile"]>[1];
|
|
38
|
+
type CloudflareReaddirOptions = Parameters<CloudflareWorkspaceFilesystem["readdir"]>[1];
|
|
39
|
+
type CloudflareFindOptions = Parameters<CloudflareWorkspaceFilesystem["find"]>[2];
|
|
40
|
+
type CloudflareGrepOptions = Parameters<CloudflareWorkspaceFilesystem["grep"]>[2];
|
|
41
|
+
type CloudflareRmOptions = Parameters<CloudflareWorkspaceFilesystem["rm"]>[1];
|
|
42
|
+
type CloudflareMkdirOptions = Parameters<CloudflareWorkspaceFilesystem["mkdir"]>[1];
|
|
43
|
+
type CloudflareWriteFileContent = Parameters<CloudflareWorkspaceFilesystem["writeFile"]>[1];
|
|
44
|
+
type CloudflareWriteFileOptions = Parameters<CloudflareWorkspaceFilesystem["writeFile"]>[2];
|
|
45
|
+
type CloudflareWorkspaceDirentResult = Awaited<
|
|
46
|
+
ReturnType<CloudflareWorkspaceFilesystem["readdir"]>
|
|
47
|
+
>[number];
|
|
48
|
+
type CloudflareWorkspaceStatResult = Awaited<ReturnType<CloudflareWorkspaceFilesystem["stat"]>>;
|
|
49
|
+
type CloudflareWorkspaceFoundEntry = Awaited<
|
|
50
|
+
ReturnType<CloudflareWorkspaceFilesystem["find"]>
|
|
51
|
+
>[number];
|
|
52
|
+
type CloudflareWorkspaceGrepMatch = Awaited<
|
|
53
|
+
ReturnType<CloudflareWorkspaceFilesystem["grep"]>
|
|
54
|
+
>[number];
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Workspace errors use `Schema.TaggedError`, unlike most local binding errors,
|
|
58
|
+
* because workspace operations commonly sit behind Durable Object RPC methods.
|
|
59
|
+
* The schema form preserves the operation-family tags across that boundary.
|
|
60
|
+
*/
|
|
61
|
+
export class WorkspaceFsError extends Schema.TaggedError<WorkspaceFsError>()("WorkspaceFsError", {
|
|
62
|
+
operation: Schema.String,
|
|
63
|
+
path: Schema.String,
|
|
64
|
+
message: Schema.String,
|
|
65
|
+
code: Schema.optionalKey(Schema.String),
|
|
66
|
+
cause: Schema.optionalKey(Schema.Defect({ includeStack: true })),
|
|
67
|
+
}) {}
|
|
68
|
+
|
|
69
|
+
/** Schema-serializable error for a workspace Git operation. */
|
|
70
|
+
export class WorkspaceGitError extends Schema.TaggedError<WorkspaceGitError>()(
|
|
71
|
+
"WorkspaceGitError",
|
|
72
|
+
{
|
|
73
|
+
operation: Schema.String,
|
|
74
|
+
message: Schema.String,
|
|
75
|
+
code: Schema.optionalKey(Schema.String),
|
|
76
|
+
cause: Schema.optionalKey(Schema.Defect({ includeStack: true })),
|
|
77
|
+
},
|
|
78
|
+
) {}
|
|
79
|
+
|
|
80
|
+
/** Schema-serializable error for a workspace runtime operation. */
|
|
81
|
+
export class WorkspaceExecError extends Schema.TaggedError<WorkspaceExecError>()(
|
|
82
|
+
"WorkspaceExecError",
|
|
83
|
+
{
|
|
84
|
+
operation: Schema.String,
|
|
85
|
+
message: Schema.String,
|
|
86
|
+
code: Schema.optionalKey(Schema.String),
|
|
87
|
+
cause: Schema.optionalKey(Schema.Defect({ includeStack: true })),
|
|
88
|
+
},
|
|
89
|
+
) {}
|
|
90
|
+
|
|
91
|
+
/** Schema-serializable error for a workspace Assets operation. */
|
|
92
|
+
export class WorkspaceAssetsError extends Schema.TaggedError<WorkspaceAssetsError>()(
|
|
93
|
+
"WorkspaceAssetsError",
|
|
94
|
+
{
|
|
95
|
+
operation: Schema.String,
|
|
96
|
+
path: Schema.String,
|
|
97
|
+
message: Schema.String,
|
|
98
|
+
code: Schema.optionalKey(Schema.String),
|
|
99
|
+
cause: Schema.optionalKey(Schema.Defect({ includeStack: true })),
|
|
100
|
+
},
|
|
101
|
+
) {}
|
|
102
|
+
|
|
103
|
+
export type WorkspaceReadFileOptions = Omit<CloudflareReadFileOptions, "encoding">;
|
|
104
|
+
export type ReadTextFileOptions = WorkspaceReadFileOptions;
|
|
105
|
+
export type ListDirectoryOptions = CloudflareReaddirOptions;
|
|
106
|
+
export type WorkspaceDirectoryEntry = CloudflareWorkspaceDirentResult;
|
|
107
|
+
export type WorkspaceEntryStat = CloudflareWorkspaceStatResult;
|
|
108
|
+
export type WorkspaceFindOptions = CloudflareFindOptions;
|
|
109
|
+
export type WorkspaceFoundEntry = CloudflareWorkspaceFoundEntry;
|
|
110
|
+
export type WorkspaceGrepOptions = CloudflareGrepOptions;
|
|
111
|
+
export type WorkspaceGrepMatch = CloudflareWorkspaceGrepMatch;
|
|
112
|
+
export type RemovePathOptions = CloudflareRmOptions;
|
|
113
|
+
export type WorkspaceMkdirOptions = CloudflareMkdirOptions;
|
|
114
|
+
export type WorkspaceWriteFileContent = CloudflareWriteFileContent;
|
|
115
|
+
export type WorkspaceWriteFileOptions = CloudflareWriteFileOptions;
|
|
116
|
+
|
|
117
|
+
/** Explicit read mode. Text remains the default for consumer-port compatibility. */
|
|
118
|
+
export type WorkspaceReadEncoding = "utf8" | "stream";
|
|
119
|
+
|
|
120
|
+
export interface WorkspaceReadFile {
|
|
121
|
+
(
|
|
122
|
+
path: string,
|
|
123
|
+
options?: WorkspaceReadFileOptions & { readonly encoding?: "utf8" },
|
|
124
|
+
): Effect.Effect<string, WorkspaceFsError>;
|
|
125
|
+
(
|
|
126
|
+
path: string,
|
|
127
|
+
options: WorkspaceReadFileOptions & { readonly encoding: "stream" },
|
|
128
|
+
): Effect.Effect<ReadableStream<Uint8Array>, WorkspaceFsError>;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** Complete Effect wrapper of `WorkspaceClient.fs`. */
|
|
132
|
+
export interface ComputerWorkspaceFilesystem {
|
|
133
|
+
readonly readFile: WorkspaceReadFile;
|
|
134
|
+
readonly stat: (path: string) => Effect.Effect<WorkspaceEntryStat, WorkspaceFsError>;
|
|
135
|
+
readonly lstat: (path: string) => Effect.Effect<WorkspaceEntryStat, WorkspaceFsError>;
|
|
136
|
+
readonly exists: (path: string) => Effect.Effect<boolean, WorkspaceFsError>;
|
|
137
|
+
readonly readlink: (path: string) => Effect.Effect<string, WorkspaceFsError>;
|
|
138
|
+
readonly readdir: (
|
|
139
|
+
path: string,
|
|
140
|
+
options?: ListDirectoryOptions,
|
|
141
|
+
) => Effect.Effect<ReadonlyArray<WorkspaceDirectoryEntry>, WorkspaceFsError>;
|
|
142
|
+
readonly find: (
|
|
143
|
+
directory: string,
|
|
144
|
+
pattern?: string,
|
|
145
|
+
options?: WorkspaceFindOptions,
|
|
146
|
+
) => Effect.Effect<ReadonlyArray<WorkspaceFoundEntry>, WorkspaceFsError>;
|
|
147
|
+
readonly ls: (prefix: string) => Effect.Effect<ReadonlyArray<string>, WorkspaceFsError>;
|
|
148
|
+
readonly grep: (
|
|
149
|
+
pattern: string,
|
|
150
|
+
path: string,
|
|
151
|
+
options?: WorkspaceGrepOptions,
|
|
152
|
+
) => Effect.Effect<ReadonlyArray<WorkspaceGrepMatch>, WorkspaceFsError>;
|
|
153
|
+
readonly writeFile: (
|
|
154
|
+
path: string,
|
|
155
|
+
content: WorkspaceWriteFileContent,
|
|
156
|
+
options?: WorkspaceWriteFileOptions,
|
|
157
|
+
) => Effect.Effect<void, WorkspaceFsError>;
|
|
158
|
+
readonly mkdir: (
|
|
159
|
+
path: string,
|
|
160
|
+
options?: WorkspaceMkdirOptions,
|
|
161
|
+
) => Effect.Effect<void, WorkspaceFsError>;
|
|
162
|
+
readonly rm: (path: string, options?: RemovePathOptions) => Effect.Effect<void, WorkspaceFsError>;
|
|
163
|
+
readonly chmod: (path: string, mode: number) => Effect.Effect<void, WorkspaceFsError>;
|
|
164
|
+
readonly symlink: (target: string, path: string) => Effect.Effect<void, WorkspaceFsError>;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export type WorkspaceGitCloneOptions = CloudflareGitCloneOptions;
|
|
168
|
+
export type WorkspaceGitFetchOptions = CloudflareGitFetchOptions;
|
|
169
|
+
export type WorkspaceGitFetchResult = Awaited<ReturnType<CloudflareGitClient["fetch"]>>;
|
|
170
|
+
export type WorkspaceGitCheckoutOptions = Parameters<CloudflareGitClient["checkout"]>[0];
|
|
171
|
+
export type WorkspaceGitUpdateRefOptions = CloudflareGitUpdateRefOptions;
|
|
172
|
+
export type WorkspaceGitDiffOptions = CloudflareGitDiffOptions;
|
|
173
|
+
export type WorkspaceGitCommitOptions = CloudflareGitCommitOptions;
|
|
174
|
+
|
|
175
|
+
export interface WorkspaceGitLogOptions extends Omit<CloudflareGitLogOptions, "depth"> {
|
|
176
|
+
/** Preferred upstream name for the history bound. */
|
|
177
|
+
readonly depth?: number;
|
|
178
|
+
/** Consumer-port compatibility alias for `depth`. */
|
|
179
|
+
readonly maxCount?: number;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export type WorkspaceGitPerson = CloudflareCommitView["author"] & {
|
|
183
|
+
/** Consumer-port compatibility alias for `timestamp`. */
|
|
184
|
+
readonly timestampSeconds: number;
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
export interface WorkspaceGitCommit {
|
|
188
|
+
readonly oid: string;
|
|
189
|
+
readonly message: string;
|
|
190
|
+
readonly tree: string;
|
|
191
|
+
readonly parent: ReadonlyArray<string>;
|
|
192
|
+
/** Consumer-port compatibility alias for `parent`. */
|
|
193
|
+
readonly parents: ReadonlyArray<string>;
|
|
194
|
+
readonly author: WorkspaceGitPerson;
|
|
195
|
+
readonly committer: WorkspaceGitPerson;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
type EffectGitMethod<Method> = Method extends (...args: infer Args) => Promise<infer Success>
|
|
199
|
+
? (...args: Args) => Effect.Effect<Success, WorkspaceGitError>
|
|
200
|
+
: never;
|
|
201
|
+
|
|
202
|
+
type EffectGitClient = {
|
|
203
|
+
readonly [Name in keyof CloudflareGitClient]: EffectGitMethod<CloudflareGitClient[Name]>;
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
/** Complete Effect wrapper of the `@cloudflare/computer/git` client. */
|
|
207
|
+
export type ComputerWorkspaceGit = Omit<EffectGitClient, "log" | "revParse"> & {
|
|
208
|
+
readonly log: (
|
|
209
|
+
options?: WorkspaceGitLogOptions,
|
|
210
|
+
) => Effect.Effect<ReadonlyArray<WorkspaceGitCommit>, WorkspaceGitError>;
|
|
211
|
+
readonly revParse: {
|
|
212
|
+
(ref: string): Effect.Effect<string, WorkspaceGitError>;
|
|
213
|
+
(
|
|
214
|
+
options: Parameters<CloudflareGitClient["revParse"]>[0],
|
|
215
|
+
): Effect.Effect<string, WorkspaceGitError>;
|
|
216
|
+
};
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
export type WorkspaceExecSignal = NonNullable<CloudflareRuntimeKillOptions["signal"]>;
|
|
220
|
+
export type WorkspaceRuntimeValue = CloudflareWorkspaceRuntimeValue;
|
|
221
|
+
export type WorkspaceExecEncoding = "utf8" | "binary";
|
|
222
|
+
export type WorkspaceExecSyncResult = CloudflareExecSyncResult;
|
|
223
|
+
export type WorkspaceExecSkippedEntry = CloudflareSkippedEntry;
|
|
224
|
+
|
|
225
|
+
type CloudflareEncoding<Encoding extends WorkspaceExecEncoding> = Encoding extends "utf8"
|
|
226
|
+
? "utf8"
|
|
227
|
+
: undefined;
|
|
228
|
+
|
|
229
|
+
export type WorkspaceExecChunk<Encoding extends WorkspaceExecEncoding> = Encoding extends "utf8"
|
|
230
|
+
? string
|
|
231
|
+
: Uint8Array;
|
|
232
|
+
|
|
233
|
+
export type WorkspaceExecOptions<Encoding extends WorkspaceExecEncoding = "utf8"> = Omit<
|
|
234
|
+
CloudflareRuntimeExecOptions,
|
|
235
|
+
"encoding" | "timeoutMs"
|
|
236
|
+
> & {
|
|
237
|
+
readonly encoding?: Encoding;
|
|
238
|
+
/** Upstream runtime option name. */
|
|
239
|
+
readonly timeoutMs?: number;
|
|
240
|
+
/** Consumer-port compatibility alias for `timeoutMs`. */
|
|
241
|
+
readonly timeoutMillis?: number;
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
export type WorkspaceGetExecOptions<Encoding extends WorkspaceExecEncoding = "utf8"> = Omit<
|
|
245
|
+
CloudflareRuntimeGetOptions,
|
|
246
|
+
"encoding"
|
|
247
|
+
> & {
|
|
248
|
+
readonly encoding?: Encoding;
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
export type WorkspaceExecEvent<Encoding extends WorkspaceExecEncoding = "utf8"> =
|
|
252
|
+
| ({
|
|
253
|
+
readonly _tag: "Stdout";
|
|
254
|
+
readonly id: string;
|
|
255
|
+
readonly sequence: number;
|
|
256
|
+
readonly chunk: WorkspaceExecChunk<Encoding>;
|
|
257
|
+
} & (Encoding extends "utf8" ? { readonly text: string } : { readonly bytes: Uint8Array }))
|
|
258
|
+
| ({
|
|
259
|
+
readonly _tag: "Stderr";
|
|
260
|
+
readonly id: string;
|
|
261
|
+
readonly sequence: number;
|
|
262
|
+
readonly chunk: WorkspaceExecChunk<Encoding>;
|
|
263
|
+
} & (Encoding extends "utf8" ? { readonly text: string } : { readonly bytes: Uint8Array }))
|
|
264
|
+
| {
|
|
265
|
+
readonly _tag: "Exit";
|
|
266
|
+
readonly id: string;
|
|
267
|
+
readonly sequence: number;
|
|
268
|
+
readonly exitCode: number;
|
|
269
|
+
readonly value?: WorkspaceRuntimeValue;
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
export interface WorkspaceExecResult<Encoding extends WorkspaceExecEncoding = "utf8"> {
|
|
273
|
+
readonly status: CloudflareWorkspaceRuntimeResult<CloudflareEncoding<Encoding>>["status"];
|
|
274
|
+
readonly exitCode: number;
|
|
275
|
+
readonly stdout: WorkspaceExecChunk<Encoding>;
|
|
276
|
+
readonly stderr: WorkspaceExecChunk<Encoding>;
|
|
277
|
+
readonly value?: WorkspaceRuntimeValue;
|
|
278
|
+
readonly pushed: number;
|
|
279
|
+
readonly pulled: number;
|
|
280
|
+
readonly skipped: ReadonlyArray<WorkspaceExecSkippedEntry>;
|
|
281
|
+
readonly sync: WorkspaceExecSyncResult;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
export interface WorkspaceExecRun<Encoding extends WorkspaceExecEncoding = "utf8"> {
|
|
285
|
+
readonly id: string;
|
|
286
|
+
readonly backend: string;
|
|
287
|
+
/**
|
|
288
|
+
* Live output. Like the upstream handle, `events` and `result` are mutually
|
|
289
|
+
* exclusive consumers of a run.
|
|
290
|
+
*/
|
|
291
|
+
readonly events: Stream.Stream<WorkspaceExecEvent<Encoding>, WorkspaceExecError>;
|
|
292
|
+
readonly result: Effect.Effect<WorkspaceExecResult<Encoding>, WorkspaceExecError>;
|
|
293
|
+
readonly kill: (signal?: WorkspaceExecSignal) => Effect.Effect<void, WorkspaceExecError>;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
export interface WorkspaceExec {
|
|
297
|
+
(
|
|
298
|
+
strings: TemplateStringsArray,
|
|
299
|
+
...values: Array<CloudflareShellValue>
|
|
300
|
+
): Effect.Effect<WorkspaceExecRun<"utf8">, WorkspaceExecError, Scope.Scope>;
|
|
301
|
+
(
|
|
302
|
+
command: string,
|
|
303
|
+
options?: WorkspaceExecOptions<"utf8">,
|
|
304
|
+
): Effect.Effect<WorkspaceExecRun<"utf8">, WorkspaceExecError, Scope.Scope>;
|
|
305
|
+
(
|
|
306
|
+
command: string,
|
|
307
|
+
options: WorkspaceExecOptions<"binary">,
|
|
308
|
+
): Effect.Effect<WorkspaceExecRun<"binary">, WorkspaceExecError, Scope.Scope>;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
export interface WorkspaceGetExec {
|
|
312
|
+
(
|
|
313
|
+
id: string,
|
|
314
|
+
options?: WorkspaceGetExecOptions<"utf8">,
|
|
315
|
+
): Effect.Effect<WorkspaceExecRun<"utf8">, WorkspaceExecError, Scope.Scope>;
|
|
316
|
+
(
|
|
317
|
+
id: string,
|
|
318
|
+
options: WorkspaceGetExecOptions<"binary">,
|
|
319
|
+
): Effect.Effect<WorkspaceExecRun<"binary">, WorkspaceExecError, Scope.Scope>;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
export interface WorkspaceExecCollect {
|
|
323
|
+
(
|
|
324
|
+
strings: TemplateStringsArray,
|
|
325
|
+
...values: Array<CloudflareShellValue>
|
|
326
|
+
): Effect.Effect<WorkspaceExecResult<"utf8">, WorkspaceExecError>;
|
|
327
|
+
(
|
|
328
|
+
command: string,
|
|
329
|
+
options?: WorkspaceExecOptions<"utf8">,
|
|
330
|
+
): Effect.Effect<WorkspaceExecResult<"utf8">, WorkspaceExecError>;
|
|
331
|
+
(
|
|
332
|
+
command: string,
|
|
333
|
+
options: WorkspaceExecOptions<"binary">,
|
|
334
|
+
): Effect.Effect<WorkspaceExecResult<"binary">, WorkspaceExecError>;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/** Complete Effect wrapper of `WorkspaceClient.runtime`. */
|
|
338
|
+
export interface ComputerWorkspaceRuntime {
|
|
339
|
+
readonly exec: WorkspaceExec;
|
|
340
|
+
readonly execCollect: WorkspaceExecCollect;
|
|
341
|
+
readonly getExec: WorkspaceGetExec;
|
|
342
|
+
readonly killExec: (
|
|
343
|
+
id: string,
|
|
344
|
+
options?: CloudflareRuntimeKillOptions,
|
|
345
|
+
) => Effect.Effect<void, WorkspaceExecError>;
|
|
346
|
+
readonly disposeExec: (
|
|
347
|
+
id: string,
|
|
348
|
+
options?: { readonly backend?: string },
|
|
349
|
+
) => Effect.Effect<void, WorkspaceExecError>;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/** Effect wrapper over a configured local `Workspace.assets` client. */
|
|
353
|
+
export interface ComputerWorkspaceAssets {
|
|
354
|
+
readonly share: (
|
|
355
|
+
path: string,
|
|
356
|
+
options: CloudflareComputerShareOptions,
|
|
357
|
+
) => Effect.Effect<string, WorkspaceAssetsError>;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
type EffectThinkMethod<Method> = Method extends (...args: infer Args) => Promise<infer Success>
|
|
361
|
+
? (...args: Args) => Effect.Effect<Success, WorkspaceFsError>
|
|
362
|
+
: never;
|
|
363
|
+
|
|
364
|
+
/** Effect wrapper of the optional Think compatibility surface. */
|
|
365
|
+
export type ComputerWorkspaceThink = {
|
|
366
|
+
readonly [Name in keyof CloudflareThinkWorkspaceCompatibility]: EffectThinkMethod<
|
|
367
|
+
CloudflareThinkWorkspaceCompatibility[Name]
|
|
368
|
+
>;
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
/** Effect service over every client surface exposed by Cloudflare Computer. */
|
|
372
|
+
export interface ComputerWorkspaceShape {
|
|
373
|
+
readonly fs: ComputerWorkspaceFilesystem;
|
|
374
|
+
readonly git: ComputerWorkspaceGit;
|
|
375
|
+
readonly runtime: ComputerWorkspaceRuntime;
|
|
376
|
+
readonly artifacts: ComputerArtifacts.ComputerArtifactsClient;
|
|
377
|
+
readonly assets: ComputerWorkspaceAssets | undefined;
|
|
378
|
+
readonly think: ComputerWorkspaceThink | undefined;
|
|
379
|
+
|
|
380
|
+
/** Flat filesystem aliases retained for mechanical consumer migration. */
|
|
381
|
+
readonly readFile: WorkspaceReadFile;
|
|
382
|
+
readonly stat: ComputerWorkspaceFilesystem["stat"];
|
|
383
|
+
readonly lstat: ComputerWorkspaceFilesystem["lstat"];
|
|
384
|
+
readonly exists: ComputerWorkspaceFilesystem["exists"];
|
|
385
|
+
readonly readlink: ComputerWorkspaceFilesystem["readlink"];
|
|
386
|
+
readonly readdir: ComputerWorkspaceFilesystem["readdir"];
|
|
387
|
+
readonly listDirectory: ComputerWorkspaceFilesystem["readdir"];
|
|
388
|
+
readonly find: ComputerWorkspaceFilesystem["find"];
|
|
389
|
+
readonly ls: ComputerWorkspaceFilesystem["ls"];
|
|
390
|
+
readonly grep: ComputerWorkspaceFilesystem["grep"];
|
|
391
|
+
readonly writeFile: ComputerWorkspaceFilesystem["writeFile"];
|
|
392
|
+
readonly mkdir: ComputerWorkspaceFilesystem["mkdir"];
|
|
393
|
+
readonly rm: ComputerWorkspaceFilesystem["rm"];
|
|
394
|
+
readonly removePath: ComputerWorkspaceFilesystem["rm"];
|
|
395
|
+
readonly chmod: ComputerWorkspaceFilesystem["chmod"];
|
|
396
|
+
readonly symlink: ComputerWorkspaceFilesystem["symlink"];
|
|
397
|
+
|
|
398
|
+
/** Flat runtime aliases retained for mechanical consumer migration. */
|
|
399
|
+
readonly exec: WorkspaceExec;
|
|
400
|
+
readonly execCollect: WorkspaceExecCollect;
|
|
401
|
+
readonly getExec: WorkspaceGetExec;
|
|
402
|
+
readonly killExec: ComputerWorkspaceRuntime["killExec"];
|
|
403
|
+
readonly disposeExec: ComputerWorkspaceRuntime["disposeExec"];
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
const errorMessage = (cause: unknown): string =>
|
|
407
|
+
cause instanceof Error ? cause.message : String(cause);
|
|
408
|
+
|
|
409
|
+
const errorCode = (cause: unknown): string | undefined => {
|
|
410
|
+
if (typeof cause !== "object" || cause === null) {
|
|
411
|
+
return undefined;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
const code = Reflect.get(cause, "code");
|
|
415
|
+
|
|
416
|
+
return typeof code === "string" ? code : undefined;
|
|
417
|
+
};
|
|
418
|
+
|
|
419
|
+
const fsError = (operation: string, path: string, cause: unknown) =>
|
|
420
|
+
WorkspaceFsError.make({
|
|
421
|
+
operation,
|
|
422
|
+
path,
|
|
423
|
+
message: errorMessage(cause),
|
|
424
|
+
cause,
|
|
425
|
+
...(errorCode(cause) === undefined ? {} : { code: errorCode(cause) }),
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
const gitError = (operation: string, cause: unknown) =>
|
|
429
|
+
WorkspaceGitError.make({
|
|
430
|
+
operation,
|
|
431
|
+
message: errorMessage(cause),
|
|
432
|
+
cause,
|
|
433
|
+
...(errorCode(cause) === undefined ? {} : { code: errorCode(cause) }),
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
const execError = (operation: string, cause: unknown) =>
|
|
437
|
+
WorkspaceExecError.make({
|
|
438
|
+
operation,
|
|
439
|
+
message: errorMessage(cause),
|
|
440
|
+
cause,
|
|
441
|
+
...(errorCode(cause) === undefined ? {} : { code: errorCode(cause) }),
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
const assetsError = (operation: string, path: string, cause: unknown) =>
|
|
445
|
+
WorkspaceAssetsError.make({
|
|
446
|
+
operation,
|
|
447
|
+
path,
|
|
448
|
+
message: errorMessage(cause),
|
|
449
|
+
cause,
|
|
450
|
+
...(errorCode(cause) === undefined ? {} : { code: errorCode(cause) }),
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
const tryFs = <A>(operation: string, path: string, evaluate: () => Promise<A>) =>
|
|
454
|
+
Effect.tryPromise({
|
|
455
|
+
try: evaluate,
|
|
456
|
+
catch: (cause) => fsError(operation, path, cause),
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
const isNotFound = (error: WorkspaceFsError): boolean =>
|
|
460
|
+
error.code === "ENOENT" || /ENOENT|no such/i.test(error.message);
|
|
461
|
+
|
|
462
|
+
const makeFilesystem = (fsClient: CloudflareWorkspaceFilesystem): ComputerWorkspaceFilesystem => {
|
|
463
|
+
const readFileImplementation = Effect.fn("ComputerWorkspace.fs.readFile", {
|
|
464
|
+
attributes: { family: "fs", operation: "readFile" },
|
|
465
|
+
})(function* (
|
|
466
|
+
path: string,
|
|
467
|
+
options: WorkspaceReadFileOptions & { readonly encoding?: WorkspaceReadEncoding } = {},
|
|
468
|
+
) {
|
|
469
|
+
yield* Effect.annotateCurrentSpan("path", path);
|
|
470
|
+
|
|
471
|
+
const { encoding = "utf8", ...readOptions } = options;
|
|
472
|
+
|
|
473
|
+
if (encoding === "stream") {
|
|
474
|
+
return yield* tryFs("readFile", path, () => fsClient.readFile(path, readOptions));
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
return yield* tryFs("readFile", path, () =>
|
|
478
|
+
fsClient.readFile(path, { ...readOptions, encoding: "utf8" }),
|
|
479
|
+
);
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
const readFile = readFileImplementation as WorkspaceReadFile;
|
|
483
|
+
|
|
484
|
+
const stat = Effect.fn("ComputerWorkspace.fs.stat", {
|
|
485
|
+
attributes: { family: "fs", operation: "stat" },
|
|
486
|
+
})(function* (path: string) {
|
|
487
|
+
yield* Effect.annotateCurrentSpan("path", path);
|
|
488
|
+
|
|
489
|
+
return yield* tryFs("stat", path, () => fsClient.stat(path));
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
const lstat = Effect.fn("ComputerWorkspace.fs.lstat", {
|
|
493
|
+
attributes: { family: "fs", operation: "lstat" },
|
|
494
|
+
})(function* (path: string) {
|
|
495
|
+
yield* Effect.annotateCurrentSpan("path", path);
|
|
496
|
+
|
|
497
|
+
return yield* tryFs("lstat", path, () => fsClient.lstat(path));
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
const exists = Effect.fn("ComputerWorkspace.fs.exists", {
|
|
501
|
+
attributes: { family: "fs", operation: "exists" },
|
|
502
|
+
})(function* (path: string) {
|
|
503
|
+
yield* Effect.annotateCurrentSpan("path", path);
|
|
504
|
+
|
|
505
|
+
return yield* stat(path).pipe(
|
|
506
|
+
Effect.as(true),
|
|
507
|
+
Effect.catch((error) => (isNotFound(error) ? Effect.succeed(false) : Effect.fail(error))),
|
|
508
|
+
);
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
const readlink = Effect.fn("ComputerWorkspace.fs.readlink", {
|
|
512
|
+
attributes: { family: "fs", operation: "readlink" },
|
|
513
|
+
})(function* (path: string) {
|
|
514
|
+
yield* Effect.annotateCurrentSpan("path", path);
|
|
515
|
+
|
|
516
|
+
return yield* tryFs("readlink", path, () => fsClient.readlink(path));
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
const readdir = Effect.fn("ComputerWorkspace.fs.readdir", {
|
|
520
|
+
attributes: { family: "fs", operation: "readdir" },
|
|
521
|
+
})(function* (path: string, options?: ListDirectoryOptions) {
|
|
522
|
+
yield* Effect.annotateCurrentSpan("path", path);
|
|
523
|
+
|
|
524
|
+
return yield* tryFs("readdir", path, () => fsClient.readdir(path, options));
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
const find = Effect.fn("ComputerWorkspace.fs.find", {
|
|
528
|
+
attributes: { family: "fs", operation: "find" },
|
|
529
|
+
})(function* (directory: string, pattern?: string, options?: WorkspaceFindOptions) {
|
|
530
|
+
yield* Effect.annotateCurrentSpan({ path: directory, pattern });
|
|
531
|
+
|
|
532
|
+
return yield* tryFs("find", directory, () => fsClient.find(directory, pattern, options));
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
const ls = Effect.fn("ComputerWorkspace.fs.ls", {
|
|
536
|
+
attributes: { family: "fs", operation: "ls" },
|
|
537
|
+
})(function* (prefix: string) {
|
|
538
|
+
yield* Effect.annotateCurrentSpan("path", prefix);
|
|
539
|
+
|
|
540
|
+
return yield* tryFs("ls", prefix, () => fsClient.ls(prefix));
|
|
541
|
+
});
|
|
542
|
+
|
|
543
|
+
const grep = Effect.fn("ComputerWorkspace.fs.grep", {
|
|
544
|
+
attributes: { family: "fs", operation: "grep" },
|
|
545
|
+
})(function* (pattern: string, path: string, options?: WorkspaceGrepOptions) {
|
|
546
|
+
yield* Effect.annotateCurrentSpan({ path, pattern });
|
|
547
|
+
|
|
548
|
+
return yield* tryFs("grep", path, () => fsClient.grep(pattern, path, options));
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
const writeFile = Effect.fn("ComputerWorkspace.fs.writeFile", {
|
|
552
|
+
attributes: { family: "fs", operation: "writeFile" },
|
|
553
|
+
})(function* (
|
|
554
|
+
path: string,
|
|
555
|
+
content: WorkspaceWriteFileContent,
|
|
556
|
+
options?: WorkspaceWriteFileOptions,
|
|
557
|
+
) {
|
|
558
|
+
yield* Effect.annotateCurrentSpan("path", path);
|
|
559
|
+
yield* tryFs("writeFile", path, () => fsClient.writeFile(path, content, options));
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
const mkdir = Effect.fn("ComputerWorkspace.fs.mkdir", {
|
|
563
|
+
attributes: { family: "fs", operation: "mkdir" },
|
|
564
|
+
})(function* (path: string, options?: WorkspaceMkdirOptions) {
|
|
565
|
+
yield* Effect.annotateCurrentSpan("path", path);
|
|
566
|
+
yield* tryFs("mkdir", path, () => fsClient.mkdir(path, options));
|
|
567
|
+
});
|
|
568
|
+
|
|
569
|
+
const rm = Effect.fn("ComputerWorkspace.fs.rm", {
|
|
570
|
+
attributes: { family: "fs", operation: "rm" },
|
|
571
|
+
})(function* (path: string, options?: RemovePathOptions) {
|
|
572
|
+
yield* Effect.annotateCurrentSpan("path", path);
|
|
573
|
+
yield* tryFs("rm", path, () => fsClient.rm(path, options));
|
|
574
|
+
});
|
|
575
|
+
|
|
576
|
+
const chmod = Effect.fn("ComputerWorkspace.fs.chmod", {
|
|
577
|
+
attributes: { family: "fs", operation: "chmod" },
|
|
578
|
+
})(function* (path: string, mode: number) {
|
|
579
|
+
yield* Effect.annotateCurrentSpan({ path, mode });
|
|
580
|
+
yield* tryFs("chmod", path, () => fsClient.chmod(path, mode));
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
const symlink = Effect.fn("ComputerWorkspace.fs.symlink", {
|
|
584
|
+
attributes: { family: "fs", operation: "symlink" },
|
|
585
|
+
})(function* (target: string, path: string) {
|
|
586
|
+
yield* Effect.annotateCurrentSpan({ path, target });
|
|
587
|
+
yield* tryFs("symlink", path, () => fsClient.symlink(target, path));
|
|
588
|
+
});
|
|
589
|
+
|
|
590
|
+
return {
|
|
591
|
+
readFile,
|
|
592
|
+
stat,
|
|
593
|
+
lstat,
|
|
594
|
+
exists,
|
|
595
|
+
readlink,
|
|
596
|
+
readdir,
|
|
597
|
+
find,
|
|
598
|
+
ls,
|
|
599
|
+
grep,
|
|
600
|
+
writeFile,
|
|
601
|
+
mkdir,
|
|
602
|
+
rm,
|
|
603
|
+
chmod,
|
|
604
|
+
symlink,
|
|
605
|
+
};
|
|
606
|
+
};
|
|
607
|
+
|
|
608
|
+
const gitPerson = (person: CloudflareCommitView["author"]): WorkspaceGitPerson => ({
|
|
609
|
+
...person,
|
|
610
|
+
timestampSeconds: person.timestamp,
|
|
611
|
+
});
|
|
612
|
+
|
|
613
|
+
const gitCommit = (
|
|
614
|
+
commit: Awaited<ReturnType<CloudflareGitClient["show"]>>,
|
|
615
|
+
): WorkspaceGitCommit => ({
|
|
616
|
+
oid: commit.oid,
|
|
617
|
+
message: commit.message,
|
|
618
|
+
tree: commit.tree,
|
|
619
|
+
parent: commit.parent,
|
|
620
|
+
parents: commit.parent,
|
|
621
|
+
author: gitPerson(commit.author),
|
|
622
|
+
committer: gitPerson(commit.committer),
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
const makeGit = (gitClient: CloudflareGitClient): ComputerWorkspaceGit => {
|
|
626
|
+
const method = <Args extends Array<unknown>, Success>(
|
|
627
|
+
operation: string,
|
|
628
|
+
evaluate: (...args: Args) => Promise<Success>,
|
|
629
|
+
) =>
|
|
630
|
+
Effect.fn(`ComputerWorkspace.git.${operation}`, {
|
|
631
|
+
attributes: { family: "git", operation },
|
|
632
|
+
})(function* (...args: Args) {
|
|
633
|
+
return yield* Effect.tryPromise({
|
|
634
|
+
try: () => evaluate(...args),
|
|
635
|
+
catch: (cause) => gitError(operation, cause),
|
|
636
|
+
});
|
|
637
|
+
});
|
|
638
|
+
|
|
639
|
+
const log = Effect.fn("ComputerWorkspace.git.log", {
|
|
640
|
+
attributes: { family: "git", operation: "log" },
|
|
641
|
+
})(function* (options?: WorkspaceGitLogOptions) {
|
|
642
|
+
const { maxCount, ...upstreamOptions } = options ?? {};
|
|
643
|
+
const commits = yield* Effect.tryPromise({
|
|
644
|
+
try: () =>
|
|
645
|
+
gitClient.log({
|
|
646
|
+
...upstreamOptions,
|
|
647
|
+
...(upstreamOptions.depth === undefined && maxCount !== undefined
|
|
648
|
+
? { depth: maxCount }
|
|
649
|
+
: {}),
|
|
650
|
+
}),
|
|
651
|
+
catch: (cause) => gitError("log", cause),
|
|
652
|
+
});
|
|
653
|
+
|
|
654
|
+
return commits.map(gitCommit);
|
|
655
|
+
});
|
|
656
|
+
|
|
657
|
+
const revParseImplementation = Effect.fn("ComputerWorkspace.git.revParse", {
|
|
658
|
+
attributes: { family: "git", operation: "revParse" },
|
|
659
|
+
})(function* (input: string | Parameters<CloudflareGitClient["revParse"]>[0]) {
|
|
660
|
+
const options = typeof input === "string" ? { ref: input } : input;
|
|
661
|
+
|
|
662
|
+
return yield* Effect.tryPromise({
|
|
663
|
+
try: () => gitClient.revParse(options),
|
|
664
|
+
catch: (cause) => gitError("revParse", cause),
|
|
665
|
+
});
|
|
666
|
+
});
|
|
667
|
+
|
|
668
|
+
const revParse = revParseImplementation as ComputerWorkspaceGit["revParse"];
|
|
669
|
+
|
|
670
|
+
return {
|
|
671
|
+
clone: method("clone", (options) => gitClient.clone(options as CloudflareGitCloneOptions)),
|
|
672
|
+
diff: method("diff", (options) => gitClient.diff(options as CloudflareGitDiffOptions)),
|
|
673
|
+
diffSummary: method("diffSummary", (options) =>
|
|
674
|
+
gitClient.diffSummary(options as CloudflareGitDiffOptions),
|
|
675
|
+
),
|
|
676
|
+
init: method("init", (options) => gitClient.init(options)),
|
|
677
|
+
status: method("status", (options) => gitClient.status(options)),
|
|
678
|
+
add: method("add", (options) => gitClient.add(options)),
|
|
679
|
+
rm: method("rm", (options) => gitClient.rm(options)),
|
|
680
|
+
commit: method("commit", (options) => gitClient.commit(options as CloudflareGitCommitOptions)),
|
|
681
|
+
log,
|
|
682
|
+
show: method("show", (options) => gitClient.show(options)),
|
|
683
|
+
revParse,
|
|
684
|
+
repoRoot: method("repoRoot", (options) => gitClient.repoRoot(options)),
|
|
685
|
+
currentBranch: method("currentBranch", (options) => gitClient.currentBranch(options)),
|
|
686
|
+
lsFiles: method("lsFiles", (options) => gitClient.lsFiles(options)),
|
|
687
|
+
lsTree: method("lsTree", (options) => gitClient.lsTree(options)),
|
|
688
|
+
branch: method("branch", (options) => gitClient.branch(options)),
|
|
689
|
+
branchDelete: method("branchDelete", (options) => gitClient.branchDelete(options)),
|
|
690
|
+
branchList: method("branchList", (options) => gitClient.branchList(options)),
|
|
691
|
+
tag: method("tag", (options) => gitClient.tag(options)),
|
|
692
|
+
tagDelete: method("tagDelete", (options) => gitClient.tagDelete(options)),
|
|
693
|
+
tagList: method("tagList", (options) => gitClient.tagList(options)),
|
|
694
|
+
checkout: method("checkout", (options) => gitClient.checkout(options)),
|
|
695
|
+
fetch: method("fetch", (options) => gitClient.fetch(options as CloudflareGitFetchOptions)),
|
|
696
|
+
push: method("push", (options) => gitClient.push(options)),
|
|
697
|
+
pull: method("pull", (options) => gitClient.pull(options)),
|
|
698
|
+
merge: method("merge", (options) => gitClient.merge(options)),
|
|
699
|
+
remoteAdd: method("remoteAdd", (options) => gitClient.remoteAdd(options)),
|
|
700
|
+
remoteRemove: method("remoteRemove", (options) => gitClient.remoteRemove(options)),
|
|
701
|
+
remoteList: method("remoteList", (options) => gitClient.remoteList(options)),
|
|
702
|
+
hashObject: method("hashObject", (options) => gitClient.hashObject(options)),
|
|
703
|
+
catFile: method("catFile", (options) => gitClient.catFile(options)),
|
|
704
|
+
updateRef: method("updateRef", (options) =>
|
|
705
|
+
gitClient.updateRef(options as CloudflareGitUpdateRefOptions),
|
|
706
|
+
),
|
|
707
|
+
configGet: method("configGet", (options) => gitClient.configGet(options)),
|
|
708
|
+
configSet: method("configSet", (options) => gitClient.configSet(options)),
|
|
709
|
+
stashPush: method("stashPush", (options) => gitClient.stashPush(options)),
|
|
710
|
+
stashList: method("stashList", (options) => gitClient.stashList(options)),
|
|
711
|
+
stashPop: method("stashPop", (options) => gitClient.stashPop(options)),
|
|
712
|
+
reset: method("reset", (options) => gitClient.reset(options)),
|
|
713
|
+
clean: method("clean", (options) => gitClient.clean(options)),
|
|
714
|
+
cli: method("cli", (input) => gitClient.cli(input)),
|
|
715
|
+
} as ComputerWorkspaceGit;
|
|
716
|
+
};
|
|
717
|
+
|
|
718
|
+
const runtimeExecOptions = <Encoding extends WorkspaceExecEncoding>(
|
|
719
|
+
options?: WorkspaceExecOptions<Encoding>,
|
|
720
|
+
): CloudflareRuntimeExecOptions => {
|
|
721
|
+
const { encoding = "utf8", timeoutMillis, timeoutMs, ...rest } = options ?? {};
|
|
722
|
+
|
|
723
|
+
return {
|
|
724
|
+
...rest,
|
|
725
|
+
...(encoding === "utf8" ? { encoding: "utf8" as const } : {}),
|
|
726
|
+
...(timeoutMs === undefined && timeoutMillis === undefined
|
|
727
|
+
? {}
|
|
728
|
+
: { timeoutMs: timeoutMs ?? timeoutMillis }),
|
|
729
|
+
};
|
|
730
|
+
};
|
|
731
|
+
|
|
732
|
+
const runtimeGetOptions = <Encoding extends WorkspaceExecEncoding>(
|
|
733
|
+
options?: WorkspaceGetExecOptions<Encoding>,
|
|
734
|
+
): CloudflareRuntimeGetOptions => {
|
|
735
|
+
const { encoding = "utf8", ...rest } = options ?? {};
|
|
736
|
+
|
|
737
|
+
return {
|
|
738
|
+
...rest,
|
|
739
|
+
...(encoding === "utf8" ? { encoding: "utf8" as const } : {}),
|
|
740
|
+
};
|
|
741
|
+
};
|
|
742
|
+
|
|
743
|
+
const wrapRuntimeResult = <Encoding extends WorkspaceExecEncoding>(
|
|
744
|
+
result: CloudflareWorkspaceRuntimeResult<CloudflareEncoding<Encoding>>,
|
|
745
|
+
): WorkspaceExecResult<Encoding> => ({
|
|
746
|
+
status: result.status,
|
|
747
|
+
exitCode: result.exitCode,
|
|
748
|
+
stdout: result.stdout as WorkspaceExecChunk<Encoding>,
|
|
749
|
+
stderr: result.stderr as WorkspaceExecChunk<Encoding>,
|
|
750
|
+
...(result.value === undefined ? {} : { value: result.value }),
|
|
751
|
+
pushed: result.pushed,
|
|
752
|
+
pulled: result.pulled,
|
|
753
|
+
skipped: result.skipped,
|
|
754
|
+
sync: result.sync,
|
|
755
|
+
});
|
|
756
|
+
|
|
757
|
+
const wrapRuntimeEvent = <Encoding extends WorkspaceExecEncoding>(
|
|
758
|
+
event: CloudflareWorkspaceRuntimeEvent<CloudflareEncoding<Encoding>>,
|
|
759
|
+
): WorkspaceExecEvent<Encoding> => {
|
|
760
|
+
switch (event.name) {
|
|
761
|
+
case "stdout": {
|
|
762
|
+
const chunk = event.value as unknown as string | Uint8Array;
|
|
763
|
+
|
|
764
|
+
return (typeof chunk === "string"
|
|
765
|
+
? { _tag: "Stdout", id: event.id, sequence: event.seq, chunk, text: chunk }
|
|
766
|
+
: {
|
|
767
|
+
_tag: "Stdout",
|
|
768
|
+
id: event.id,
|
|
769
|
+
sequence: event.seq,
|
|
770
|
+
chunk,
|
|
771
|
+
bytes: chunk,
|
|
772
|
+
}) as unknown as WorkspaceExecEvent<Encoding>;
|
|
773
|
+
}
|
|
774
|
+
case "stderr": {
|
|
775
|
+
const chunk = event.value as unknown as string | Uint8Array;
|
|
776
|
+
|
|
777
|
+
return (typeof chunk === "string"
|
|
778
|
+
? { _tag: "Stderr", id: event.id, sequence: event.seq, chunk, text: chunk }
|
|
779
|
+
: {
|
|
780
|
+
_tag: "Stderr",
|
|
781
|
+
id: event.id,
|
|
782
|
+
sequence: event.seq,
|
|
783
|
+
chunk,
|
|
784
|
+
bytes: chunk,
|
|
785
|
+
}) as unknown as WorkspaceExecEvent<Encoding>;
|
|
786
|
+
}
|
|
787
|
+
case "exit":
|
|
788
|
+
return {
|
|
789
|
+
_tag: "Exit",
|
|
790
|
+
id: event.id,
|
|
791
|
+
sequence: event.seq,
|
|
792
|
+
exitCode: event.code,
|
|
793
|
+
...(event.result === undefined ? {} : { value: event.result }),
|
|
794
|
+
};
|
|
795
|
+
}
|
|
796
|
+
};
|
|
797
|
+
|
|
798
|
+
const wrapRun = <Encoding extends WorkspaceExecEncoding>(
|
|
799
|
+
handle: CloudflareWorkspaceRuntimeExecHandle<CloudflareEncoding<Encoding>>,
|
|
800
|
+
): WorkspaceExecRun<Encoding> => {
|
|
801
|
+
const events = Stream.fromReadableStream<
|
|
802
|
+
CloudflareWorkspaceRuntimeEvent<CloudflareEncoding<Encoding>>,
|
|
803
|
+
WorkspaceExecError
|
|
804
|
+
>({
|
|
805
|
+
evaluate: () => handle,
|
|
806
|
+
onError: (cause) => execError("events", cause),
|
|
807
|
+
}).pipe(Stream.map(wrapRuntimeEvent<Encoding>));
|
|
808
|
+
|
|
809
|
+
const result = Effect.tryPromise({
|
|
810
|
+
try: () => handle.result(),
|
|
811
|
+
catch: (cause) => execError("result", cause),
|
|
812
|
+
}).pipe(Effect.map(wrapRuntimeResult<Encoding>));
|
|
813
|
+
|
|
814
|
+
const kill = Effect.fn("ComputerWorkspace.runtime.run.kill", {
|
|
815
|
+
attributes: { family: "runtime", operation: "run.kill", runId: handle.id },
|
|
816
|
+
})(function* (signal?: WorkspaceExecSignal) {
|
|
817
|
+
yield* Effect.tryPromise({
|
|
818
|
+
try: () => handle.kill(signal),
|
|
819
|
+
catch: (cause) => execError("run.kill", cause),
|
|
820
|
+
});
|
|
821
|
+
});
|
|
822
|
+
|
|
823
|
+
return { id: handle.id, backend: handle.backend, events, result, kill };
|
|
824
|
+
};
|
|
825
|
+
|
|
826
|
+
const releaseRun = (handle: CloudflareWorkspaceRuntimeExecHandle<"utf8" | undefined>) =>
|
|
827
|
+
Effect.sync(() => handle[Symbol.dispose]());
|
|
828
|
+
|
|
829
|
+
const makeRuntime = (client: CloudflareWorkspaceClient): ComputerWorkspaceRuntime => {
|
|
830
|
+
const execImplementation = Effect.fn("ComputerWorkspace.runtime.exec", {
|
|
831
|
+
attributes: { family: "runtime", operation: "exec" },
|
|
832
|
+
})(function* <Encoding extends WorkspaceExecEncoding = "utf8">(
|
|
833
|
+
source: string | TemplateStringsArray,
|
|
834
|
+
optionsOrValue?: WorkspaceExecOptions<Encoding> | CloudflareShellValue,
|
|
835
|
+
...remainingValues: Array<CloudflareShellValue>
|
|
836
|
+
) {
|
|
837
|
+
const options =
|
|
838
|
+
typeof source === "string"
|
|
839
|
+
? (optionsOrValue as WorkspaceExecOptions<Encoding> | undefined)
|
|
840
|
+
: undefined;
|
|
841
|
+
|
|
842
|
+
yield* Effect.annotateCurrentSpan({ backend: options?.backend, runId: options?.id });
|
|
843
|
+
const handle = yield* Effect.acquireRelease(
|
|
844
|
+
Effect.tryPromise({
|
|
845
|
+
try: () => {
|
|
846
|
+
if (typeof source === "string") {
|
|
847
|
+
return client.runtime.exec(source, runtimeExecOptions(options));
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
const values =
|
|
851
|
+
optionsOrValue === undefined
|
|
852
|
+
? remainingValues
|
|
853
|
+
: [optionsOrValue as CloudflareShellValue, ...remainingValues];
|
|
854
|
+
|
|
855
|
+
return client.runtime.exec(source, ...values);
|
|
856
|
+
},
|
|
857
|
+
catch: (cause) => execError("exec", cause),
|
|
858
|
+
}),
|
|
859
|
+
releaseRun,
|
|
860
|
+
);
|
|
861
|
+
|
|
862
|
+
return wrapRun<Encoding>(
|
|
863
|
+
handle as CloudflareWorkspaceRuntimeExecHandle<CloudflareEncoding<Encoding>>,
|
|
864
|
+
);
|
|
865
|
+
});
|
|
866
|
+
const exec = execImplementation as WorkspaceExec;
|
|
867
|
+
|
|
868
|
+
const getExecImplementation = Effect.fn("ComputerWorkspace.runtime.getExec", {
|
|
869
|
+
attributes: { family: "runtime", operation: "getExec" },
|
|
870
|
+
})(function* <Encoding extends WorkspaceExecEncoding = "utf8">(
|
|
871
|
+
id: string,
|
|
872
|
+
options?: WorkspaceGetExecOptions<Encoding>,
|
|
873
|
+
) {
|
|
874
|
+
yield* Effect.annotateCurrentSpan({ backend: options?.backend, runId: id });
|
|
875
|
+
const handle = yield* Effect.acquireRelease(
|
|
876
|
+
Effect.tryPromise({
|
|
877
|
+
try: () => client.runtime.getExec(id, runtimeGetOptions(options)),
|
|
878
|
+
catch: (cause) => execError("getExec", cause),
|
|
879
|
+
}),
|
|
880
|
+
releaseRun,
|
|
881
|
+
);
|
|
882
|
+
|
|
883
|
+
return wrapRun<Encoding>(
|
|
884
|
+
handle as CloudflareWorkspaceRuntimeExecHandle<CloudflareEncoding<Encoding>>,
|
|
885
|
+
);
|
|
886
|
+
});
|
|
887
|
+
const getExec = getExecImplementation as WorkspaceGetExec;
|
|
888
|
+
|
|
889
|
+
const killExec = Effect.fn("ComputerWorkspace.runtime.killExec", {
|
|
890
|
+
attributes: { family: "runtime", operation: "killExec" },
|
|
891
|
+
})(function* (id: string, options?: CloudflareRuntimeKillOptions) {
|
|
892
|
+
yield* Effect.annotateCurrentSpan({ backend: options?.backend, runId: id });
|
|
893
|
+
yield* Effect.tryPromise({
|
|
894
|
+
try: () => client.runtime.killExec(id, options),
|
|
895
|
+
catch: (cause) => execError("killExec", cause),
|
|
896
|
+
});
|
|
897
|
+
});
|
|
898
|
+
|
|
899
|
+
const disposeExec = Effect.fn("ComputerWorkspace.runtime.disposeExec", {
|
|
900
|
+
attributes: { family: "runtime", operation: "disposeExec" },
|
|
901
|
+
})(function* (id: string, options?: { readonly backend?: string }) {
|
|
902
|
+
yield* Effect.annotateCurrentSpan({ backend: options?.backend, runId: id });
|
|
903
|
+
yield* Effect.tryPromise({
|
|
904
|
+
try: () => client.runtime.disposeExec(id, options),
|
|
905
|
+
catch: (cause) => execError("disposeExec", cause),
|
|
906
|
+
});
|
|
907
|
+
});
|
|
908
|
+
|
|
909
|
+
const execCollectImplementation = Effect.fn("ComputerWorkspace.runtime.execCollect", {
|
|
910
|
+
attributes: { family: "runtime", operation: "execCollect" },
|
|
911
|
+
})(function* <Encoding extends WorkspaceExecEncoding = "utf8">(
|
|
912
|
+
source: string | TemplateStringsArray,
|
|
913
|
+
optionsOrValue?: WorkspaceExecOptions<Encoding> | CloudflareShellValue,
|
|
914
|
+
...remainingValues: Array<CloudflareShellValue>
|
|
915
|
+
) {
|
|
916
|
+
return yield* Effect.scoped(
|
|
917
|
+
Effect.gen(function* () {
|
|
918
|
+
const run = yield* execImplementation(source, optionsOrValue, ...remainingValues);
|
|
919
|
+
|
|
920
|
+
return yield* run.result;
|
|
921
|
+
}),
|
|
922
|
+
);
|
|
923
|
+
});
|
|
924
|
+
const execCollect = execCollectImplementation as WorkspaceExecCollect;
|
|
925
|
+
|
|
926
|
+
return { exec, execCollect, getExec, killExec, disposeExec };
|
|
927
|
+
};
|
|
928
|
+
|
|
929
|
+
const makeAssets = (
|
|
930
|
+
client: CloudflareComputerAssetsClient | undefined,
|
|
931
|
+
): ComputerWorkspaceAssets | undefined =>
|
|
932
|
+
client === undefined
|
|
933
|
+
? undefined
|
|
934
|
+
: {
|
|
935
|
+
share: Effect.fn("ComputerWorkspace.assets.share", {
|
|
936
|
+
attributes: { family: "assets", operation: "share" },
|
|
937
|
+
})(function* (path: string, options: CloudflareComputerShareOptions) {
|
|
938
|
+
yield* Effect.annotateCurrentSpan("path", path);
|
|
939
|
+
|
|
940
|
+
return yield* Effect.tryPromise({
|
|
941
|
+
try: () => client.share(path, options),
|
|
942
|
+
catch: (cause) => assetsError("share", path, cause),
|
|
943
|
+
});
|
|
944
|
+
}),
|
|
945
|
+
};
|
|
946
|
+
|
|
947
|
+
const makeThink = (client: CloudflareWorkspaceClient): ComputerWorkspaceThink | undefined => {
|
|
948
|
+
if (
|
|
949
|
+
client.readFile === undefined ||
|
|
950
|
+
client.readFileBytes === undefined ||
|
|
951
|
+
client.writeFile === undefined ||
|
|
952
|
+
client.readDir === undefined ||
|
|
953
|
+
client.rm === undefined ||
|
|
954
|
+
client.glob === undefined ||
|
|
955
|
+
client.mkdir === undefined ||
|
|
956
|
+
client.stat === undefined
|
|
957
|
+
) {
|
|
958
|
+
return undefined;
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
const method = <Args extends Array<unknown>, Success>(
|
|
962
|
+
operation: string,
|
|
963
|
+
pathOf: (...args: Args) => string,
|
|
964
|
+
evaluate: (...args: Args) => Promise<Success>,
|
|
965
|
+
) =>
|
|
966
|
+
Effect.fn(`ComputerWorkspace.think.${operation}`, {
|
|
967
|
+
attributes: { family: "think", operation },
|
|
968
|
+
})(function* (...args: Args) {
|
|
969
|
+
const path = pathOf(...args);
|
|
970
|
+
|
|
971
|
+
yield* Effect.annotateCurrentSpan("path", path);
|
|
972
|
+
|
|
973
|
+
return yield* tryFs(`think.${operation}`, path, () => evaluate(...args));
|
|
974
|
+
});
|
|
975
|
+
|
|
976
|
+
return {
|
|
977
|
+
readFile: method(
|
|
978
|
+
"readFile",
|
|
979
|
+
(path) => path as string,
|
|
980
|
+
(path) => client.readFile!(path as string),
|
|
981
|
+
),
|
|
982
|
+
readFileBytes: method(
|
|
983
|
+
"readFileBytes",
|
|
984
|
+
(path) => path as string,
|
|
985
|
+
(path) => client.readFileBytes!(path as string),
|
|
986
|
+
),
|
|
987
|
+
writeFile: method(
|
|
988
|
+
"writeFile",
|
|
989
|
+
(path: string, _content: string) => path,
|
|
990
|
+
(path: string, content: string) => client.writeFile!(path, content),
|
|
991
|
+
),
|
|
992
|
+
readDir: method(
|
|
993
|
+
"readDir",
|
|
994
|
+
(path: string, _options?: { readonly limit?: number; readonly offset?: number }) => path,
|
|
995
|
+
(path: string, options?: { readonly limit?: number; readonly offset?: number }) =>
|
|
996
|
+
client.readDir!(path, options),
|
|
997
|
+
),
|
|
998
|
+
rm: method(
|
|
999
|
+
"rm",
|
|
1000
|
+
(path: string, _options?: { readonly recursive?: boolean; readonly force?: boolean }) => path,
|
|
1001
|
+
(path: string, options?: { readonly recursive?: boolean; readonly force?: boolean }) =>
|
|
1002
|
+
client.rm!(path, options),
|
|
1003
|
+
),
|
|
1004
|
+
glob: method(
|
|
1005
|
+
"glob",
|
|
1006
|
+
(pattern) => pattern as string,
|
|
1007
|
+
(pattern) => client.glob!(pattern as string),
|
|
1008
|
+
),
|
|
1009
|
+
mkdir: method(
|
|
1010
|
+
"mkdir",
|
|
1011
|
+
(path: string, _options?: { readonly recursive?: boolean }) => path,
|
|
1012
|
+
(path: string, options?: { readonly recursive?: boolean }) => client.mkdir!(path, options),
|
|
1013
|
+
),
|
|
1014
|
+
stat: method(
|
|
1015
|
+
"stat",
|
|
1016
|
+
(path) => path as string,
|
|
1017
|
+
(path) => client.stat!(path as string),
|
|
1018
|
+
),
|
|
1019
|
+
} as ComputerWorkspaceThink;
|
|
1020
|
+
};
|
|
1021
|
+
|
|
1022
|
+
/**
|
|
1023
|
+
* Wraps a client whose lifetime is owned by the caller. Prefer
|
|
1024
|
+
* {@link ComputerWorkspace.layer} in Durable Objects so the parent client and
|
|
1025
|
+
* every runtime handle are disposed by Effect `Scope`.
|
|
1026
|
+
*/
|
|
1027
|
+
export const fromWorkspaceClient = (client: CloudflareWorkspaceClient): ComputerWorkspaceShape => {
|
|
1028
|
+
const fs = makeFilesystem(client.fs);
|
|
1029
|
+
const git = makeGit(client.git as CloudflareGitClient);
|
|
1030
|
+
const runtime = makeRuntime(client);
|
|
1031
|
+
const artifacts = ComputerArtifacts.fromClient(
|
|
1032
|
+
client.artifacts as CloudflareComputerArtifactClient,
|
|
1033
|
+
{ binding: "ComputerWorkspace.artifacts" },
|
|
1034
|
+
);
|
|
1035
|
+
const assets = makeAssets(client.assets as CloudflareComputerAssetsClient | undefined);
|
|
1036
|
+
const think = makeThink(client);
|
|
1037
|
+
|
|
1038
|
+
return {
|
|
1039
|
+
fs,
|
|
1040
|
+
git,
|
|
1041
|
+
runtime,
|
|
1042
|
+
artifacts,
|
|
1043
|
+
assets,
|
|
1044
|
+
think,
|
|
1045
|
+
readFile: fs.readFile,
|
|
1046
|
+
stat: fs.stat,
|
|
1047
|
+
lstat: fs.lstat,
|
|
1048
|
+
exists: fs.exists,
|
|
1049
|
+
readlink: fs.readlink,
|
|
1050
|
+
readdir: fs.readdir,
|
|
1051
|
+
listDirectory: fs.readdir,
|
|
1052
|
+
find: fs.find,
|
|
1053
|
+
ls: fs.ls,
|
|
1054
|
+
grep: fs.grep,
|
|
1055
|
+
writeFile: fs.writeFile,
|
|
1056
|
+
mkdir: fs.mkdir,
|
|
1057
|
+
rm: fs.rm,
|
|
1058
|
+
removePath: fs.rm,
|
|
1059
|
+
chmod: fs.chmod,
|
|
1060
|
+
symlink: fs.symlink,
|
|
1061
|
+
exec: runtime.exec,
|
|
1062
|
+
execCollect: runtime.execCollect,
|
|
1063
|
+
getExec: runtime.getExec,
|
|
1064
|
+
killExec: runtime.killExec,
|
|
1065
|
+
disposeExec: runtime.disposeExec,
|
|
1066
|
+
};
|
|
1067
|
+
};
|
|
1068
|
+
|
|
1069
|
+
/** Effect service for the workspace owned by the enclosing Durable Object. */
|
|
1070
|
+
export class ComputerWorkspace extends Context.Service<ComputerWorkspace, ComputerWorkspaceShape>()(
|
|
1071
|
+
"effect-cf/ComputerWorkspace",
|
|
1072
|
+
) {
|
|
1073
|
+
/**
|
|
1074
|
+
* Acquires the single workspace registered by `withComputerWorkspace` and
|
|
1075
|
+
* releases its RPC client when the layer scope closes.
|
|
1076
|
+
*/
|
|
1077
|
+
static readonly layer: Layer.Layer<ComputerWorkspace, never, DurableObjectState> = Layer.effect(
|
|
1078
|
+
ComputerWorkspace,
|
|
1079
|
+
Effect.gen(function* () {
|
|
1080
|
+
const state = yield* DurableObjectState;
|
|
1081
|
+
const host = HostRegistry.lookup(state.raw);
|
|
1082
|
+
|
|
1083
|
+
if (host === undefined) {
|
|
1084
|
+
return yield* Effect.die(
|
|
1085
|
+
"ComputerWorkspace.layer requires a Durable Object class built with withComputerWorkspace",
|
|
1086
|
+
);
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
const computer = yield* Effect.promise(() => import("@cloudflare/computer"));
|
|
1090
|
+
const client = yield* Effect.acquireRelease(
|
|
1091
|
+
Effect.promise(() => computer.getWorkspace(host)),
|
|
1092
|
+
(workspace) => Effect.sync(() => workspace[Symbol.dispose]()),
|
|
1093
|
+
);
|
|
1094
|
+
|
|
1095
|
+
return ComputerWorkspace.of(fromWorkspaceClient(client));
|
|
1096
|
+
}),
|
|
1097
|
+
);
|
|
1098
|
+
}
|