airfoil 0.3.0 → 0.4.1

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.
Files changed (42) hide show
  1. package/dist/index.js +843 -99650
  2. package/package.json +21 -22
  3. package/src/commands/cluster/index.ts +15 -42
  4. package/src/commands/cluster/namespace/create.ts +280 -73
  5. package/src/commands/cluster/namespace/delete.ts +12 -20
  6. package/src/commands/cluster/namespace/get.ts +15 -21
  7. package/src/commands/cluster/namespace/list.ts +17 -44
  8. package/src/commands/cluster/table/create.ts +279 -0
  9. package/src/commands/cluster/table/delete.ts +54 -0
  10. package/src/commands/cluster/table/get.ts +62 -0
  11. package/src/commands/cluster/table/list.ts +61 -0
  12. package/src/commands/dev.ts +73 -172
  13. package/src/commands/sql.ts +31 -62
  14. package/src/index.ts +13 -6
  15. package/src/utils/client.ts +3 -3
  16. package/src/utils/docker.ts +137 -0
  17. package/src/utils/options.ts +2 -4
  18. package/src/utils/wings.ts +72 -64
  19. package/src/commands/cluster/data-lake/create-iceberg.ts +0 -61
  20. package/src/commands/cluster/data-lake/create-parquet.ts +0 -61
  21. package/src/commands/cluster/data-lake/create.ts +0 -16
  22. package/src/commands/cluster/data-lake/delete.ts +0 -58
  23. package/src/commands/cluster/data-lake/get.ts +0 -46
  24. package/src/commands/cluster/data-lake/list.ts +0 -72
  25. package/src/commands/cluster/object-store/create-aws.ts +0 -106
  26. package/src/commands/cluster/object-store/create-azure.ts +0 -100
  27. package/src/commands/cluster/object-store/create-google.ts +0 -102
  28. package/src/commands/cluster/object-store/create-s3.ts +0 -126
  29. package/src/commands/cluster/object-store/create.ts +0 -20
  30. package/src/commands/cluster/object-store/delete.ts +0 -58
  31. package/src/commands/cluster/object-store/get.ts +0 -51
  32. package/src/commands/cluster/object-store/list.ts +0 -72
  33. package/src/commands/cluster/tenant/create.ts +0 -46
  34. package/src/commands/cluster/tenant/delete.ts +0 -62
  35. package/src/commands/cluster/tenant/get.ts +0 -44
  36. package/src/commands/cluster/tenant/list.ts +0 -60
  37. package/src/commands/cluster/topic/create.ts +0 -317
  38. package/src/commands/cluster/topic/delete.ts +0 -67
  39. package/src/commands/cluster/topic/get.ts +0 -51
  40. package/src/commands/cluster/topic/list.ts +0 -70
  41. package/src/scripts/compile-all.ts +0 -21
  42. package/src/utils/effect.ts +0 -11
@@ -1,8 +1,14 @@
1
- import * as p from "@clack/prompts";
2
- import { Effect } from "effect";
3
- import { Command, Flag } from "effect/unstable/cli";
4
- import { handleCliError } from "../utils/effect.js";
5
- import { downloadWings, getWingsPath, verifyChecksum } from "../utils/wings.js";
1
+ import { Effect, FileSystem } from "effect";
2
+ import { Command, Flag, Prompt } from "effect/unstable/cli";
3
+ import { ChildProcess } from "effect/unstable/process";
4
+
5
+ import {
6
+ checkDockerVersion,
7
+ createDockerVolume,
8
+ pullDockerImage,
9
+ runDockerContainer,
10
+ } from "../utils/docker";
11
+ import { downloadWings, getWingsPath } from "../utils/wings";
6
12
 
