airfoil 0.2.0 → 0.4.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/dist/index.js +1181 -104238
- package/package.json +19 -24
- package/src/commands/cluster/data-lake/create-iceberg.ts +8 -10
- package/src/commands/cluster/data-lake/create-parquet.ts +8 -10
- package/src/commands/cluster/data-lake/create.ts +4 -10
- package/src/commands/cluster/data-lake/delete.ts +6 -10
- package/src/commands/cluster/data-lake/get.ts +7 -13
- package/src/commands/cluster/data-lake/list.ts +8 -18
- package/src/commands/cluster/index.ts +3 -6
- package/src/commands/cluster/namespace/create.ts +18 -28
- package/src/commands/cluster/namespace/delete.ts +6 -10
- package/src/commands/cluster/namespace/get.ts +6 -10
- package/src/commands/cluster/namespace/list.ts +7 -16
- package/src/commands/cluster/object-store/create-aws.ts +20 -24
- package/src/commands/cluster/object-store/create-azure.ts +17 -21
- package/src/commands/cluster/object-store/create-google.ts +19 -34
- package/src/commands/cluster/object-store/create-s3.ts +22 -34
- package/src/commands/cluster/object-store/create.ts +2 -1
- package/src/commands/cluster/object-store/delete.ts +6 -8
- package/src/commands/cluster/object-store/get.ts +6 -8
- package/src/commands/cluster/object-store/list.ts +8 -18
- package/src/commands/cluster/tenant/create.ts +6 -10
- package/src/commands/cluster/tenant/delete.ts +10 -16
- package/src/commands/cluster/tenant/get.ts +6 -10
- package/src/commands/cluster/tenant/list.ts +5 -12
- package/src/commands/cluster/topic/create.ts +43 -66
- package/src/commands/cluster/topic/delete.ts +9 -11
- package/src/commands/cluster/topic/get.ts +5 -5
- package/src/commands/cluster/topic/list.ts +7 -16
- package/src/commands/dev.ts +89 -192
- package/src/commands/sql.ts +38 -59
- package/src/index.ts +18 -9
- package/src/utils/docker.ts +43 -0
- package/src/utils/options.ts +16 -17
- package/src/utils/wings.ts +68 -64
- package/src/scripts/compile-all.ts +0 -21
- package/src/utils/effect.ts +0 -11
package/src/commands/sql.ts
CHANGED
|
@@ -1,33 +1,31 @@
|
|
|
1
|
-
import { readFileSync } from "node:fs";
|
|
2
|
-
import * as p from "@clack/prompts";
|
|
3
|
-
import { Args, Command, Options } from "@effect/cli";
|
|
4
1
|
import { ArrowFlightSqlClient } from "@useairfoil/flight";
|
|
5
2
|
import { printTable } from "console-table-printer";
|
|
6
|
-
import { Effect, Option } from "effect";
|
|
3
|
+
import { Effect, FileSystem, Option } from "effect";
|
|
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";
|
|
10
6
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
7
|
+
import { hostOption, portOption } from "../utils/options";
|
|
8
|
+
|
|
9
|
+
const queryArg = Argument.string("query").pipe(
|
|
10
|
+
Argument.withDescription("SQL query to execute"),
|
|
11
|
+
Argument.optional,
|
|
14
12
|
);
|
|
15
13
|
|
|
16
|
-
const fileOption =
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
14
|
+
const fileOption = Flag.string("file").pipe(
|
|
15
|
+
Flag.withAlias("f"),
|
|
16
|
+
Flag.withDescription("Execute SQL from file"),
|
|
17
|
+
Flag.optional,
|
|
20
18
|
);
|
|
21
19
|
|
|
22
|
-
const namespaceOption =
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
20
|
+
const namespaceOption = Flag.string("namespace").pipe(
|
|
21
|
+
Flag.withAlias("n"),
|
|
22
|
+
Flag.withDescription("Wings namespace"),
|
|
23
|
+
Flag.withDefault("tenants/default/namespaces/default"),
|
|
26
24
|
);
|
|
27
25
|
|
|
28
|
-
const jsonOption =
|
|
29
|
-
|
|
30
|
-
|
|
26
|
+
const jsonOption = Flag.boolean("json").pipe(
|
|
27
|
+
Flag.withDescription("Output results as JSON"),
|
|
28
|
+
Flag.withDefault(false),
|
|
31
29
|
);
|
|
32
30
|
|
|
33
31
|
export const sqlCommand = Command.make(
|
|
@@ -42,32 +40,23 @@ 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*
|
|
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
|
-
|
|
69
|
-
p.log.info(`Connecting to: ${hostPort}`);
|
|
70
|
-
p.log.info(`Namespace: ${namespace}`);
|
|
59
|
+
yield* Effect.logInfo(`Namespace: ${namespace}`);
|
|
71
60
|
|
|
72
61
|
const client = new ArrowFlightSqlClient(
|
|
73
62
|
{
|
|
@@ -84,38 +73,28 @@ export const sqlCommand = Command.make(
|
|
|
84
73
|
},
|
|
85
74
|
);
|
|
86
75
|
|
|
87
|
-
const s = p.spinner();
|
|
88
|
-
s.start("Executing query...");
|
|
89
|
-
|
|
90
76
|
const flightInfo = yield* Effect.tryPromise({
|
|
91
77
|
try: () => client.executeQuery({ query: sqlQuery }),
|
|
92
|
-
catch: (error) =>
|
|
93
|
-
|
|
94
|
-
}).pipe(Effect.tapError(() => Effect.sync(() => s.stop("Query failed"))));
|
|
78
|
+
catch: (error) => (error instanceof Error ? error : new Error("Query failed")),
|
|
79
|
+
});
|
|
95
80
|
|
|
96
81
|
const batches = yield* Effect.tryPromise({
|
|
97
82
|
try: () => Array.fromAsync(client.executeFlightInfo(flightInfo)),
|
|
98
|
-
catch: (error) =>
|
|
99
|
-
|
|
100
|
-
}).pipe(Effect.tapError(() => Effect.sync(() => s.stop("Query failed"))));
|
|
83
|
+
catch: (error) => (error instanceof Error ? error : new Error("Query failed")),
|
|
84
|
+
});
|
|
101
85
|
|
|
102
86
|
const data = batches.flatMap((batch) => batch.toArray());
|
|
103
87
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
});
|
|
117
|
-
}
|
|
118
|
-
p.outro("✓ Done");
|
|
119
|
-
});
|
|
120
|
-
}).pipe(Effect.catchAll(handleCliError("Command failed"))),
|
|
88
|
+
if (json) {
|
|
89
|
+
yield* Effect.log(JSON.stringify(data, null, 2));
|
|
90
|
+
} else if (data.length === 0) {
|
|
91
|
+
yield* Effect.logWarning("No results returned");
|
|
92
|
+
} else {
|
|
93
|
+
printTable(data, {
|
|
94
|
+
defaultColumnOptions: {
|
|
95
|
+
alignment: "left",
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
}),
|
|
121
100
|
).pipe(Command.withDescription("Execute SQL queries using Arrow Flight SQL"));
|
package/src/index.ts
CHANGED
|
@@ -1,18 +1,27 @@
|
|
|
1
|
-
#!/usr/bin/env
|
|
2
|
-
import {
|
|
3
|
-
import { NodeContext, NodeRuntime } from "@effect/platform-node";
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { NodeRuntime, NodeServices } from "@effect/platform-node";
|
|
4
3
|
import { Effect } from "effect";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
|
|
4
|
+
import { Command } from "effect/unstable/cli";
|
|
5
|
+
import { FetchHttpClient } from "effect/unstable/http";
|
|
6
|
+
|
|
7
|
+
import packageJson from "../package.json";
|
|
8
|
+
import { clusterCommand } from "./commands/cluster/index";
|
|
9
|
+
import { devCommand } from "./commands/dev";
|
|
10
|
+
import { sqlCommand } from "./commands/sql";
|
|
11
|
+
|
|
12
|
+
const version = packageJson.version;
|
|
8
13
|
|
|
9
14
|
const program = Command.make("airfoil", {}, () => Effect.void).pipe(
|
|
10
15
|
Command.withSubcommands([devCommand, sqlCommand, clusterCommand]),
|
|
11
16
|
);
|
|
12
17
|
|
|
13
18
|
const cli = Command.run(program, {
|
|
14
|
-
|
|
15
|
-
version: "0.1.0",
|
|
19
|
+
version,
|
|
16
20
|
});
|
|
17
21
|
|
|
18
|
-
cli
|
|
22
|
+
cli.pipe(
|
|
23
|
+
Effect.provide(FetchHttpClient.layer),
|
|
24
|
+
Effect.provide(NodeServices.layer),
|
|
25
|
+
Effect.scoped,
|
|
26
|
+
NodeRuntime.runMain,
|
|
27
|
+
);
|
|
@@ -0,0 +1,43 @@
|
|
|
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
|
+
yield* docker.exitCode;
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
export const runDockerContainer = (image: string, volumeName: string) =>
|
|
19
|
+
Effect.gen(function* () {
|
|
20
|
+
const docker = yield* ChildProcess.make("docker", [
|
|
21
|
+
"run",
|
|
22
|
+
"-it",
|
|
23
|
+
"--rm",
|
|
24
|
+
"--name",
|
|
25
|
+
"wings-dev",
|
|
26
|
+
"-v",
|
|
27
|
+
`${volumeName}:/tmp`,
|
|
28
|
+
"-p",
|
|
29
|
+
"7777:7777",
|
|
30
|
+
"-p",
|
|
31
|
+
"7780:7780",
|
|
32
|
+
image,
|
|
33
|
+
"dev",
|
|
34
|
+
"--http.address=0.0.0.0:7780",
|
|
35
|
+
"--metadata.address=0.0.0.0:7777",
|
|
36
|
+
]);
|
|
37
|
+
|
|
38
|
+
const exitCode = yield* docker.exitCode;
|
|
39
|
+
|
|
40
|
+
if (exitCode !== 0) {
|
|
41
|
+
yield* Effect.fail(new Error(`Docker process exited with code ${exitCode}`));
|
|
42
|
+
}
|
|
43
|
+
});
|
package/src/utils/options.ts
CHANGED
|
@@ -1,36 +1,35 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Flag } from "effect/unstable/cli";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Common server connection options
|
|
5
5
|
*/
|
|
6
|
-
export const hostOption =
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
export const hostOption = Flag.string("host").pipe(
|
|
7
|
+
Flag.withDescription("Server host"),
|
|
8
|
+
Flag.withDefault("localhost"),
|
|
9
9
|
);
|
|
10
10
|
|
|
11
|
-
export const portOption =
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
export const portOption = Flag.integer("port").pipe(
|
|
12
|
+
Flag.withDescription("Server port"),
|
|
13
|
+
Flag.withAlias("p"),
|
|
14
|
+
Flag.withDefault(7777),
|
|
15
15
|
);
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
18
|
* Common pagination options
|
|
19
19
|
*/
|
|
20
|
-
export const pageSizeOption =
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
export const pageSizeOption = Flag.integer("page-size").pipe(
|
|
21
|
+
Flag.withDescription("Number of items to return (max: 1000)"),
|
|
22
|
+
Flag.withDefault(100),
|
|
23
23
|
);
|
|
24
24
|
|
|
25
|
-
export const pageTokenOption =
|
|
26
|
-
|
|
27
|
-
Options.optional,
|
|
25
|
+
export const pageTokenOption = Flag.optional(
|
|
26
|
+
Flag.string("page-token").pipe(Flag.withDescription("Continuation token for pagination")),
|
|
28
27
|
);
|
|
29
28
|
|
|
30
29
|
/**
|
|
31
30
|
* Force flag for destructive operations
|
|
32
31
|
*/
|
|
33
|
-
export const forceOption =
|
|
34
|
-
|
|
35
|
-
|
|
32
|
+
export const forceOption = Flag.boolean("force").pipe(
|
|
33
|
+
Flag.withDescription("Skip confirmation prompt"),
|
|
34
|
+
Flag.withDefault(false),
|
|
36
35
|
);
|
package/src/utils/wings.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import { Effect, FileSystem, Path } from "effect";
|
|
2
|
+
import { HttpClient, HttpClientResponse } from "effect/unstable/http";
|
|
1
3
|
import { createHash } from "node:crypto";
|
|
2
4
|
import { homedir } from "node:os";
|
|
3
5
|
import { join } from "node:path";
|
|
4
6
|
|
|
5
|
-
const WINGS_DIR = join(homedir(), ".
|
|
7
|
+
const WINGS_DIR = join(homedir(), ".local", "share", "wings");
|
|
6
8
|
const GITHUB_RELEASES_URL = "https://github.com/useairfoil/wings/releases";
|
|
7
9
|
|
|
8
10
|
/**
|
|
@@ -38,79 +40,81 @@ function getPlatformString(): string {
|
|
|
38
40
|
*/
|
|
39
41
|
function getBinaryFilename(isStress: boolean): string {
|
|
40
42
|
const platformString = getPlatformString();
|
|
41
|
-
return isStress
|
|
42
|
-
? `wings-stress-${platformString}`
|
|
43
|
-
: `wings-${platformString}`;
|
|
43
|
+
return isStress ? `wings-stress-${platformString}` : `wings-${platformString}`;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
export
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
46
|
+
export const getWingsPath = (version: string, isStress = false) =>
|
|
47
|
+
Effect.gen(function* () {
|
|
48
|
+
const binaryFilename = getBinaryFilename(isStress);
|
|
49
|
+
const path = yield* Path.Path;
|
|
50
|
+
return path.join(WINGS_DIR, `${binaryFilename}-${version}`);
|
|
51
|
+
});
|
|
50
52
|
|
|
51
|
-
export
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
): Promise<void> {
|
|
56
|
-
const filename = getBinaryFilename(isStress);
|
|
53
|
+
export const downloadWings = (version: string, targetPath: string, isStress = false) =>
|
|
54
|
+
Effect.gen(function* () {
|
|
55
|
+
const fs = yield* FileSystem.FileSystem;
|
|
56
|
+
const client = yield* HttpClient.HttpClient;
|
|
57
57
|
|
|
58
|
-
|
|
59
|
-
version === "latest" ? "latest/download" : `download/${version}`;
|
|
60
|
-
const url = `${GITHUB_RELEASES_URL}/${downloadPath}/${filename}`;
|
|
58
|
+
const filename = getBinaryFilename(isStress);
|
|
61
59
|
|
|
62
|
-
|
|
60
|
+
const downloadPath = version === "latest" ? "latest/download" : `download/${version}`;
|
|
61
|
+
const url = `${GITHUB_RELEASES_URL}/${downloadPath}/${filename}`;
|
|
63
62
|
|
|
64
|
-
|
|
63
|
+
yield* fs.makeDirectory(WINGS_DIR, { recursive: true });
|
|
65
64
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
65
|
+
const response = yield* client.get(url);
|
|
66
|
+
const buffer = yield* HttpClientResponse.matchStatus({
|
|
67
|
+
200: (response) => response.arrayBuffer,
|
|
68
|
+
orElse: (response) => {
|
|
69
|
+
throw new Error(`Failed to download Wings from ${url}: ${response.status}`);
|
|
70
|
+
},
|
|
71
|
+
})(response);
|
|
71
72
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
73
|
+
yield* fs.writeFile(targetPath, new Uint8Array(buffer));
|
|
74
|
+
|
|
75
|
+
yield* verifyChecksum(version, targetPath, isStress);
|
|
76
|
+
|
|
77
|
+
yield* fs.chmod(targetPath, 0o755);
|
|
78
|
+
});
|
|
75
79
|
|
|
76
80
|
/**
|
|
77
81
|
* Download and verify the checksum of a Wings binary
|
|
78
82
|
* The hash file format is: "<hash> <path>/wings"
|
|
79
83
|
*/
|
|
80
|
-
export
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
}
|
|
84
|
+
export const verifyChecksum = (version: string, filePath: string, isStress = false) =>
|
|
85
|
+
Effect.gen(function* () {
|
|
86
|
+
const fs = yield* FileSystem.FileSystem;
|
|
87
|
+
const client = yield* HttpClient.HttpClient;
|
|
88
|
+
|
|
89
|
+
const filename = getBinaryFilename(isStress);
|
|
90
|
+
const hashFilename = `${filename}-hash.txt`;
|
|
91
|
+
|
|
92
|
+
const downloadPath = version === "latest" ? "latest/download" : `download/${version}`;
|
|
93
|
+
const url = `${GITHUB_RELEASES_URL}/${downloadPath}/${hashFilename}`;
|
|
94
|
+
|
|
95
|
+
const response = yield* client.get(url);
|
|
96
|
+
const hashContent = yield* HttpClientResponse.matchStatus({
|
|
97
|
+
200: (response) => response.text,
|
|
98
|
+
orElse: (response) => {
|
|
99
|
+
return Effect.fail(
|
|
100
|
+
new Error(`Failed to download hash file from ${url}: ${response.status}`),
|
|
101
|
+
);
|
|
102
|
+
},
|
|
103
|
+
})(response);
|
|
104
|
+
|
|
105
|
+
const expectedChecksum = hashContent.trim().split(/\s+/)[0];
|
|
106
|
+
|
|
107
|
+
if (!expectedChecksum) {
|
|
108
|
+
return yield* Effect.fail(new Error("Invalid hash file format"));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const fileBuffer = yield* fs.readFile(filePath);
|
|
112
|
+
|
|
113
|
+
const hash = createHash("sha256");
|
|
114
|
+
hash.update(fileBuffer);
|
|
115
|
+
const actualChecksum = hash.digest("hex");
|
|
116
|
+
|
|
117
|
+
if (actualChecksum !== expectedChecksum) {
|
|
118
|
+
return yield* Effect.fail(new Error("Wings binary checksum mismatch"));
|
|
119
|
+
}
|
|
120
|
+
});
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { $ } from "bun";
|
|
2
|
-
|
|
3
|
-
const targets = [
|
|
4
|
-
{ platform: "linux", arch: "x64" },
|
|
5
|
-
{ platform: "linux", arch: "arm64" },
|
|
6
|
-
{ platform: "darwin", arch: "x64" },
|
|
7
|
-
{ platform: "darwin", arch: "arm64" },
|
|
8
|
-
{ platform: "windows", arch: "x64" },
|
|
9
|
-
];
|
|
10
|
-
|
|
11
|
-
for (const { platform, arch } of targets) {
|
|
12
|
-
const outfile = `dist/airfoil-${platform}-${arch}${platform === "windows" ? ".exe" : ""}`;
|
|
13
|
-
|
|
14
|
-
console.log(`Compiling for ${platform}-${arch}...`);
|
|
15
|
-
|
|
16
|
-
await $`bun build src/index.ts --compile --target=bun-${platform}-${arch} --outfile ${outfile}`;
|
|
17
|
-
|
|
18
|
-
console.log(`✓ Created ${outfile}`);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
console.log("\n✓ All binaries compiled successfully!");
|
package/src/utils/effect.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import * as p from "@clack/prompts";
|
|
2
|
-
import { Effect } from "effect";
|
|
3
|
-
|
|
4
|
-
export const handleCliError = (fallbackMessage: string) => (error: unknown) =>
|
|
5
|
-
Effect.sync(() => {
|
|
6
|
-
p.cancel(error instanceof Error ? error.message : fallbackMessage);
|
|
7
|
-
}).pipe(
|
|
8
|
-
Effect.andThen(
|
|
9
|
-
Effect.fail(error instanceof Error ? error : new Error(fallbackMessage)),
|
|
10
|
-
),
|
|
11
|
-
);
|