7
13
  const dockerOption = Flag.boolean("docker").pipe(
8
14
  Flag.withDescription("Run Wings using Docker (recommended)"),
@@ -10,9 +16,7 @@ const dockerOption = Flag.boolean("docker").pipe(
10
16
  );
11
17
 
12
18
  const versionOption = Flag.string("version").pipe(
13
- Flag.withDescription(
14
- "Specify Wings version (e.g., v0.1.0-alpha.11 or 'latest')",
15
- ),
19
+ Flag.withDescription("Specify Wings version (e.g., v0.1.0-alpha.14 or 'latest')"),
16
20
  Flag.withDefault("latest"),
17
21
  );
18
22
 
@@ -47,189 +51,86 @@ export const devCommand = Command.make(
47
51
  stress: stressOption,
48
52
  yes: yesOption,
49
53
  },
50
- (options) =>
51
- Effect.tryPromise({
52
- try: () =>
53
- options.docker ? runWithDocker(options) : runWithBinary(options),
54
- catch: (error) =>
55
- error instanceof Error ? error : new Error("Operation failed"),
56
- }).pipe(Effect.catch(handleCliError("Operation failed"))),
54
+ (options) => (options.docker ? runWithDocker(options) : runWithBinary(options)),
57
55
  ).pipe(
58
56
  Command.withDescription(
59
57
  "Download and run Wings dev server locally (Docker recommended for portability)",
60
58
  ),
61
59
  );
62
60
 
63
- async function runWithBinary(options: {
64
- version: string;
65
- yes: boolean;
66
- stress: boolean;
67
- }) {
68
- p.intro("🪽 Airfoil Dev");
69
-
70
- const version = options.version;
71
- const isStress = options.stress;
72
- const wingsPath = getWingsPath(version, isStress);
73
- const fileExists = await Bun.file(wingsPath).exists();
74
-
75
- if (!fileExists) {
76
- p.log.warn(
77
- `Wings${isStress ? " stress" : ""} binary not found for version ${version}`,
78
- );
79
-
80
- if (!options.yes) {
81
- const confirm = await p.confirm({
82
- message: `Download Wings${isStress ? " stress" : ""} ${version} binary? (~100MB)`,
83
- initialValue: true,
84
- });
85
-
86
- if (p.isCancel(confirm) || !confirm) {
87
- p.cancel("Download cancelled");
88
- process.exit(0);
89
- }
90
- }
61
+ const runWithBinary = (options: { version: string; yes: boolean; stress: boolean }) =>
62
+ Effect.gen(function* () {
63
+ const fs = yield* FileSystem.FileSystem;
91
64
 
92
- const s = p.spinner();
93
- s.start(`Downloading Wings${isStress ? " stress" : ""} ${version}...`);
65
+ yield* Effect.logInfo("🪽 Airfoil Dev");
94
66
 
95
- try {
96
- await downloadWings(version, wingsPath, isStress);
97
- s.stop(`Downloaded Wings${isStress ? " stress" : ""} ${version}`);
98
- } catch (error) {
99
- s.stop("Download failed");
100
- throw error;
101
- }
67
+ const version = options.version;
68
+ const isStress = options.stress;
69
+ const wingsPath = yield* getWingsPath(version, isStress);
70
+ const exists = yield* fs.exists(wingsPath);
102
71
 
103
- const s2 = p.spinner();
104
- s2.start("Verifying checksum...");
72
+ if (!exists) {
73
+ yield* Effect.logWarning(
74
+ `Wings${isStress ? " stress" : ""} binary not found for version ${version}`,
75
+ );
105
76
 
106
- try {
107
- const isValid = await verifyChecksum(version, wingsPath, isStress);
77
+ if (!options.yes) {
78
+ const confirm = yield* Prompt.confirm({
79
+ message: `Download Wings${isStress ? " stress" : ""} ${version} binary? (~100MB)`,
80
+ initial: true,
81
+ });
108
82
 
109
- if (!isValid) {
110
- s2.stop("Checksum verification failed");
111
- throw new Error(
112
- "Binary checksum does not match expected value. This could indicate a corrupted download or security issue.",
113
- );
83
+ if (!confirm) {
84
+ return yield* Effect.fail(new Error("Download cancelled"));
85
+ }
114
86
  }
115
87
 
116
- s2.stop("Checksum verified ✓");
117
- } catch (error) {
118
- s2.stop("Checksum verification failed");
119
- throw error;
88
+ yield* Effect.logInfo(`Downloading Wings${isStress ? " stress" : ""} ${version}...`);
89
+
90
+ yield* downloadWings(version, wingsPath, isStress);
91
+ } else {
92
+ yield* Effect.logInfo(`Using cached Wings${isStress ? " stress" : ""} binary (${version})`);
120
93
  }
121
94
 
122
- await Bun.$`chmod +x ${wingsPath}`;
123
- } else {
124
- p.log.success(
125
- `Using cached Wings${isStress ? " stress" : ""} binary (${version})`,
126
- );
127
- }
95
+ yield* Effect.logInfo(`Starting Wings dev server...`);
128
96
 
129
- p.log.info("Starting Wings dev server...");
97
+ const handle = yield* ChildProcess.make(wingsPath, ["dev"], {
98
+ stdin: "inherit",
99
+ stdout: "inherit",
100
+ });
101
+
102
+ const exitCode = yield* handle.exitCode;
130
103
 
131
- const proc = Bun.spawn([wingsPath, "dev"], {
132
- stdout: "inherit",
133
- stderr: "inherit",
134
- stdin: "inherit",
104
+ if (exitCode !== 0) {
105
+ return yield* Effect.fail(new Error(`Process exited with code ${exitCode}`));
106
+ }
135
107
  });
136
108
 
137
- await proc.exited;
138
- }
139
-
140
- async function runWithDocker(options: { tag: string; forcePull: boolean }) {
141
- p.intro("🐳 Airfoil Dev (Docker)");
142
-
143
- let tag = options.tag;
144
-
145
- // If tag is not "latest" and doesn't include architecture, append it
146
- // Format: 0.1.0-alpha.10-aarch64 or 0.1.0-alpha.10-x86_64
147
- if (tag !== "latest" && !tag.includes("aarch64") && !tag.includes("x86_64")) {
148
- const arch = process.arch === "arm64" ? "aarch64" : "x86_64";
149
- // Remove 'v' prefix if present for docker tags
150
- const cleanTag = tag.startsWith("v") ? tag.substring(1) : tag;
151
- tag = `${cleanTag}-${arch}`;
152
- }
153
-
154
- const image = `docker.useairfoil.com/airfoil/wings:${tag}`;
155
-
156
- p.log.info(`Using Docker image: ${image}`);
157
- p.log.info("Ports: 7777 (metadata), 7780 (http)");
158
-
159
- try {
160
- await Bun.$`docker --version`.quiet();
161
- } catch {
162
- p.cancel(
163
- "Docker is not installed or not running. Please install Docker first.",
164
- );
165
- process.exit(1);
166
- }
167
-
168
- let imageExists = false;
169
- if (!options.forcePull) {
170
- try {
171
- const result = await Bun.$`docker images -q ${image}`.text();
172
- imageExists = result.trim().length > 0;
173
- } catch {
174
- imageExists = false;
109
+ const runWithDocker = (options: { tag: string; forcePull: boolean }) =>
110
+ Effect.gen(function* () {
111
+ yield* Effect.logInfo("🐳 Airfoil Dev (Docker)");
112
+
113
+ let tag = options.tag;
114
+
115
+ if (tag !== "latest" && !tag.includes("aarch64") && !tag.includes("x86_64")) {
116
+ const arch = process.arch === "arm64" ? "aarch64" : "x86_64";
117
+ const cleanTag = tag.startsWith("v") ? tag.substring(1) : tag;
118
+ tag = `${cleanTag}-${arch}`;
175
119
  }
176
- }
177
-
178
- if (!imageExists || options.forcePull) {
179
- const s = p.spinner();
180
- s.start(
181
- options.forcePull
182
- ? "Pulling latest Docker image..."
183
- : "Downloading Docker image...",
184
- );
185
-
186
- try {
187
- await Bun.$`docker pull ${image}`.quiet();
188
- s.stop("Docker image ready");
189
- } catch {
190
- s.stop("Failed to pull image");
191
- p.cancel(
192
- "Could not download Docker image. Please check your internet connection and registry access.",
193
- );
194
- process.exit(1);
120
+
121
+ const image = `docker.useairfoil.com/airfoil/wings:${tag}`;
122
+
123
+ yield* Effect.logInfo(`Using Docker image: ${image}`);
124
+ yield* Effect.logInfo("Ports: 7777 (gRPC)");
125
+
126
+ yield* checkDockerVersion();
127
+
128
+ if (options.forcePull) {
129
+ yield* Effect.logInfo(`Pulling Docker image: ${image}`);
130
+ yield* pullDockerImage(image);
195
131
  }
196
- } else {
197
- p.log.success("Using cached Docker image");
198
- }
199
-
200
- try {
201
- await Bun.$`docker volume create wings-data`.quiet();
202
- } catch {
203
- // volume may already exist, that's fine
204
- }
205
-
206
- p.log.info("Starting Wings dev server...");
207
-
208
- const proc = Bun.spawn(
209
- [
210
- "docker",
211
- "run",
212
- "-it",
213
- "--rm",
214
- "--name",
215
- "wings-dev",
216
- "-v",
217
- "wings-data:/tmp",
218
- "-p",
219
- "7777:7777",
220
- "-p",
221
- "7780:7780",
222
- image,
223
- "dev",
224
- "--http.address=0.0.0.0:7780",
225
- "--metadata.address=0.0.0.0:7777",
226
- ],
227
- {
228
- stdout: "inherit",
229
- stderr: "inherit",
230
- stdin: "inherit",
231
- },
232
- );
233
132
 
234
- await proc.exited;
235
- }
133
+ yield* createDockerVolume("wings-data");
134
+
135
+ yield* runDockerContainer(image, "wings-data");
136
+ });
@@ -1,12 +1,10 @@
1
- import { readFileSync } from "node:fs";
2
- import * as p from "@clack/prompts";
3
1
  import { ArrowFlightSqlClient } from "@useairfoil/flight";
4
2
  import { printTable } from "console-table-printer";
5
- import { Effect, Option } from "effect";
3
+ import { Effect, FileSystem, Option, Stream } from "effect";
6
4
  import { Argument, Command, Flag } from "effect/unstable/cli";
7
5
  import { Metadata } from "nice-grpc-common";
8
- import { handleCliError } from "../utils/effect.js";
9
- import { hostOption, portOption } from "../utils/options.js";
6
+
7
+ import { hostOption, portOption } from "../utils/options";
10
8
 
11
9
  const queryArg = Argument.string("query").pipe(
12
10
  Argument.withDescription("SQL query to execute"),
@@ -22,7 +20,7 @@ const fileOption = Flag.string("file").pipe(
22
20
  const namespaceOption = Flag.string("namespace").pipe(
23
21
  Flag.withAlias("n"),
24
22
  Flag.withDescription("Wings namespace"),
25
- Flag.withDefault("tenants/default/namespaces/default"),
23
+ Flag.withDefault("namespaces/default"),
26
24
  );
27
25
 
28
26
  const jsonOption = Flag.boolean("json").pipe(
@@ -42,80 +40,51 @@ export const sqlCommand = Command.make(
42
40
  },
43
41
  ({ query, host, port, file, namespace, json }) =>
44
42
  Effect.gen(function* () {
43
+ const fs = yield* FileSystem.FileSystem;
45
44
  let sqlQuery = Option.getOrUndefined(query);
46
45
  const filePath = Option.getOrUndefined(file);
47
46
 
48
47
  if (filePath) {
49
- sqlQuery = yield* Effect.try({
50
- try: () => readFileSync(filePath, "utf-8"),
51
- catch: (error) =>
52
- new Error(
53
- `Failed to read file: ${error instanceof Error ? error.message : "Unknown error"}`,
54
- ),
55
- });
48
+ sqlQuery = yield* fs.readFileString(filePath);
56
49
  }
57
50
 
58
51
  if (!sqlQuery) {
59
52
  return yield* Effect.fail(
60
- new Error(
61
- "No query provided. Use a query argument or --file option.",
62
- ),
53
+ new Error("No query provided. Use a query argument or --file option."),
63
54
  );
64
55
  }
65
56
 
66
57
  const hostPort = `${host}:${port}`;
67
58
 
68
- p.intro("🔍 Arrow Flight SQL");
69
- p.log.info(`Connecting to: ${hostPort}`);
70
- p.log.info(`Namespace: ${namespace}`);
59
+ yield* Effect.logInfo(`Namespace: ${namespace}`);
71
60
 
72
- const client = new ArrowFlightSqlClient(
73
- {
74
- host: hostPort,
75
- },
76
- {
77
- defaultCallOptions: {
78
- "*": {
79
- metadata: Metadata({
80
- "x-wings-namespace": namespace,
81
- }),
82
- },
61
+ const client = yield* ArrowFlightSqlClient.make({
62
+ host: hostPort,
63
+ defaultCallOptions: {
64
+ "*": {
65
+ metadata: Metadata({
66
+ "x-wings-namespace": namespace,
67
+ }),
83
68
  },
84
69
  },
85
- );
86
-
87
- const s = p.spinner();
88
- s.start("Executing query...");
89
-
90
- const flightInfo = yield* Effect.tryPromise({
91
- try: () => client.executeQuery({ query: sqlQuery }),
92
- catch: (error) =>
93
- error instanceof Error ? error : new Error("Query failed"),
94
- }).pipe(Effect.tapError(() => Effect.sync(() => s.stop("Query failed"))));
70
+ });
95
71
 
96
- const batches = yield* Effect.tryPromise({
97
- try: () => Array.fromAsync(client.executeFlightInfo(flightInfo)),
98
- catch: (error) =>
99
- error instanceof Error ? error : new Error("Query failed"),
100
- }).pipe(Effect.tapError(() => Effect.sync(() => s.stop("Query failed"))));
72
+ const flightInfo = yield* client.executeQuery({ query: sqlQuery });
101
73
 
102
- const data = batches.flatMap((batch) => batch.toArray());
74
+ const batches = yield* client.executeFlightInfo(flightInfo).pipe(Stream.runCollect);
103
75
 
104
- s.stop(`Query executed: ${sqlQuery}`);
76
+ const data = Array.from(batches).flatMap(({ batch }) => batch.toArray());
105
77
 
106
- yield* Effect.sync(() => {
107
- if (json) {
108
- console.log(JSON.stringify(data, null, 2));
109
- } else if (data.length === 0) {
110
- p.log.warn("No results returned");
111
- } else {
112
- printTable(data, {
113
- defaultColumnOptions: {
114
- alignment: "left",
115
- },
116
- });
117
- }
118
- p.outro("✓ Done");
119
- });
120
- }).pipe(Effect.catch(handleCliError("Command failed"))),
78
+ if (json) {
79
+ yield* Effect.log(JSON.stringify(data, null, 2));
80
+ } else if (data.length === 0) {
81
+ yield* Effect.logWarning("No results returned");
82
+ } else {
83
+ printTable(data, {
84
+ defaultColumnOptions: {
85
+ alignment: "left",
86
+ },
87
+ });
88
+ }
89
+ }),
121
90
  ).pipe(Command.withDescription("Execute SQL queries using Arrow Flight SQL"));
package/src/index.ts CHANGED
@@ -1,11 +1,13 @@
1
- #!/usr/bin/env bun
1
+ #!/usr/bin/env node
2
2
  import { NodeRuntime, NodeServices } from "@effect/platform-node";
3
- import { Effect } from "effect";
3
+ import { Effect, Layer } from "effect";
4
4
  import { Command } from "effect/unstable/cli";
5
+ import { FetchHttpClient } from "effect/unstable/http";
6
+
5
7
  import packageJson from "../package.json";
6
- import { clusterCommand } from "./commands/cluster/index.js";
7
- import { devCommand } from "./commands/dev.js";
8
- import { sqlCommand } from "./commands/sql.js";
8
+ import { clusterCommand } from "./commands/cluster/index";
9
+ import { devCommand } from "./commands/dev";
10
+ import { sqlCommand } from "./commands/sql";
9
11
 
10
12
  const version = packageJson.version;
11
13
 
@@ -17,4 +19,9 @@ const cli = Command.run(program, {
17
19
  version,
18
20
  });
19
21
 
20
- cli.pipe(Effect.provide(NodeServices.layer), NodeRuntime.runMain);
22
+ NodeRuntime.runMain(
23
+ cli.pipe(
24
+ Effect.provide(Layer.mergeAll(FetchHttpClient.layer, NodeServices.layer)),
25
+ Effect.scoped,
26
+ ),
27
+ );
@@ -1,6 +1,6 @@
1
- import { WingsClusterMetadata } from "@useairfoil/wings";
1
+ import { ClusterClient } from "@useairfoil/wings";
2
2
 
3
- export const makeClusterMetadataLayer = (host: string, port: number) =>
4
- WingsClusterMetadata.layer({
3
+ export const makeClusterClientLayer = (host: string, port: number) =>
4
+ ClusterClient.layer({
5
5
  host: `${host}:${port}`,
6
6
  });
@@ -0,0 +1,137 @@
1
+ import { Effect, String } from "effect";
2
+ import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process";
3
+
4
+ export const checkDockerVersion = () =>
5
+ Effect.gen(function* () {
6
+ const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
7
+ const docker = ChildProcess.make("docker", ["--version"]);
8
+
9
+ return yield* spawner.string(docker).pipe(Effect.map(String.trim));
10
+ });
11
+
12
+ export const createDockerVolume = (volumeName: string) =>
13
+ Effect.gen(function* () {
14
+ const docker = yield* ChildProcess.make("docker", ["volume", "create", volumeName]);
15
+ const exitCode = yield* docker.exitCode;
16
+ if (exitCode !== 0) {
17
+ return yield* Effect.fail(new Error(`Docker volume create failed with code ${exitCode}`));
18
+ }
19
+ });
20
+
21
+ export const createDockerNetwork = (networkName: string) =>
22
+ Effect.gen(function* () {
23
+ const docker = yield* ChildProcess.make("docker", ["network", "create", networkName]);
24
+ const exitCode = yield* docker.exitCode;
25
+ if (exitCode !== 0) {
26
+ return yield* Effect.fail(new Error(`Docker network create failed with code ${exitCode}`));
27
+ }
28
+ });
29
+
30
+ export const removeDockerContainer = (containerName: string) =>
31
+ Effect.gen(function* () {
32
+ const docker = yield* ChildProcess.make("docker", ["rm", "-f", containerName], {
33
+ stdout: "ignore",
34
+ stderr: "ignore",
35
+ });
36
+ yield* docker.exitCode;
37
+ });
38
+
39
+ export const removeDockerNetwork = (networkName: string) =>
40
+ Effect.gen(function* () {
41
+ const docker = yield* ChildProcess.make("docker", ["network", "rm", networkName], {
42
+ stdout: "ignore",
43
+ stderr: "ignore",
44
+ });
45
+ yield* docker.exitCode;
46
+ });
47
+
48
+ export const pullDockerImage = (image: string) =>
49
+ Effect.gen(function* () {
50
+ const docker = yield* ChildProcess.make("docker", ["pull", image], {
51
+ stdout: "inherit",
52
+ stderr: "inherit",
53
+ });
54
+ const exitCode = yield* docker.exitCode;
55
+ if (exitCode !== 0) {
56
+ return yield* Effect.fail(new Error(`Docker pull failed with code ${exitCode}`));
57
+ }
58
+ });
59
+
60
+ export const runDockerContainer = (image: string, volumeName: string) =>
61
+ Effect.gen(function* () {
62
+ const networkName = "airfoil-dev";
63
+ const seaweedContainerName = "airfoil-seaweedfs";
64
+
65
+ yield* removeDockerContainer(seaweedContainerName);
66
+ yield* removeDockerNetwork(networkName);
67
+ yield* createDockerNetwork(networkName);
68
+ yield* Effect.addFinalizer(() =>
69
+ removeDockerNetwork(networkName).pipe(Effect.catchCause(() => Effect.void)),
70
+ );
71
+
72
+ const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
73
+ const seaweed = ChildProcess.make("docker", [
74
+ "run",
75
+ "-d",
76
+ "--rm",
77
+ "--name",
78
+ seaweedContainerName,
79
+ "--network",
80
+ networkName,
81
+ "--network-alias",
82
+ "seaweedfs",
83
+ "-e",
84
+ "AWS_ACCESS_KEY_ID=wingsdevaccesskey",
85
+ "-e",
86
+ "AWS_SECRET_ACCESS_KEY=wingsdevsecretkey",
87
+ "-e",
88
+ "S3_BUCKET=default-bucket",
89
+ "-v",
90
+ `${volumeName}:/data`,
91
+ "-p",
92
+ "8333:8333",
93
+ "chrislusf/seaweedfs:4.29",
94
+ "weed",
95
+ "mini",
96
+ "-s3",
97
+ "-dir=/data",
98
+ ]);
99
+
100
+ yield* spawner.string(seaweed).pipe(Effect.map(String.trim));
101
+ yield* Effect.addFinalizer(() =>
102
+ removeDockerContainer(seaweedContainerName).pipe(Effect.catchCause(() => Effect.void)),
103
+ );
104
+
105
+ const docker = yield* ChildProcess.make("docker", [
106
+ "run",
107
+ "-it",
108
+ "--rm",
109
+ "--name",
110
+ "wings-dev",
111
+ "--network",
112
+ networkName,
113
+ "-e",
114
+ "AWS_ACCESS_KEY_ID=wingsdevaccesskey",
115
+ "-e",
116
+ "AWS_SECRET_ACCESS_KEY=wingsdevsecretkey",
117
+ "-e",
118
+ "AWS_ENDPOINT=http://seaweedfs:8333",
119
+ "-e",
120
+ "AWS_BUCKET_NAME=default-bucket",
121
+ "-e",
122
+ "AWS_DEFAULT_REGION=us-east-1",
123
+ "-e",
124
+ "AWS_ALLOW_HTTP=true",
125
+ "-p",
126
+ "7777:7777",
127
+ image,
128
+ "dev",
129
+ "--grpc.address=0.0.0.0:7777",
130
+ ]);
131
+
132
+ const exitCode = yield* docker.exitCode;
133
+
134
+ if (exitCode !== 0) {
135
+ return yield* Effect.fail(new Error(`Docker process exited with code ${exitCode}`));
136
+ }
137
+ });
@@ -5,7 +5,7 @@ import { Flag } from "effect/unstable/cli";
5
5
  */
6
6
  export const hostOption = Flag.string("host").pipe(
7
7
  Flag.withDescription("Server host"),
8
- Flag.withDefault("localhost"),
8
+ Flag.withDefault("127.0.0.1"),
9
9
  );
10
10
 
11
11
  export const portOption = Flag.integer("port").pipe(
@@ -23,9 +23,7 @@ export const pageSizeOption = Flag.integer("page-size").pipe(
23
23
  );
24
24
 
25
25
  export const pageTokenOption = Flag.optional(
26
- Flag.string("page-token").pipe(
27
- Flag.withDescription("Continuation token for pagination"),
28
- ),
26
+ Flag.string("page-token").pipe(Flag.withDescription("Continuation token for pagination")),
29
27
  );
30
28
 
31
29
  /**