gigadrive 2.7.5 → 2.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -0
- package/dist/index.cjs +1130 -237
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +1117 -220
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -4
package/dist/index.mjs
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import { Command as
|
|
4
|
+
import { Command as Command17 } from "@effect/cli";
|
|
5
5
|
import { NodeContext, NodeRuntime } from "@effect/platform-node";
|
|
6
6
|
import { NetworkConfigLive as NetworkConfigLive2 } from "@gigadrive/network-config";
|
|
7
|
-
import { Effect as
|
|
7
|
+
import { Effect as Effect29, Layer, LogLevel, Logger } from "effect";
|
|
8
8
|
|
|
9
9
|
// package.json
|
|
10
10
|
var package_default = {
|
|
11
11
|
name: "gigadrive",
|
|
12
|
-
version: "2.
|
|
12
|
+
version: "2.8.0",
|
|
13
13
|
repository: {
|
|
14
14
|
type: "git",
|
|
15
15
|
url: "https://github.com/Gigadrive/sdk.git",
|
|
@@ -332,6 +332,24 @@ var DeploymentFailedError = class extends Schema.TaggedError()("DeploymentFailed
|
|
|
332
332
|
message: Schema.String
|
|
333
333
|
}) {
|
|
334
334
|
};
|
|
335
|
+
var InvalidMicroVmOptionsError = class extends Schema.TaggedError()(
|
|
336
|
+
"InvalidMicroVmOptionsError",
|
|
337
|
+
{ message: Schema.String }
|
|
338
|
+
) {
|
|
339
|
+
};
|
|
340
|
+
var InvalidCopyTargetError = class extends Schema.TaggedError()("InvalidCopyTargetError", {
|
|
341
|
+
message: Schema.String
|
|
342
|
+
}) {
|
|
343
|
+
};
|
|
344
|
+
var LocalFileError = class extends Schema.TaggedError()("LocalFileError", {
|
|
345
|
+
message: Schema.String,
|
|
346
|
+
path: Schema.String
|
|
347
|
+
}) {
|
|
348
|
+
};
|
|
349
|
+
var TerminalRequiredError = class extends Schema.TaggedError()("TerminalRequiredError", {
|
|
350
|
+
message: Schema.String
|
|
351
|
+
}) {
|
|
352
|
+
};
|
|
335
353
|
|
|
336
354
|
// src/services/api-client.ts
|
|
337
355
|
import { ApiError, GigadriveClient } from "@gigadrive/sdk";
|
|
@@ -536,6 +554,13 @@ var CLI_SCOPES = [
|
|
|
536
554
|
"network:deployments:trigger",
|
|
537
555
|
"network:ai_gateway:chat",
|
|
538
556
|
"network:ai_gateway:models",
|
|
557
|
+
"network:microvms:read",
|
|
558
|
+
"network:microvms:write",
|
|
559
|
+
"network:microvms:exec",
|
|
560
|
+
"network:microvms:delete",
|
|
561
|
+
"network:microvm_images:read",
|
|
562
|
+
"network:microvm_images:write",
|
|
563
|
+
"network:microvm_images:delete",
|
|
539
564
|
// Provisioning local-dev API credentials
|
|
540
565
|
"platform:api_keys:read",
|
|
541
566
|
"platform:api_keys:write",
|
|
@@ -2939,23 +2964,893 @@ var logoutCommand = Command11.make(
|
|
|
2939
2964
|
)
|
|
2940
2965
|
);
|
|
2941
2966
|
|
|
2967
|
+
// src/commands/microvms/index.ts
|
|
2968
|
+
import { Args as Args5, Command as Command12, Options as Options7 } from "@effect/cli";
|
|
2969
|
+
import { FileSystem as FileSystem8, Path as Path7 } from "@effect/platform";
|
|
2970
|
+
import {
|
|
2971
|
+
durationToMs,
|
|
2972
|
+
MicroVM,
|
|
2973
|
+
MICROVM_MAX_TIMEOUT_MS
|
|
2974
|
+
} from "@gigadrive/sdk";
|
|
2975
|
+
import { Config as Config4, Console as Console12, Effect as Effect22, Exit, Option as Option14 } from "effect";
|
|
2976
|
+
|
|
2977
|
+
// src/lib/microvm.ts
|
|
2978
|
+
var REMOTE_PREFIX = "vm:";
|
|
2979
|
+
var parseCopyTarget = (value) => {
|
|
2980
|
+
if (!value.startsWith(REMOTE_PREFIX)) {
|
|
2981
|
+
return value.length === 0 ? "The path is empty." : { kind: "local", path: value };
|
|
2982
|
+
}
|
|
2983
|
+
const rest = value.slice(REMOTE_PREFIX.length);
|
|
2984
|
+
const separator = rest.indexOf(":");
|
|
2985
|
+
if (separator <= 0) return `"${value}" is not in the form vm:<id>:<path>.`;
|
|
2986
|
+
const microvmId = rest.slice(0, separator);
|
|
2987
|
+
const path2 = rest.slice(separator + 1);
|
|
2988
|
+
return { kind: "remote", microvmId, path: path2.length === 0 ? "." : path2 };
|
|
2989
|
+
};
|
|
2990
|
+
var posixBasename = (path2) => {
|
|
2991
|
+
const trimmed = path2.replace(/\/+$/, "");
|
|
2992
|
+
const index = trimmed.lastIndexOf("/");
|
|
2993
|
+
return index === -1 ? trimmed : trimmed.slice(index + 1);
|
|
2994
|
+
};
|
|
2995
|
+
var posixJoin = (...segments) => segments.filter((segment) => segment.length > 0).join("/").replace(/\/{2,}/g, "/");
|
|
2996
|
+
var relativeTo = (root, entry) => {
|
|
2997
|
+
const base = root.replace(/\/+$/, "");
|
|
2998
|
+
return entry.startsWith(`${base}/`) ? entry.slice(base.length + 1) : posixBasename(entry);
|
|
2999
|
+
};
|
|
3000
|
+
var parseKeyValues = (entries) => {
|
|
3001
|
+
const value = {};
|
|
3002
|
+
for (const entry of entries) {
|
|
3003
|
+
const index = entry.indexOf("=");
|
|
3004
|
+
if (index <= 0) return { ok: false, entry };
|
|
3005
|
+
value[entry.slice(0, index)] = entry.slice(index + 1);
|
|
3006
|
+
}
|
|
3007
|
+
return { ok: true, value };
|
|
3008
|
+
};
|
|
3009
|
+
var SIGNAL_NUMBERS = {
|
|
3010
|
+
SIGHUP: 1,
|
|
3011
|
+
SIGINT: 2,
|
|
3012
|
+
SIGQUIT: 3,
|
|
3013
|
+
SIGABRT: 6,
|
|
3014
|
+
SIGKILL: 9,
|
|
3015
|
+
SIGUSR1: 10,
|
|
3016
|
+
SIGSEGV: 11,
|
|
3017
|
+
SIGUSR2: 12,
|
|
3018
|
+
SIGPIPE: 13,
|
|
3019
|
+
SIGTERM: 15
|
|
3020
|
+
};
|
|
3021
|
+
var exitStatusOf = (command) => {
|
|
3022
|
+
if (command.exitCode !== null) return command.exitCode;
|
|
3023
|
+
if (command.signal) return 128 + (SIGNAL_NUMBERS[command.signal] ?? 0);
|
|
3024
|
+
return 1;
|
|
3025
|
+
};
|
|
3026
|
+
var formatMemory = (mb) => mb % 1024 === 0 ? `${mb / 1024} GiB` : `${mb} MiB`;
|
|
3027
|
+
var relativeTime = (iso, now = Date.now()) => {
|
|
3028
|
+
if (!iso) return "-";
|
|
3029
|
+
const delta = Date.parse(iso) - now;
|
|
3030
|
+
if (!Number.isFinite(delta)) return iso;
|
|
3031
|
+
const abs = Math.abs(delta);
|
|
3032
|
+
const text = abs < 6e4 ? `${Math.round(abs / 1e3)}s` : abs < 36e5 ? `${Math.round(abs / 6e4)}m` : abs < 864e5 ? `${Math.round(abs / 36e5)}h` : `${Math.round(abs / 864e5)}d`;
|
|
3033
|
+
return delta >= 0 ? `in ${text}` : `${text} ago`;
|
|
3034
|
+
};
|
|
3035
|
+
var formatTable = (rows) => {
|
|
3036
|
+
const widths = [];
|
|
3037
|
+
for (const row of rows) {
|
|
3038
|
+
row.forEach((cell, index) => {
|
|
3039
|
+
widths[index] = Math.max(widths[index] ?? 0, cell.length);
|
|
3040
|
+
});
|
|
3041
|
+
}
|
|
3042
|
+
return rows.map(
|
|
3043
|
+
(row) => row.map((cell, index) => index === row.length - 1 ? cell : cell.padEnd(widths[index] ?? 0)).join(" ").trimEnd()
|
|
3044
|
+
);
|
|
3045
|
+
};
|
|
3046
|
+
var formatMicroVmTable = (vms, now = Date.now()) => formatTable([
|
|
3047
|
+
["ID", "STATUS", "IMAGE", "SIZE", "PORTS", "CREATED", "EXPIRES"],
|
|
3048
|
+
...vms.map((vm) => [
|
|
3049
|
+
vm.id,
|
|
3050
|
+
vm.status,
|
|
3051
|
+
vm.image.name,
|
|
3052
|
+
`${vm.vcpus} vCPU, ${formatMemory(vm.memoryMb)}`,
|
|
3053
|
+
vm.ports.map((entry) => entry.port).join(",") || "-",
|
|
3054
|
+
relativeTime(vm.createdAt, now),
|
|
3055
|
+
vm.status === "running" || vm.status === "starting" ? relativeTime(vm.expiresAt, now) : "-"
|
|
3056
|
+
])
|
|
3057
|
+
]);
|
|
3058
|
+
var describeMicroVm = (vm, now = Date.now()) => {
|
|
3059
|
+
const lines = [
|
|
3060
|
+
`ID: ${vm.id}`,
|
|
3061
|
+
`Status: ${vm.status}${vm.stopReason ? ` (${vm.stopReason})` : ""}`,
|
|
3062
|
+
`Image: ${vm.image.name}`,
|
|
3063
|
+
`Size: ${vm.vcpus} vCPU, ${formatMemory(vm.memoryMb)} memory, ${formatMemory(vm.diskMb)} disk`,
|
|
3064
|
+
`Region: ${vm.region}`,
|
|
3065
|
+
`Network: ${vm.network.egress === "deny" ? "outbound traffic blocked" : "internet access"}`,
|
|
3066
|
+
`Application: ${vm.applicationId}`,
|
|
3067
|
+
`Created: ${vm.createdAt}`
|
|
3068
|
+
];
|
|
3069
|
+
if (vm.expiresAt && (vm.status === "running" || vm.status === "starting")) {
|
|
3070
|
+
lines.push(`Expires: ${vm.expiresAt} (${relativeTime(vm.expiresAt, now)})`);
|
|
3071
|
+
}
|
|
3072
|
+
if (vm.stoppedAt) lines.push(`Stopped: ${vm.stoppedAt}`);
|
|
3073
|
+
const metadata = Object.entries(vm.metadata);
|
|
3074
|
+
if (metadata.length > 0) lines.push(`Metadata: ${metadata.map(([key, value]) => `${key}=${value}`).join(", ")}`);
|
|
3075
|
+
if (vm.ports.length > 0) {
|
|
3076
|
+
lines.push("Ports:");
|
|
3077
|
+
for (const entry of vm.ports) lines.push(` ${entry.port} ${entry.url}`);
|
|
3078
|
+
}
|
|
3079
|
+
return lines;
|
|
3080
|
+
};
|
|
3081
|
+
var imageSource = (image) => {
|
|
3082
|
+
if (image.builtin) return "built-in";
|
|
3083
|
+
const source = image.source;
|
|
3084
|
+
if (!source) return "-";
|
|
3085
|
+
return source.type === "dockerfile" ? "Dockerfile" : source.ref;
|
|
3086
|
+
};
|
|
3087
|
+
var formatImageTable = (images) => formatTable([
|
|
3088
|
+
["NAME", "STATUS", "SOURCE", "ID"],
|
|
3089
|
+
...images.map((image) => [image.name, image.status, imageSource(image), image.id])
|
|
3090
|
+
]);
|
|
3091
|
+
var formatBytes = (bytes) => {
|
|
3092
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
3093
|
+
const units = ["KiB", "MiB", "GiB", "TiB"];
|
|
3094
|
+
let value = bytes / 1024;
|
|
3095
|
+
let unit = 0;
|
|
3096
|
+
while (value >= 1024 && unit < units.length - 1) {
|
|
3097
|
+
value /= 1024;
|
|
3098
|
+
unit += 1;
|
|
3099
|
+
}
|
|
3100
|
+
return `${value.toFixed(1)} ${units[unit]}`;
|
|
3101
|
+
};
|
|
3102
|
+
var concatBytes = (chunks) => {
|
|
3103
|
+
if (chunks.length === 1) return chunks[0];
|
|
3104
|
+
const total = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
|
|
3105
|
+
const result = new Uint8Array(total);
|
|
3106
|
+
let offset = 0;
|
|
3107
|
+
for (const chunk of chunks) {
|
|
3108
|
+
result.set(chunk, offset);
|
|
3109
|
+
offset += chunk.length;
|
|
3110
|
+
}
|
|
3111
|
+
return result;
|
|
3112
|
+
};
|
|
3113
|
+
var StdinBatcher = class {
|
|
3114
|
+
constructor(send, options = {}) {
|
|
3115
|
+
this.send = send;
|
|
3116
|
+
this.options = options;
|
|
3117
|
+
this.delayMs = options.delayMs ?? 8;
|
|
3118
|
+
this.maxBytes = options.maxBytes ?? 64 * 1024;
|
|
3119
|
+
}
|
|
3120
|
+
send;
|
|
3121
|
+
options;
|
|
3122
|
+
pending = [];
|
|
3123
|
+
pendingBytes = 0;
|
|
3124
|
+
timer;
|
|
3125
|
+
inFlight;
|
|
3126
|
+
closed = false;
|
|
3127
|
+
delayMs;
|
|
3128
|
+
maxBytes;
|
|
3129
|
+
/** Queues a chunk. Ignored after {@link close}. */
|
|
3130
|
+
push(chunk) {
|
|
3131
|
+
if (this.closed || chunk.length === 0) return;
|
|
3132
|
+
this.pending.push(chunk);
|
|
3133
|
+
this.pendingBytes += chunk.length;
|
|
3134
|
+
if (this.pendingBytes >= this.maxBytes) this.schedule(0);
|
|
3135
|
+
else if (this.timer === void 0) this.schedule(this.delayMs);
|
|
3136
|
+
}
|
|
3137
|
+
/** Sends everything queued so far, after any request already in flight. */
|
|
3138
|
+
async flush() {
|
|
3139
|
+
while (this.inFlight) await this.inFlight;
|
|
3140
|
+
if (this.pending.length === 0) return;
|
|
3141
|
+
const data = concatBytes(this.pending);
|
|
3142
|
+
this.pending = [];
|
|
3143
|
+
this.pendingBytes = 0;
|
|
3144
|
+
this.inFlight = this.send(data).catch((error) => this.options.onError?.(error)).finally(() => {
|
|
3145
|
+
this.inFlight = void 0;
|
|
3146
|
+
});
|
|
3147
|
+
await this.inFlight;
|
|
3148
|
+
}
|
|
3149
|
+
/** Stops accepting chunks and sends what is left. */
|
|
3150
|
+
async close() {
|
|
3151
|
+
this.closed = true;
|
|
3152
|
+
if (this.timer !== void 0) clearTimeout(this.timer);
|
|
3153
|
+
this.timer = void 0;
|
|
3154
|
+
await this.flush();
|
|
3155
|
+
}
|
|
3156
|
+
schedule(delay) {
|
|
3157
|
+
if (this.timer !== void 0) clearTimeout(this.timer);
|
|
3158
|
+
this.timer = setTimeout(() => {
|
|
3159
|
+
this.timer = void 0;
|
|
3160
|
+
void this.flush();
|
|
3161
|
+
}, delay);
|
|
3162
|
+
}
|
|
3163
|
+
};
|
|
3164
|
+
|
|
3165
|
+
// src/lib/microvm-session.ts
|
|
3166
|
+
async function attachCommand(command, io, options) {
|
|
3167
|
+
const batcher = new StdinBatcher((data) => command.write(data), {
|
|
3168
|
+
// Input after the command exited is expected to fail; nothing to report.
|
|
3169
|
+
onError: () => void 0
|
|
3170
|
+
});
|
|
3171
|
+
const cleanups = [];
|
|
3172
|
+
if (options.forwardInput) {
|
|
3173
|
+
cleanups.push(
|
|
3174
|
+
io.onInput(
|
|
3175
|
+
(chunk) => batcher.push(chunk),
|
|
3176
|
+
() => {
|
|
3177
|
+
if (!options.endInputOnEof) return;
|
|
3178
|
+
void batcher.close().then(() => command.end()).catch(() => void 0);
|
|
3179
|
+
}
|
|
3180
|
+
)
|
|
3181
|
+
);
|
|
3182
|
+
}
|
|
3183
|
+
if (options.forwardResize) {
|
|
3184
|
+
cleanups.push(
|
|
3185
|
+
io.onResize(() => {
|
|
3186
|
+
const size = io.size();
|
|
3187
|
+
if (size) void command.resize(size.cols, size.rows).catch(() => void 0);
|
|
3188
|
+
})
|
|
3189
|
+
);
|
|
3190
|
+
}
|
|
3191
|
+
try {
|
|
3192
|
+
for await (const chunk of command.logs()) io.write(chunk.data, chunk.stream);
|
|
3193
|
+
} finally {
|
|
3194
|
+
for (const cleanup of cleanups) cleanup();
|
|
3195
|
+
await batcher.close();
|
|
3196
|
+
}
|
|
3197
|
+
return command.status === "exited" ? command.info : await command.wait();
|
|
3198
|
+
}
|
|
3199
|
+
var nodeSessionIo = () => ({
|
|
3200
|
+
onInput: (listener, onEnd) => {
|
|
3201
|
+
const encoder = new TextEncoder();
|
|
3202
|
+
const onData = (chunk) => listener(typeof chunk === "string" ? encoder.encode(chunk) : chunk);
|
|
3203
|
+
process.stdin.on("data", onData);
|
|
3204
|
+
process.stdin.on("end", onEnd);
|
|
3205
|
+
process.stdin.resume();
|
|
3206
|
+
return () => {
|
|
3207
|
+
process.stdin.off("data", onData);
|
|
3208
|
+
process.stdin.off("end", onEnd);
|
|
3209
|
+
process.stdin.pause();
|
|
3210
|
+
};
|
|
3211
|
+
},
|
|
3212
|
+
write: (data, stream) => {
|
|
3213
|
+
(stream === "stderr" ? process.stderr : process.stdout).write(data);
|
|
3214
|
+
},
|
|
3215
|
+
size: () => process.stdout.isTTY ? { cols: process.stdout.columns, rows: process.stdout.rows } : void 0,
|
|
3216
|
+
onResize: (listener) => {
|
|
3217
|
+
process.stdout.on("resize", listener);
|
|
3218
|
+
return () => {
|
|
3219
|
+
process.stdout.off("resize", listener);
|
|
3220
|
+
};
|
|
3221
|
+
}
|
|
3222
|
+
});
|
|
3223
|
+
|
|
3224
|
+
// src/commands/microvms/index.ts
|
|
3225
|
+
var CONSOLE_URL2 = "https://console.gigadrive.de";
|
|
3226
|
+
var MICROVM_STATUSES = [
|
|
3227
|
+
"pending",
|
|
3228
|
+
"starting",
|
|
3229
|
+
"running",
|
|
3230
|
+
"stopping",
|
|
3231
|
+
"stopped",
|
|
3232
|
+
"failed"
|
|
3233
|
+
];
|
|
3234
|
+
var DEFAULT_SHELL = ["/bin/sh", "-c", "if command -v bash >/dev/null 2>&1; then exec bash -l; else exec sh -l; fi"];
|
|
3235
|
+
var appOption6 = Options7.text("app").pipe(
|
|
3236
|
+
Options7.withAlias("a"),
|
|
3237
|
+
Options7.withDescription("Application ID (defaults to the linked application)"),
|
|
3238
|
+
Options7.optional
|
|
3239
|
+
);
|
|
3240
|
+
var jsonOption2 = Options7.boolean("json").pipe(Options7.withDescription("Print the result as JSON"));
|
|
3241
|
+
var microvmIdArg = Args5.text({ name: "microvm-id" }).pipe(Args5.withDescription("The microVM ID"));
|
|
3242
|
+
var envOption = Options7.text("env").pipe(
|
|
3243
|
+
Options7.withAlias("e"),
|
|
3244
|
+
Options7.withDescription("Environment variable as KEY=VALUE (repeatable)"),
|
|
3245
|
+
Options7.repeated
|
|
3246
|
+
);
|
|
3247
|
+
var userOption = Options7.text("user").pipe(
|
|
3248
|
+
Options7.withAlias("u"),
|
|
3249
|
+
Options7.withDescription("Run as this user (default: the image user)"),
|
|
3250
|
+
Options7.optional
|
|
3251
|
+
);
|
|
3252
|
+
var cwdOption = Options7.text("cwd").pipe(
|
|
3253
|
+
Options7.withAlias("w"),
|
|
3254
|
+
Options7.withDescription("Working directory inside the microVM"),
|
|
3255
|
+
Options7.optional
|
|
3256
|
+
);
|
|
3257
|
+
var errorMessage2 = (action, error) => {
|
|
3258
|
+
switch (error._tag) {
|
|
3259
|
+
case "NotAuthenticatedError":
|
|
3260
|
+
return 'You are not logged in. Run "gigadrive login" to authenticate.';
|
|
3261
|
+
case "ApiRequestError":
|
|
3262
|
+
if (error.code === "microvm_not_running") {
|
|
3263
|
+
return `${error.message}
|
|
3264
|
+
The microVM has stopped. Create a new one with "gigadrive microvms create".`;
|
|
3265
|
+
}
|
|
3266
|
+
if (error.code === "quota_exceeded" || error.code === "plan_limit_exceeded") {
|
|
3267
|
+
return `${error.message}
|
|
3268
|
+
Stop microVMs you no longer need, or raise your plan's limits at ${CONSOLE_URL2}.`;
|
|
3269
|
+
}
|
|
3270
|
+
if (error.code === "microvm_busy") {
|
|
3271
|
+
return `${error.message}
|
|
3272
|
+
The microVM is busy. Wait a moment and try again.`;
|
|
3273
|
+
}
|
|
3274
|
+
if (error.code === "microvm_unreachable") {
|
|
3275
|
+
return `${error.message}
|
|
3276
|
+
The microVM could not be reached. Try again; if it keeps failing, check "gigadrive microvms get <id>".`;
|
|
3277
|
+
}
|
|
3278
|
+
if (error.code === "image_not_ready") {
|
|
3279
|
+
return `${error.message}
|
|
3280
|
+
The image is not built yet. Check it with "gigadrive microvms images ls".`;
|
|
3281
|
+
}
|
|
3282
|
+
return `Failed to ${action}: ${error.message}`;
|
|
3283
|
+
default:
|
|
3284
|
+
return error.message;
|
|
3285
|
+
}
|
|
3286
|
+
};
|
|
3287
|
+
var reportErrors2 = (action) => (self) => Effect22.tapError(self, (error) => Console12.error(errorMessage2(action, error)));
|
|
3288
|
+
var parseDuration = (flag, value) => Effect22.try({
|
|
3289
|
+
try: () => {
|
|
3290
|
+
if (/^\d+(\.\d+)?$/.test(value.trim())) throw new RangeError("missing unit");
|
|
3291
|
+
return durationToMs(value);
|
|
3292
|
+
},
|
|
3293
|
+
catch: () => new InvalidMicroVmOptionsError({
|
|
3294
|
+
message: `${flag} must be a duration with a unit, such as 90s, 10m or 1h (got "${value}").`
|
|
3295
|
+
})
|
|
3296
|
+
});
|
|
3297
|
+
var optionalDuration = (flag, value) => Option14.match(value, {
|
|
3298
|
+
onNone: () => Effect22.succeed(void 0),
|
|
3299
|
+
onSome: (text) => parseDuration(flag, text)
|
|
3300
|
+
});
|
|
3301
|
+
var parseLifetime = (flag, value) => parseDuration(flag, value).pipe(
|
|
3302
|
+
Effect22.filterOrFail(
|
|
3303
|
+
(ms) => ms >= 1 && ms <= MICROVM_MAX_TIMEOUT_MS,
|
|
3304
|
+
() => new InvalidMicroVmOptionsError({ message: `${flag} can be at most 24h (got "${value}").` })
|
|
3305
|
+
)
|
|
3306
|
+
);
|
|
3307
|
+
var keyValues = (flag, entries) => {
|
|
3308
|
+
const parsed = parseKeyValues(entries);
|
|
3309
|
+
return parsed.ok ? Effect22.succeed(entries.length > 0 ? parsed.value : void 0) : Effect22.fail(new InvalidMicroVmOptionsError({ message: `${flag} expects KEY=VALUE, got "${parsed.entry}".` }));
|
|
3310
|
+
};
|
|
3311
|
+
var resolveApplicationId = (app) => Option14.match(app, {
|
|
3312
|
+
onSome: (id) => Effect22.succeed(id),
|
|
3313
|
+
onNone: () => Effect22.gen(function* () {
|
|
3314
|
+
const projectLink = yield* ProjectLinkService;
|
|
3315
|
+
const link = yield* projectLink.load(process.cwd());
|
|
3316
|
+
return Option14.match(link, { onNone: () => void 0, onSome: (value) => value.applicationId });
|
|
3317
|
+
})
|
|
3318
|
+
});
|
|
3319
|
+
var withClient = (run) => Effect22.gen(function* () {
|
|
3320
|
+
const apiClient = yield* ApiClientService;
|
|
3321
|
+
const client = yield* apiClient.getClient;
|
|
3322
|
+
return yield* Effect22.tryPromise({ try: () => run(client), catch: toApiRequestError });
|
|
3323
|
+
});
|
|
3324
|
+
var signalOnInterrupt = (signal) => (command, exit) => Exit.isInterrupted(exit) ? Effect22.tryPromise(() => command.kill(signal)).pipe(Effect22.timeout("2 seconds"), Effect22.ignore) : Effect22.void;
|
|
3325
|
+
var createInput = (flags) => Effect22.gen(function* () {
|
|
3326
|
+
const env = yield* keyValues("--env", flags.env);
|
|
3327
|
+
const metadata = yield* keyValues("--metadata", flags.metadata);
|
|
3328
|
+
const timeoutMs = yield* Option14.match(flags.timeout, {
|
|
3329
|
+
onNone: () => Effect22.succeed(void 0),
|
|
3330
|
+
onSome: (text) => parseLifetime("--timeout", text)
|
|
3331
|
+
});
|
|
3332
|
+
const badPort = flags.ports.find((port) => !Number.isInteger(port) || port < 1 || port > 65535);
|
|
3333
|
+
if (badPort !== void 0) {
|
|
3334
|
+
return yield* new InvalidMicroVmOptionsError({ message: `--port must be between 1 and 65535 (got ${badPort}).` });
|
|
3335
|
+
}
|
|
3336
|
+
const applicationId = yield* resolveApplicationId(flags.app);
|
|
3337
|
+
const input = {
|
|
3338
|
+
...applicationId ? { applicationId } : {},
|
|
3339
|
+
...Option14.match(flags.image, { onNone: () => ({}), onSome: (image) => ({ image }) }),
|
|
3340
|
+
...Option14.match(flags.vcpus, {
|
|
3341
|
+
onNone: () => ({}),
|
|
3342
|
+
onSome: (vcpus) => ({ vcpus: Number(vcpus) })
|
|
3343
|
+
}),
|
|
3344
|
+
...Option14.match(flags.memory, { onNone: () => ({}), onSome: (memoryMb) => ({ memoryMb }) }),
|
|
3345
|
+
...Option14.match(flags.disk, { onNone: () => ({}), onSome: (diskMb) => ({ diskMb }) }),
|
|
3346
|
+
...timeoutMs !== void 0 ? { timeoutMs } : {},
|
|
3347
|
+
...flags.ports.length > 0 ? { ports: [...flags.ports] } : {},
|
|
3348
|
+
...env ? { env } : {},
|
|
3349
|
+
...metadata ? { metadata } : {},
|
|
3350
|
+
...flags.denyEgress ? { network: { egress: "deny" } } : {},
|
|
3351
|
+
...Option14.match(flags.region, { onNone: () => ({}), onSome: (region) => ({ region }) })
|
|
3352
|
+
};
|
|
3353
|
+
return input;
|
|
3354
|
+
});
|
|
3355
|
+
var createMicroVm = (flags) => Effect22.gen(function* () {
|
|
3356
|
+
const apiClient = yield* ApiClientService;
|
|
3357
|
+
const input = yield* createInput(flags);
|
|
3358
|
+
if (!flags.json) yield* Console12.error(`Creating a microVM from ${input.image ?? "node22"}...`);
|
|
3359
|
+
const vm = yield* apiClient.request((client) => client.microvms.create(input));
|
|
3360
|
+
if (flags.json) {
|
|
3361
|
+
yield* Console12.log(JSON.stringify(vm, null, 2));
|
|
3362
|
+
return vm;
|
|
3363
|
+
}
|
|
3364
|
+
for (const line of describeMicroVm(vm)) yield* Console12.log(line);
|
|
3365
|
+
yield* Console12.log(`
|
|
3366
|
+
Run a command: gigadrive microvms exec ${vm.id} -- <command>`);
|
|
3367
|
+
yield* Console12.log(`Open a shell: gigadrive microvms shell ${vm.id}`);
|
|
3368
|
+
return vm;
|
|
3369
|
+
});
|
|
3370
|
+
var createCommand = Command12.make(
|
|
3371
|
+
"create",
|
|
3372
|
+
{
|
|
3373
|
+
app: appOption6,
|
|
3374
|
+
image: Options7.text("image").pipe(
|
|
3375
|
+
Options7.withAlias("i"),
|
|
3376
|
+
Options7.withDescription(
|
|
3377
|
+
"Image name or ID: node22, node24, python3.13, python3.12, bun1, debian12, ubuntu24.04, or a custom image (default: node22)"
|
|
3378
|
+
),
|
|
3379
|
+
Options7.optional
|
|
3380
|
+
),
|
|
3381
|
+
vcpus: Options7.choice("vcpus", ["1", "2", "4"]).pipe(
|
|
3382
|
+
Options7.withDescription("Number of vCPUs (default: 1)"),
|
|
3383
|
+
Options7.optional
|
|
3384
|
+
),
|
|
3385
|
+
memory: Options7.integer("memory").pipe(Options7.withDescription("Memory in MiB (default: 2048)"), Options7.optional),
|
|
3386
|
+
disk: Options7.integer("disk").pipe(Options7.withDescription("Disk in MiB (default: 10240)"), Options7.optional),
|
|
3387
|
+
timeout: Options7.text("timeout").pipe(
|
|
3388
|
+
Options7.withAlias("t"),
|
|
3389
|
+
Options7.withDescription("Lifetime before it stops automatically, such as 30m or 2h (default: 5m)"),
|
|
3390
|
+
Options7.optional
|
|
3391
|
+
),
|
|
3392
|
+
ports: Options7.integer("port").pipe(
|
|
3393
|
+
Options7.withAlias("p"),
|
|
3394
|
+
Options7.withDescription("Port to expose on a public preview URL (repeatable, up to 8)"),
|
|
3395
|
+
Options7.repeated
|
|
3396
|
+
),
|
|
3397
|
+
env: envOption,
|
|
3398
|
+
metadata: Options7.text("metadata").pipe(
|
|
3399
|
+
Options7.withAlias("m"),
|
|
3400
|
+
Options7.withDescription("Metadata as KEY=VALUE (repeatable)"),
|
|
3401
|
+
Options7.repeated
|
|
3402
|
+
),
|
|
3403
|
+
denyEgress: Options7.boolean("deny-egress").pipe(Options7.withDescription("Block all outbound network traffic")),
|
|
3404
|
+
region: Options7.text("region").pipe(Options7.withDescription("Region to place the microVM in"), Options7.optional),
|
|
3405
|
+
json: jsonOption2
|
|
3406
|
+
},
|
|
3407
|
+
(flags) => createMicroVm(flags).pipe(Effect22.asVoid, reportErrors2("create the microVM"))
|
|
3408
|
+
).pipe(Command12.withDescription("Create a microVM and wait until it runs"));
|
|
3409
|
+
var listMicroVms = (flags) => Effect22.gen(function* () {
|
|
3410
|
+
const apiClient = yield* ApiClientService;
|
|
3411
|
+
const applicationId = yield* resolveApplicationId(flags.app);
|
|
3412
|
+
const status = Option14.getOrUndefined(flags.status);
|
|
3413
|
+
const showAll = flags.all || status !== void 0;
|
|
3414
|
+
const vms = yield* apiClient.request(async (client) => {
|
|
3415
|
+
const items = [];
|
|
3416
|
+
for await (const vm of client.microvms.listAll({ applicationId, status, limit: Math.min(flags.limit, 100) })) {
|
|
3417
|
+
if (!showAll && (vm.status === "stopped" || vm.status === "failed")) continue;
|
|
3418
|
+
items.push(vm);
|
|
3419
|
+
if (items.length >= flags.limit) break;
|
|
3420
|
+
}
|
|
3421
|
+
return items;
|
|
3422
|
+
});
|
|
3423
|
+
if (flags.json) return yield* Console12.log(JSON.stringify(vms, null, 2));
|
|
3424
|
+
if (vms.length === 0) {
|
|
3425
|
+
return yield* Console12.log(
|
|
3426
|
+
showAll ? "No microVMs found." : 'No running microVMs. Create one with "gigadrive microvms create".'
|
|
3427
|
+
);
|
|
3428
|
+
}
|
|
3429
|
+
for (const line of formatMicroVmTable(vms)) yield* Console12.log(line);
|
|
3430
|
+
});
|
|
3431
|
+
var listCommand2 = Command12.make(
|
|
3432
|
+
"ls",
|
|
3433
|
+
{
|
|
3434
|
+
app: appOption6,
|
|
3435
|
+
status: Options7.choice("status", MICROVM_STATUSES).pipe(
|
|
3436
|
+
Options7.withAlias("s"),
|
|
3437
|
+
Options7.withDescription("Only microVMs in this status"),
|
|
3438
|
+
Options7.optional
|
|
3439
|
+
),
|
|
3440
|
+
all: Options7.boolean("all").pipe(Options7.withDescription("Include stopped and failed microVMs")),
|
|
3441
|
+
limit: Options7.integer("limit").pipe(
|
|
3442
|
+
Options7.withDescription("Show at most this many (default: 50)"),
|
|
3443
|
+
Options7.withDefault(50)
|
|
3444
|
+
),
|
|
3445
|
+
json: jsonOption2
|
|
3446
|
+
},
|
|
3447
|
+
(flags) => listMicroVms(flags).pipe(reportErrors2("list microVMs"))
|
|
3448
|
+
).pipe(Command12.withDescription("List microVMs"));
|
|
3449
|
+
var getCommand = Command12.make(
|
|
3450
|
+
"get",
|
|
3451
|
+
{ microvmId: microvmIdArg, json: jsonOption2 },
|
|
3452
|
+
({ microvmId, json }) => Effect22.gen(function* () {
|
|
3453
|
+
const apiClient = yield* ApiClientService;
|
|
3454
|
+
const vm = yield* apiClient.request((client) => client.microvms.get(microvmId));
|
|
3455
|
+
if (json) return yield* Console12.log(JSON.stringify(vm, null, 2));
|
|
3456
|
+
for (const line of describeMicroVm(vm)) yield* Console12.log(line);
|
|
3457
|
+
}).pipe(reportErrors2("get the microVM"))
|
|
3458
|
+
).pipe(Command12.withDescription("Show a microVM"));
|
|
3459
|
+
var execInMicroVm = (flags, io = nodeSessionIo()) => Effect22.gen(function* () {
|
|
3460
|
+
const env = yield* keyValues("--env", flags.env);
|
|
3461
|
+
const timeout = yield* optionalDuration("--timeout", flags.timeout);
|
|
3462
|
+
const [cmd, ...args] = flags.command;
|
|
3463
|
+
const options = {
|
|
3464
|
+
cwd: Option14.getOrUndefined(flags.cwd),
|
|
3465
|
+
env,
|
|
3466
|
+
user: Option14.getOrUndefined(flags.user),
|
|
3467
|
+
sudo: flags.sudo,
|
|
3468
|
+
timeout,
|
|
3469
|
+
stdin: flags.interactive
|
|
3470
|
+
};
|
|
3471
|
+
const info = yield* Effect22.acquireUseRelease(
|
|
3472
|
+
withClient(async (client) => {
|
|
3473
|
+
const vm = await MicroVM.get(flags.microvmId, { client });
|
|
3474
|
+
return args.length > 0 ? vm.spawn(cmd, args, options) : vm.spawn(cmd, options);
|
|
3475
|
+
}),
|
|
3476
|
+
(command) => Effect22.tryPromise({
|
|
3477
|
+
try: () => attachCommand(command, io, {
|
|
3478
|
+
forwardInput: flags.interactive,
|
|
3479
|
+
endInputOnEof: true,
|
|
3480
|
+
forwardResize: false
|
|
3481
|
+
}),
|
|
3482
|
+
catch: toApiRequestError
|
|
3483
|
+
}),
|
|
3484
|
+
signalOnInterrupt("SIGINT")
|
|
3485
|
+
);
|
|
3486
|
+
return exitStatusOf(info);
|
|
3487
|
+
});
|
|
3488
|
+
var setExitCode = (status) => Effect22.sync(() => {
|
|
3489
|
+
process.exitCode = status;
|
|
3490
|
+
});
|
|
3491
|
+
var execCommand = Command12.make(
|
|
3492
|
+
"exec",
|
|
3493
|
+
{
|
|
3494
|
+
microvmId: microvmIdArg,
|
|
3495
|
+
command: Args5.text({ name: "command" }).pipe(
|
|
3496
|
+
Args5.withDescription('The command and its arguments, after "--". One quoted argument runs through /bin/sh -c'),
|
|
3497
|
+
Args5.atLeast(1)
|
|
3498
|
+
),
|
|
3499
|
+
cwd: cwdOption,
|
|
3500
|
+
env: envOption,
|
|
3501
|
+
user: userOption,
|
|
3502
|
+
sudo: Options7.boolean("sudo").pipe(Options7.withDescription("Run as root")),
|
|
3503
|
+
timeout: Options7.text("timeout").pipe(
|
|
3504
|
+
Options7.withAlias("t"),
|
|
3505
|
+
Options7.withDescription("Kill the command after this long, such as 30s or 10m"),
|
|
3506
|
+
Options7.optional
|
|
3507
|
+
),
|
|
3508
|
+
interactive: Options7.boolean("interactive").pipe(
|
|
3509
|
+
Options7.withAlias("i"),
|
|
3510
|
+
Options7.withDescription("Forward local stdin to the command")
|
|
3511
|
+
)
|
|
3512
|
+
},
|
|
3513
|
+
(flags) => execInMicroVm(flags).pipe(Effect22.flatMap(setExitCode), reportErrors2("run the command"))
|
|
3514
|
+
).pipe(Command12.withDescription("Run a command in a microVM and exit with its exit code: exec <id> -- <command...>"));
|
|
3515
|
+
var openShell = (flags, io = nodeSessionIo()) => Effect22.gen(function* () {
|
|
3516
|
+
if (process.stdin.isTTY !== true || process.stdout.isTTY !== true) {
|
|
3517
|
+
return yield* new TerminalRequiredError({
|
|
3518
|
+
message: 'microvms shell needs an interactive terminal. Use "gigadrive microvms exec <id> -- <command>" in scripts.'
|
|
3519
|
+
});
|
|
3520
|
+
}
|
|
3521
|
+
const term = yield* Config4.string("TERM").pipe(Config4.withDefault("xterm-256color"));
|
|
3522
|
+
const size = io.size() ?? { cols: 80, rows: 24 };
|
|
3523
|
+
const [cmd, ...args] = Option14.match(flags.shell, { onNone: () => DEFAULT_SHELL, onSome: (shell) => [shell] });
|
|
3524
|
+
const options = {
|
|
3525
|
+
pty: { cols: size.cols, rows: size.rows },
|
|
3526
|
+
env: { TERM: term },
|
|
3527
|
+
cwd: Option14.getOrUndefined(flags.cwd),
|
|
3528
|
+
user: Option14.getOrUndefined(flags.user)
|
|
3529
|
+
};
|
|
3530
|
+
const info = yield* Effect22.acquireUseRelease(
|
|
3531
|
+
withClient(async (client) => {
|
|
3532
|
+
const vm = await MicroVM.get(flags.microvmId, { client });
|
|
3533
|
+
return args.length > 0 ? vm.spawn(cmd, args, options) : vm.spawn(cmd, options);
|
|
3534
|
+
}),
|
|
3535
|
+
(command) => Effect22.acquireUseRelease(
|
|
3536
|
+
Effect22.sync(() => process.stdin.setRawMode(true)),
|
|
3537
|
+
() => Effect22.tryPromise({
|
|
3538
|
+
try: () => attachCommand(command, io, { forwardInput: true, endInputOnEof: false, forwardResize: true }),
|
|
3539
|
+
catch: toApiRequestError
|
|
3540
|
+
}),
|
|
3541
|
+
() => Effect22.sync(() => process.stdin.setRawMode(false))
|
|
3542
|
+
),
|
|
3543
|
+
signalOnInterrupt("SIGHUP")
|
|
3544
|
+
);
|
|
3545
|
+
return exitStatusOf(info);
|
|
3546
|
+
});
|
|
3547
|
+
var shellCommand = Command12.make(
|
|
3548
|
+
"shell",
|
|
3549
|
+
{
|
|
3550
|
+
microvmId: microvmIdArg,
|
|
3551
|
+
shell: Options7.text("shell").pipe(
|
|
3552
|
+
Options7.withDescription("Program to run instead of the login shell, such as python3"),
|
|
3553
|
+
Options7.optional
|
|
3554
|
+
),
|
|
3555
|
+
cwd: cwdOption,
|
|
3556
|
+
user: userOption
|
|
3557
|
+
},
|
|
3558
|
+
(flags) => openShell(flags).pipe(Effect22.flatMap(setExitCode), reportErrors2("open a shell"))
|
|
3559
|
+
).pipe(Command12.withDescription("Open an interactive shell in a microVM"));
|
|
3560
|
+
var localError = (path2, action) => () => new LocalFileError({ message: `Failed to ${action} ${path2}`, path: path2 });
|
|
3561
|
+
var parseTarget = (value) => {
|
|
3562
|
+
const target = parseCopyTarget(value);
|
|
3563
|
+
return typeof target === "string" ? Effect22.fail(new InvalidCopyTargetError({ message: target })) : Effect22.succeed(target);
|
|
3564
|
+
};
|
|
3565
|
+
var remoteStat = (client, microvmId, path2) => Effect22.tryPromise({ try: () => client.microvms.files.stat(microvmId, path2), catch: toApiRequestError }).pipe(
|
|
3566
|
+
Effect22.map(Option14.some),
|
|
3567
|
+
Effect22.catchIf(
|
|
3568
|
+
(error) => error.statusCode === 404,
|
|
3569
|
+
() => Effect22.succeed(Option14.none())
|
|
3570
|
+
)
|
|
3571
|
+
);
|
|
3572
|
+
var upload = (client, source, target) => Effect22.gen(function* () {
|
|
3573
|
+
const fs = yield* FileSystem8.FileSystem;
|
|
3574
|
+
const path2 = yield* Path7.Path;
|
|
3575
|
+
const info = yield* fs.stat(source).pipe(Effect22.mapError(localError(source, "read")));
|
|
3576
|
+
const existing = yield* remoteStat(client, target.microvmId, target.path);
|
|
3577
|
+
const intoDirectory = target.path.endsWith("/") || Option14.exists(existing, (entry) => entry.type === "directory");
|
|
3578
|
+
const destination = intoDirectory ? posixJoin(target.path, path2.basename(source)) : target.path;
|
|
3579
|
+
const files = [];
|
|
3580
|
+
if (info.type === "Directory") {
|
|
3581
|
+
const entries = yield* fs.readDirectory(source, { recursive: true }).pipe(Effect22.mapError(localError(source, "list")));
|
|
3582
|
+
for (const entry of entries) {
|
|
3583
|
+
const local = path2.join(source, entry);
|
|
3584
|
+
const stat = yield* fs.stat(local).pipe(Effect22.mapError(localError(local, "read")));
|
|
3585
|
+
if (stat.type === "File")
|
|
3586
|
+
files.push({ local, remote: posixJoin(destination, entry.split(path2.sep).join("/")) });
|
|
3587
|
+
}
|
|
3588
|
+
} else {
|
|
3589
|
+
files.push({ local: source, remote: destination });
|
|
3590
|
+
}
|
|
3591
|
+
let bytes = 0;
|
|
3592
|
+
for (const file of files) {
|
|
3593
|
+
const content = yield* fs.readFile(file.local).pipe(Effect22.mapError(localError(file.local, "read")));
|
|
3594
|
+
const result = yield* Effect22.tryPromise({
|
|
3595
|
+
try: () => client.microvms.files.write(target.microvmId, file.remote, content),
|
|
3596
|
+
catch: toApiRequestError
|
|
3597
|
+
});
|
|
3598
|
+
bytes += result.bytes;
|
|
3599
|
+
}
|
|
3600
|
+
return { files: files.length, bytes };
|
|
3601
|
+
});
|
|
3602
|
+
var download = (client, source, target) => Effect22.gen(function* () {
|
|
3603
|
+
const fs = yield* FileSystem8.FileSystem;
|
|
3604
|
+
const path2 = yield* Path7.Path;
|
|
3605
|
+
const entry = yield* Effect22.tryPromise({
|
|
3606
|
+
try: () => client.microvms.files.stat(source.microvmId, source.path),
|
|
3607
|
+
catch: toApiRequestError
|
|
3608
|
+
});
|
|
3609
|
+
const targetExists = yield* fs.exists(target).pipe(Effect22.mapError(localError(target, "check")));
|
|
3610
|
+
const targetIsDirectory = targetExists ? (yield* fs.stat(target).pipe(Effect22.mapError(localError(target, "read")))).type === "Directory" : false;
|
|
3611
|
+
const intoDirectory = targetIsDirectory || target.endsWith(path2.sep) || target.endsWith("/");
|
|
3612
|
+
const destination = intoDirectory ? path2.join(target, posixBasename(entry.path)) : target;
|
|
3613
|
+
const files = [];
|
|
3614
|
+
if (entry.type === "directory") {
|
|
3615
|
+
const listing = yield* Effect22.tryPromise({
|
|
3616
|
+
try: () => client.microvms.files.list(source.microvmId, entry.path, { recursive: true }),
|
|
3617
|
+
catch: toApiRequestError
|
|
3618
|
+
});
|
|
3619
|
+
if (listing.truncated) {
|
|
3620
|
+
yield* Console12.error(
|
|
3621
|
+
`${entry.path} has too many files to list at once; only part of it is copied. Copy its subdirectories separately.`
|
|
3622
|
+
);
|
|
3623
|
+
}
|
|
3624
|
+
for (const child of listing.entries) {
|
|
3625
|
+
if (child.type !== "file") continue;
|
|
3626
|
+
const segments = relativeTo(entry.path, child.path).split("/");
|
|
3627
|
+
if (segments.some((segment) => segment === "" || segment === "." || segment === "..")) {
|
|
3628
|
+
yield* Console12.error(`Skipped ${child.path}: its path leaves ${entry.path}.`);
|
|
3629
|
+
continue;
|
|
3630
|
+
}
|
|
3631
|
+
files.push({ remote: child.path, local: path2.join(destination, ...segments) });
|
|
3632
|
+
}
|
|
3633
|
+
yield* fs.makeDirectory(destination, { recursive: true }).pipe(Effect22.mapError(localError(destination, "create")));
|
|
3634
|
+
} else {
|
|
3635
|
+
files.push({ remote: entry.path, local: destination });
|
|
3636
|
+
}
|
|
3637
|
+
let bytes = 0;
|
|
3638
|
+
for (const file of files) {
|
|
3639
|
+
const content = yield* Effect22.tryPromise({
|
|
3640
|
+
try: () => client.microvms.files.read(source.microvmId, file.remote),
|
|
3641
|
+
catch: toApiRequestError
|
|
3642
|
+
});
|
|
3643
|
+
yield* fs.makeDirectory(path2.dirname(file.local), { recursive: true }).pipe(Effect22.mapError(localError(file.local, "create the directory of")));
|
|
3644
|
+
yield* fs.writeFile(file.local, content).pipe(Effect22.mapError(localError(file.local, "write")));
|
|
3645
|
+
bytes += content.length;
|
|
3646
|
+
}
|
|
3647
|
+
return { files: files.length, bytes };
|
|
3648
|
+
});
|
|
3649
|
+
var copyFiles = (sourceSpec, targetSpec) => Effect22.gen(function* () {
|
|
3650
|
+
const source = yield* parseTarget(sourceSpec);
|
|
3651
|
+
const target = yield* parseTarget(targetSpec);
|
|
3652
|
+
const apiClient = yield* ApiClientService;
|
|
3653
|
+
const client = yield* apiClient.getClient;
|
|
3654
|
+
if (source.kind === "local" && target.kind === "remote") return yield* upload(client, source.path, target);
|
|
3655
|
+
if (source.kind === "remote" && target.kind === "local") return yield* download(client, source, target.path);
|
|
3656
|
+
return yield* new InvalidCopyTargetError({
|
|
3657
|
+
message: 'One side must be local and the other vm:<id>:<path>, e.g. "gigadrive microvms cp ./app vm:<id>:/app".'
|
|
3658
|
+
});
|
|
3659
|
+
});
|
|
3660
|
+
var copyCommand = Command12.make(
|
|
3661
|
+
"cp",
|
|
3662
|
+
{
|
|
3663
|
+
source: Args5.text({ name: "source" }).pipe(Args5.withDescription("Local path or vm:<id>:<path>")),
|
|
3664
|
+
target: Args5.text({ name: "target" }).pipe(Args5.withDescription("Local path or vm:<id>:<path>"))
|
|
3665
|
+
},
|
|
3666
|
+
({ source, target }) => copyFiles(source, target).pipe(
|
|
3667
|
+
Effect22.flatMap(
|
|
3668
|
+
(result) => Console12.log(`Copied ${result.files} file${result.files === 1 ? "" : "s"} (${formatBytes(result.bytes)}).`)
|
|
3669
|
+
),
|
|
3670
|
+
reportErrors2("copy")
|
|
3671
|
+
)
|
|
3672
|
+
).pipe(Command12.withDescription("Copy files between this machine and a microVM (vm:<id>:<path>)"));
|
|
3673
|
+
var microvmIdsArg = Args5.text({ name: "microvm-id" }).pipe(
|
|
3674
|
+
Args5.withDescription("One or more microVM IDs"),
|
|
3675
|
+
Args5.atLeast(1)
|
|
3676
|
+
);
|
|
3677
|
+
var stopCommand = Command12.make(
|
|
3678
|
+
"stop",
|
|
3679
|
+
{ microvmIds: microvmIdsArg },
|
|
3680
|
+
({ microvmIds }) => Effect22.forEach(
|
|
3681
|
+
microvmIds,
|
|
3682
|
+
(microvmId) => Effect22.gen(function* () {
|
|
3683
|
+
const apiClient = yield* ApiClientService;
|
|
3684
|
+
yield* apiClient.request((client) => client.microvms.stop(microvmId));
|
|
3685
|
+
yield* Console12.log(`Stopped ${microvmId}.`);
|
|
3686
|
+
}),
|
|
3687
|
+
{ discard: true }
|
|
3688
|
+
).pipe(reportErrors2("stop the microVM"))
|
|
3689
|
+
).pipe(Command12.withDescription("Stop microVMs; their disk and memory are discarded"));
|
|
3690
|
+
var removeCommand2 = Command12.make(
|
|
3691
|
+
"rm",
|
|
3692
|
+
{ microvmIds: microvmIdsArg },
|
|
3693
|
+
({ microvmIds }) => Effect22.forEach(
|
|
3694
|
+
microvmIds,
|
|
3695
|
+
(microvmId) => Effect22.gen(function* () {
|
|
3696
|
+
const apiClient = yield* ApiClientService;
|
|
3697
|
+
yield* apiClient.request((client) => client.microvms.delete(microvmId));
|
|
3698
|
+
yield* Console12.log(`Deleted ${microvmId}.`);
|
|
3699
|
+
}),
|
|
3700
|
+
{ discard: true }
|
|
3701
|
+
).pipe(reportErrors2("delete the microVM"))
|
|
3702
|
+
).pipe(Command12.withDescription("Delete microVMs, stopping them first"));
|
|
3703
|
+
var extendMicroVm = (microvmId, duration) => Effect22.gen(function* () {
|
|
3704
|
+
const timeoutMs = yield* parseLifetime("The duration", duration);
|
|
3705
|
+
const apiClient = yield* ApiClientService;
|
|
3706
|
+
const vm = yield* apiClient.request((client) => client.microvms.extendTimeout(microvmId, timeoutMs));
|
|
3707
|
+
yield* Console12.log(`${vm.id} now stops at ${vm.expiresAt ?? "an unknown time"}.`);
|
|
3708
|
+
return vm;
|
|
3709
|
+
});
|
|
3710
|
+
var extendCommand = Command12.make(
|
|
3711
|
+
"extend",
|
|
3712
|
+
{
|
|
3713
|
+
microvmId: microvmIdArg,
|
|
3714
|
+
duration: Args5.text({ name: "duration" }).pipe(
|
|
3715
|
+
Args5.withDescription("New remaining lifetime from now, such as 30m")
|
|
3716
|
+
)
|
|
3717
|
+
},
|
|
3718
|
+
({ microvmId, duration }) => extendMicroVm(microvmId, duration).pipe(Effect22.asVoid, reportErrors2("extend the microVM"))
|
|
3719
|
+
).pipe(Command12.withDescription("Set how long a microVM keeps running, counted from now"));
|
|
3720
|
+
var imagesListCommand = Command12.make(
|
|
3721
|
+
"ls",
|
|
3722
|
+
{ app: appOption6, json: jsonOption2 },
|
|
3723
|
+
({ app, json }) => Effect22.gen(function* () {
|
|
3724
|
+
const apiClient = yield* ApiClientService;
|
|
3725
|
+
const applicationId = yield* resolveApplicationId(app);
|
|
3726
|
+
const images = yield* apiClient.request((client) => client.microvms.images.list({ applicationId }));
|
|
3727
|
+
if (json) return yield* Console12.log(JSON.stringify(images, null, 2));
|
|
3728
|
+
for (const line of formatImageTable(images)) yield* Console12.log(line);
|
|
3729
|
+
}).pipe(reportErrors2("list images"))
|
|
3730
|
+
).pipe(Command12.withDescription("List built-in and custom images"));
|
|
3731
|
+
var createImage = (flags) => Effect22.gen(function* () {
|
|
3732
|
+
const fs = yield* FileSystem8.FileSystem;
|
|
3733
|
+
const apiClient = yield* ApiClientService;
|
|
3734
|
+
if (Option14.isSome(flags.from) === Option14.isSome(flags.dockerfile)) {
|
|
3735
|
+
return yield* new InvalidMicroVmOptionsError({
|
|
3736
|
+
message: "Pass exactly one of --from <registry reference> or --dockerfile <path>."
|
|
3737
|
+
});
|
|
3738
|
+
}
|
|
3739
|
+
const from = Option14.isSome(flags.from) ? flags.from.value : {
|
|
3740
|
+
dockerfile: yield* fs.readFileString(Option14.getOrElse(flags.dockerfile, () => "Dockerfile")).pipe(
|
|
3741
|
+
Effect22.mapError(
|
|
3742
|
+
localError(
|
|
3743
|
+
Option14.getOrElse(flags.dockerfile, () => "Dockerfile"),
|
|
3744
|
+
"read"
|
|
3745
|
+
)
|
|
3746
|
+
)
|
|
3747
|
+
)
|
|
3748
|
+
};
|
|
3749
|
+
const applicationId = yield* resolveApplicationId(flags.app);
|
|
3750
|
+
let image = yield* apiClient.request(
|
|
3751
|
+
(client) => client.microvms.images.create({ name: flags.name, from, ...applicationId ? { applicationId } : {} })
|
|
3752
|
+
);
|
|
3753
|
+
if (flags.wait) {
|
|
3754
|
+
if (!flags.json) yield* Console12.error(`Building ${image.name}...`);
|
|
3755
|
+
image = yield* apiClient.request(
|
|
3756
|
+
(client) => client.microvms.images.waitUntilReady(image.id, {
|
|
3757
|
+
onStatus: (current) => {
|
|
3758
|
+
if (!flags.json) process.stderr.write(` ${current.status}
|
|
3759
|
+
`);
|
|
3760
|
+
}
|
|
3761
|
+
})
|
|
3762
|
+
);
|
|
3763
|
+
}
|
|
3764
|
+
if (flags.json) {
|
|
3765
|
+
yield* Console12.log(JSON.stringify(image, null, 2));
|
|
3766
|
+
} else if (image.status === "ready") {
|
|
3767
|
+
yield* Console12.log(`${image.name} is ready. Use it with: gigadrive microvms create --image ${image.name}`);
|
|
3768
|
+
} else {
|
|
3769
|
+
yield* Console12.log(
|
|
3770
|
+
`${image.name} is ${image.status} (${image.id}). Check it with "gigadrive microvms images ls".`
|
|
3771
|
+
);
|
|
3772
|
+
}
|
|
3773
|
+
return image;
|
|
3774
|
+
});
|
|
3775
|
+
var imagesCreateCommand = Command12.make(
|
|
3776
|
+
"create",
|
|
3777
|
+
{
|
|
3778
|
+
name: Args5.text({ name: "name" }).pipe(
|
|
3779
|
+
Args5.withDescription('Image name: lowercase letters, digits, ".", "_", "-"')
|
|
3780
|
+
),
|
|
3781
|
+
app: appOption6,
|
|
3782
|
+
from: Options7.text("from").pipe(
|
|
3783
|
+
Options7.withDescription("Public linux/amd64 registry image, such as python:3.13 or ghcr.io/acme/tool:1"),
|
|
3784
|
+
Options7.optional
|
|
3785
|
+
),
|
|
3786
|
+
dockerfile: Options7.text("dockerfile").pipe(
|
|
3787
|
+
Options7.withAlias("f"),
|
|
3788
|
+
Options7.withDescription("Build from this Dockerfile instead"),
|
|
3789
|
+
Options7.optional
|
|
3790
|
+
),
|
|
3791
|
+
wait: Options7.boolean("wait").pipe(Options7.withDescription("Wait until the build finished")),
|
|
3792
|
+
json: jsonOption2
|
|
3793
|
+
},
|
|
3794
|
+
(flags) => createImage(flags).pipe(Effect22.asVoid, reportErrors2("create the image"))
|
|
3795
|
+
).pipe(Command12.withDescription("Build a custom image from a registry image or a Dockerfile"));
|
|
3796
|
+
var removeImage = (idOrName, app) => Effect22.gen(function* () {
|
|
3797
|
+
const apiClient = yield* ApiClientService;
|
|
3798
|
+
const applicationId = yield* resolveApplicationId(app);
|
|
3799
|
+
const images = yield* apiClient.request((client) => client.microvms.images.list({ applicationId }));
|
|
3800
|
+
const image = images.find((candidate) => candidate.id === idOrName) ?? images.find((candidate) => !candidate.builtin && candidate.name === idOrName) ?? { id: idOrName, name: idOrName };
|
|
3801
|
+
yield* apiClient.request((client) => client.microvms.images.delete(image.id));
|
|
3802
|
+
yield* Console12.log(`Deleted image ${image.name}.`);
|
|
3803
|
+
});
|
|
3804
|
+
var imagesRemoveCommand = Command12.make(
|
|
3805
|
+
"rm",
|
|
3806
|
+
{
|
|
3807
|
+
image: Args5.text({ name: "image" }).pipe(Args5.withDescription("Image ID or name")),
|
|
3808
|
+
app: appOption6
|
|
3809
|
+
},
|
|
3810
|
+
({ image, app }) => removeImage(image, app).pipe(reportErrors2("delete the image"))
|
|
3811
|
+
).pipe(Command12.withDescription("Delete a custom image"));
|
|
3812
|
+
var imagesCommand = Command12.make("images", {}, () => Effect22.void).pipe(
|
|
3813
|
+
Command12.withDescription("Built-in and custom microVM images"),
|
|
3814
|
+
Command12.withSubcommands([imagesListCommand, imagesCreateCommand, imagesRemoveCommand])
|
|
3815
|
+
);
|
|
3816
|
+
var SUBCOMMANDS = [
|
|
3817
|
+
createCommand,
|
|
3818
|
+
listCommand2,
|
|
3819
|
+
getCommand,
|
|
3820
|
+
execCommand,
|
|
3821
|
+
shellCommand,
|
|
3822
|
+
copyCommand,
|
|
3823
|
+
stopCommand,
|
|
3824
|
+
removeCommand2,
|
|
3825
|
+
extendCommand,
|
|
3826
|
+
imagesCommand
|
|
3827
|
+
];
|
|
3828
|
+
var microvmsCommand = Command12.make("microvms", {}, () => Effect22.void).pipe(
|
|
3829
|
+
Command12.withDescription("Isolated Linux machines for running code: create, exec, shell, cp, stop"),
|
|
3830
|
+
Command12.withSubcommands(SUBCOMMANDS)
|
|
3831
|
+
);
|
|
3832
|
+
var vmCommand = Command12.make("vm", {}, () => Effect22.void).pipe(
|
|
3833
|
+
Command12.withDescription('Alias of "gigadrive microvms"'),
|
|
3834
|
+
Command12.withSubcommands(SUBCOMMANDS)
|
|
3835
|
+
);
|
|
3836
|
+
|
|
2942
3837
|
// src/commands/platform/index.ts
|
|
2943
|
-
import { Command as
|
|
2944
|
-
import { Effect as
|
|
3838
|
+
import { Command as Command14 } from "@effect/cli";
|
|
3839
|
+
import { Effect as Effect26 } from "effect";
|
|
2945
3840
|
|
|
2946
3841
|
// src/commands/platform/deploy/index.ts
|
|
2947
|
-
import { Command as
|
|
2948
|
-
import { FileSystem as
|
|
3842
|
+
import { Command as Command13, Options as Options8, Prompt as Prompt4 } from "@effect/cli";
|
|
3843
|
+
import { FileSystem as FileSystem10, Path as Path9 } from "@effect/platform";
|
|
2949
3844
|
import { formatFileSize } from "@gigadrive/commons";
|
|
2950
|
-
import { Console as
|
|
3845
|
+
import { Console as Console13, Duration as Duration3, Effect as Effect25, Option as Option15, Schedule, Stream } from "effect";
|
|
2951
3846
|
import * as os4 from "os";
|
|
2952
3847
|
import * as path from "path";
|
|
2953
3848
|
|
|
2954
3849
|
// src/services/archive.ts
|
|
2955
|
-
import { FileSystem as
|
|
3850
|
+
import { FileSystem as FileSystem9, Path as Path8 } from "@effect/platform";
|
|
2956
3851
|
import { getFilesForPattern } from "@gigadrive/build-utils";
|
|
2957
3852
|
import archiver from "archiver";
|
|
2958
|
-
import { Effect as
|
|
3853
|
+
import { Effect as Effect23 } from "effect";
|
|
2959
3854
|
import ignore from "ignore";
|
|
2960
3855
|
import * as nodeFs from "fs";
|
|
2961
3856
|
import * as nodeFsPromises from "fs/promises";
|
|
@@ -2980,15 +3875,15 @@ var CONTAINER_BUILD_FILE_PATTERNS = [
|
|
|
2980
3875
|
];
|
|
2981
3876
|
var DOCKER_BUILD_IGNORE_FILE = ".dockerignore";
|
|
2982
3877
|
var readIgnoreFile = (fs, ignorePath) => fs.readFileString(ignorePath, "utf8").pipe(
|
|
2983
|
-
|
|
2984
|
-
|
|
3878
|
+
Effect23.map((content) => content.split("\n").filter((line) => line.trim() !== "")),
|
|
3879
|
+
Effect23.catchAll(() => Effect23.succeed([]))
|
|
2985
3880
|
);
|
|
2986
|
-
var collectIgnorePatterns = (fs, pathService, dir, baseDir, ignoreRules, ignoreFileNames) =>
|
|
2987
|
-
const items = yield* fs.readDirectory(dir).pipe(
|
|
3881
|
+
var collectIgnorePatterns = (fs, pathService, dir, baseDir, ignoreRules, ignoreFileNames) => Effect23.gen(function* () {
|
|
3882
|
+
const items = yield* fs.readDirectory(dir).pipe(Effect23.mapError(() => new ArchiveCreateError({ message: `Failed to read directory: ${dir}` })));
|
|
2988
3883
|
for (const item of items) {
|
|
2989
3884
|
const fullPath = pathService.join(dir, item);
|
|
2990
3885
|
const relativePath = pathService.relative(baseDir, fullPath);
|
|
2991
|
-
const stat = yield*
|
|
3886
|
+
const stat = yield* Effect23.tryPromise({
|
|
2992
3887
|
try: () => nodeFsPromises.lstat(fullPath),
|
|
2993
3888
|
catch: () => new ArchiveCreateError({ message: `Failed to stat: ${fullPath}` })
|
|
2994
3889
|
});
|
|
@@ -3008,8 +3903,8 @@ var collectIgnorePatterns = (fs, pathService, dir, baseDir, ignoreRules, ignoreF
|
|
|
3008
3903
|
yield* collectIgnorePatterns(fs, pathService, fullPath, baseDir, ignoreRules, ignoreFileNames);
|
|
3009
3904
|
}
|
|
3010
3905
|
}
|
|
3011
|
-
}).pipe(
|
|
3012
|
-
var initializeIgnoreRules = (fs, pathService, baseDir, useIgnoreFiles, useManagedIgnore, buildsContainerImages) =>
|
|
3906
|
+
}).pipe(Effect23.catchAll(() => Effect23.void));
|
|
3907
|
+
var initializeIgnoreRules = (fs, pathService, baseDir, useIgnoreFiles, useManagedIgnore, buildsContainerImages) => Effect23.gen(function* () {
|
|
3013
3908
|
const ignoreRules = ignore();
|
|
3014
3909
|
const ignoreFileNames = buildsContainerImages ? IGNORE_FILE_NAMES.filter((name) => name !== DOCKER_BUILD_IGNORE_FILE) : IGNORE_FILE_NAMES;
|
|
3015
3910
|
if (useManagedIgnore) {
|
|
@@ -3030,14 +3925,14 @@ var initializeIgnoreRules = (fs, pathService, baseDir, useIgnoreFiles, useManage
|
|
|
3030
3925
|
}
|
|
3031
3926
|
return ignoreRules;
|
|
3032
3927
|
});
|
|
3033
|
-
var getFilesToInclude = (fs, pathService, dir, ignoreRules) =>
|
|
3928
|
+
var getFilesToInclude = (fs, pathService, dir, ignoreRules) => Effect23.gen(function* () {
|
|
3034
3929
|
const filesToInclude = [];
|
|
3035
|
-
const walkDir = (currentDir) =>
|
|
3036
|
-
const items = yield* fs.readDirectory(currentDir).pipe(
|
|
3930
|
+
const walkDir = (currentDir) => Effect23.gen(function* () {
|
|
3931
|
+
const items = yield* fs.readDirectory(currentDir).pipe(Effect23.mapError(() => new ArchiveCreateError({ message: `Failed to read directory: ${currentDir}` })));
|
|
3037
3932
|
for (const item of items) {
|
|
3038
3933
|
const fullPath = pathService.join(currentDir, item);
|
|
3039
3934
|
const relativePath = pathService.relative(dir, fullPath);
|
|
3040
|
-
const stat = yield*
|
|
3935
|
+
const stat = yield* Effect23.tryPromise({
|
|
3041
3936
|
try: () => nodeFsPromises.lstat(fullPath),
|
|
3042
3937
|
catch: () => new ArchiveCreateError({ message: `Failed to stat: ${fullPath}` })
|
|
3043
3938
|
});
|
|
@@ -3051,19 +3946,19 @@ var getFilesToInclude = (fs, pathService, dir, ignoreRules) => Effect22.gen(func
|
|
|
3051
3946
|
}
|
|
3052
3947
|
}
|
|
3053
3948
|
}
|
|
3054
|
-
}).pipe(
|
|
3949
|
+
}).pipe(Effect23.catchAll(() => Effect23.void));
|
|
3055
3950
|
yield* walkDir(dir);
|
|
3056
3951
|
return filesToInclude;
|
|
3057
3952
|
});
|
|
3058
|
-
var ArchiveService = class extends
|
|
3953
|
+
var ArchiveService = class extends Effect23.Service()("ArchiveService", {
|
|
3059
3954
|
accessors: true,
|
|
3060
|
-
effect:
|
|
3061
|
-
const fs = yield*
|
|
3062
|
-
const pathService = yield*
|
|
3063
|
-
const createZipArchive =
|
|
3064
|
-
yield*
|
|
3065
|
-
yield*
|
|
3066
|
-
yield*
|
|
3955
|
+
effect: Effect23.gen(function* () {
|
|
3956
|
+
const fs = yield* FileSystem9.FileSystem;
|
|
3957
|
+
const pathService = yield* Path8.Path;
|
|
3958
|
+
const createZipArchive = Effect23.fn("ArchiveService.createZipArchive")(function* (inputDir, outputFile, options = {}) {
|
|
3959
|
+
yield* Effect23.annotateCurrentSpan("inputDir", inputDir);
|
|
3960
|
+
yield* Effect23.annotateCurrentSpan("outputFile", outputFile);
|
|
3961
|
+
yield* Effect23.log("Creating zip archive", { inputDir, outputFile });
|
|
3067
3962
|
const ignoreRules = yield* initializeIgnoreRules(
|
|
3068
3963
|
fs,
|
|
3069
3964
|
pathService,
|
|
@@ -3072,7 +3967,7 @@ var ArchiveService = class extends Effect22.Service()("ArchiveService", {
|
|
|
3072
3967
|
options.useManagedIgnore !== false,
|
|
3073
3968
|
options.buildsContainerImages === true
|
|
3074
3969
|
);
|
|
3075
|
-
const filesToInclude = options.whitelist ? yield*
|
|
3970
|
+
const filesToInclude = options.whitelist ? yield* Effect23.tryPromise({
|
|
3076
3971
|
try: () => Promise.all(
|
|
3077
3972
|
options.whitelist.map((file) => getFilesForPattern(file, inputDir, options.excludeFiles ?? []))
|
|
3078
3973
|
),
|
|
@@ -3080,25 +3975,25 @@ var ArchiveService = class extends Effect22.Service()("ArchiveService", {
|
|
|
3080
3975
|
message: "Failed to resolve whitelist patterns",
|
|
3081
3976
|
cause: error instanceof Error ? error.message : String(error)
|
|
3082
3977
|
})
|
|
3083
|
-
}) : yield*
|
|
3978
|
+
}) : yield* Effect23.gen(function* () {
|
|
3084
3979
|
if (options.excludeFiles && options.excludeFiles.length > 0) {
|
|
3085
3980
|
ignoreRules.add(options.excludeFiles);
|
|
3086
3981
|
}
|
|
3087
3982
|
return yield* getFilesToInclude(fs, pathService, inputDir, ignoreRules).pipe(
|
|
3088
|
-
|
|
3983
|
+
Effect23.map((files) => [files])
|
|
3089
3984
|
);
|
|
3090
3985
|
});
|
|
3091
|
-
const archiveExists = yield* fs.exists(outputFile).pipe(
|
|
3986
|
+
const archiveExists = yield* fs.exists(outputFile).pipe(Effect23.catchAll(() => Effect23.succeed(false)));
|
|
3092
3987
|
if (archiveExists) {
|
|
3093
3988
|
yield* fs.remove(outputFile).pipe(
|
|
3094
|
-
|
|
3989
|
+
Effect23.mapError(
|
|
3095
3990
|
(error) => new ArchiveCreateError({
|
|
3096
3991
|
message: `Failed to remove existing archive: ${String(error)}`
|
|
3097
3992
|
})
|
|
3098
3993
|
)
|
|
3099
3994
|
);
|
|
3100
3995
|
}
|
|
3101
|
-
yield*
|
|
3996
|
+
yield* Effect23.tryPromise({
|
|
3102
3997
|
try: () => new Promise((resolve, reject) => {
|
|
3103
3998
|
const output = nodeFs.createWriteStream(outputFile);
|
|
3104
3999
|
const archive = archiver("zip", { zlib: { level: 9 } });
|
|
@@ -3117,14 +4012,14 @@ var ArchiveService = class extends Effect22.Service()("ArchiveService", {
|
|
|
3117
4012
|
})
|
|
3118
4013
|
});
|
|
3119
4014
|
const fileInfo = yield* fs.stat(outputFile).pipe(
|
|
3120
|
-
|
|
4015
|
+
Effect23.mapError(
|
|
3121
4016
|
(error) => new ArchiveCreateError({
|
|
3122
4017
|
message: `Failed to stat archive: ${String(error)}`
|
|
3123
4018
|
})
|
|
3124
4019
|
)
|
|
3125
4020
|
);
|
|
3126
4021
|
const size = Number(fileInfo.size);
|
|
3127
|
-
yield*
|
|
4022
|
+
yield* Effect23.log("Archive created", { size });
|
|
3128
4023
|
return { path: outputFile, size };
|
|
3129
4024
|
});
|
|
3130
4025
|
return { createZipArchive };
|
|
@@ -3133,32 +4028,32 @@ var ArchiveService = class extends Effect22.Service()("ArchiveService", {
|
|
|
3133
4028
|
};
|
|
3134
4029
|
|
|
3135
4030
|
// src/services/deployment-api.ts
|
|
3136
|
-
import { Effect as
|
|
3137
|
-
var DeploymentApiService = class extends
|
|
4031
|
+
import { Effect as Effect24 } from "effect";
|
|
4032
|
+
var DeploymentApiService = class extends Effect24.Service()("DeploymentApiService", {
|
|
3138
4033
|
accessors: true,
|
|
3139
4034
|
dependencies: [ApiClientService.Default],
|
|
3140
|
-
effect:
|
|
4035
|
+
effect: Effect24.gen(function* () {
|
|
3141
4036
|
const apiClient = yield* ApiClientService;
|
|
3142
4037
|
const getClient = apiClient.getClient.pipe(
|
|
3143
|
-
|
|
4038
|
+
Effect24.mapError((err) => new DeploymentAuthError({ message: `Authentication failed: ${err.message}` }))
|
|
3144
4039
|
);
|
|
3145
|
-
const createDeployment =
|
|
3146
|
-
yield*
|
|
3147
|
-
yield*
|
|
4040
|
+
const createDeployment = Effect24.fn("DeploymentApiService.createDeployment")(function* (applicationId) {
|
|
4041
|
+
yield* Effect24.annotateCurrentSpan("applicationId", applicationId);
|
|
4042
|
+
yield* Effect24.log("Creating deployment", { applicationId });
|
|
3148
4043
|
const client = yield* getClient;
|
|
3149
|
-
const deployment = yield*
|
|
4044
|
+
const deployment = yield* Effect24.tryPromise({
|
|
3150
4045
|
try: () => client.deployments.create({ applicationId }),
|
|
3151
4046
|
catch: (error) => new DeploymentCreateError({
|
|
3152
4047
|
message: `Failed to create deployment: ${error instanceof Error ? error.message : String(error)}`
|
|
3153
4048
|
})
|
|
3154
4049
|
});
|
|
3155
|
-
yield*
|
|
4050
|
+
yield* Effect24.log("Deployment created", { deploymentId: deployment.id });
|
|
3156
4051
|
return deployment.id;
|
|
3157
4052
|
});
|
|
3158
|
-
const startMultipartUpload =
|
|
3159
|
-
yield*
|
|
4053
|
+
const startMultipartUpload = Effect24.fn("DeploymentApiService.startMultipartUpload")(function* (deploymentId) {
|
|
4054
|
+
yield* Effect24.annotateCurrentSpan("deploymentId", deploymentId);
|
|
3160
4055
|
const client = yield* getClient;
|
|
3161
|
-
const result = yield*
|
|
4056
|
+
const result = yield* Effect24.tryPromise({
|
|
3162
4057
|
try: () => client.deployments.startUpload(deploymentId),
|
|
3163
4058
|
catch: (error) => new UploadStartError({
|
|
3164
4059
|
message: `Failed to start multipart upload: ${error instanceof Error ? error.message : String(error)}`
|
|
@@ -3166,9 +4061,9 @@ var DeploymentApiService = class extends Effect23.Service()("DeploymentApiServic
|
|
|
3166
4061
|
});
|
|
3167
4062
|
return result.uploadId;
|
|
3168
4063
|
});
|
|
3169
|
-
const getPresignedUrl =
|
|
4064
|
+
const getPresignedUrl = Effect24.fn("DeploymentApiService.getPresignedUrl")(function* (deploymentId, uploadId, partNumber) {
|
|
3170
4065
|
const client = yield* getClient;
|
|
3171
|
-
const result = yield*
|
|
4066
|
+
const result = yield* Effect24.tryPromise({
|
|
3172
4067
|
try: () => client.deployments.getPresignedUrl(deploymentId, uploadId, partNumber),
|
|
3173
4068
|
catch: (error) => new PresignedUrlError({
|
|
3174
4069
|
message: `Failed to get presigned URL: ${error instanceof Error ? error.message : String(error)}`,
|
|
@@ -3177,11 +4072,11 @@ var DeploymentApiService = class extends Effect23.Service()("DeploymentApiServic
|
|
|
3177
4072
|
});
|
|
3178
4073
|
return result.url;
|
|
3179
4074
|
});
|
|
3180
|
-
const uploadPart =
|
|
3181
|
-
yield*
|
|
3182
|
-
yield*
|
|
4075
|
+
const uploadPart = Effect24.fn("DeploymentApiService.uploadPart")(function* (presignedUrl, data, partNumber) {
|
|
4076
|
+
yield* Effect24.annotateCurrentSpan("partNumber", partNumber);
|
|
4077
|
+
yield* Effect24.annotateCurrentSpan("size", data.length);
|
|
3183
4078
|
const client = yield* getClient;
|
|
3184
|
-
const result = yield*
|
|
4079
|
+
const result = yield* Effect24.tryPromise({
|
|
3185
4080
|
try: () => client.deployments.uploadPart(presignedUrl, data, partNumber),
|
|
3186
4081
|
catch: (error) => new UploadPartError({
|
|
3187
4082
|
message: `Failed to upload part ${partNumber}: ${error instanceof Error ? error.message : String(error)}`,
|
|
@@ -3190,21 +4085,21 @@ var DeploymentApiService = class extends Effect23.Service()("DeploymentApiServic
|
|
|
3190
4085
|
});
|
|
3191
4086
|
return result;
|
|
3192
4087
|
});
|
|
3193
|
-
const completeUpload =
|
|
3194
|
-
yield*
|
|
3195
|
-
yield*
|
|
4088
|
+
const completeUpload = Effect24.fn("DeploymentApiService.completeUpload")(function* (deploymentId, uploadId, parts) {
|
|
4089
|
+
yield* Effect24.annotateCurrentSpan("deploymentId", deploymentId);
|
|
4090
|
+
yield* Effect24.annotateCurrentSpan("partsCount", parts.length);
|
|
3196
4091
|
const client = yield* getClient;
|
|
3197
|
-
yield*
|
|
4092
|
+
yield* Effect24.tryPromise({
|
|
3198
4093
|
try: () => client.deployments.completeUpload(deploymentId, uploadId, parts),
|
|
3199
4094
|
catch: (error) => new UploadCompleteError({
|
|
3200
4095
|
message: `Failed to complete upload: ${error instanceof Error ? error.message : String(error)}`
|
|
3201
4096
|
})
|
|
3202
4097
|
});
|
|
3203
|
-
yield*
|
|
4098
|
+
yield* Effect24.log("Upload completed successfully");
|
|
3204
4099
|
});
|
|
3205
|
-
const getDeploymentStatus =
|
|
4100
|
+
const getDeploymentStatus = Effect24.fn("DeploymentApiService.getDeploymentStatus")(function* (deploymentId) {
|
|
3206
4101
|
const client = yield* getClient;
|
|
3207
|
-
const deployment = yield*
|
|
4102
|
+
const deployment = yield* Effect24.tryPromise({
|
|
3208
4103
|
try: () => client.deployments.get(deploymentId),
|
|
3209
4104
|
catch: (error) => new DeploymentStatusError({
|
|
3210
4105
|
message: `Failed to get deployment status: ${error instanceof Error ? error.message : String(error)}`
|
|
@@ -3212,9 +4107,9 @@ var DeploymentApiService = class extends Effect23.Service()("DeploymentApiServic
|
|
|
3212
4107
|
});
|
|
3213
4108
|
return deployment.status;
|
|
3214
4109
|
});
|
|
3215
|
-
const getLogs =
|
|
4110
|
+
const getLogs = Effect24.fn("DeploymentApiService.getLogs")(function* (deploymentId, options) {
|
|
3216
4111
|
const client = yield* getClient;
|
|
3217
|
-
const result = yield*
|
|
4112
|
+
const result = yield* Effect24.tryPromise({
|
|
3218
4113
|
try: () => client.deployments.getLogs(deploymentId, options),
|
|
3219
4114
|
catch: (error) => new DeploymentLogsFetchError({
|
|
3220
4115
|
message: `Failed to get logs: ${error instanceof Error ? error.message : String(error)}`
|
|
@@ -3222,9 +4117,9 @@ var DeploymentApiService = class extends Effect23.Service()("DeploymentApiServic
|
|
|
3222
4117
|
});
|
|
3223
4118
|
return result;
|
|
3224
4119
|
});
|
|
3225
|
-
const getHostnames =
|
|
4120
|
+
const getHostnames = Effect24.fn("DeploymentApiService.getHostnames")(function* (deploymentId) {
|
|
3226
4121
|
const client = yield* getClient;
|
|
3227
|
-
const result = yield*
|
|
4122
|
+
const result = yield* Effect24.tryPromise({
|
|
3228
4123
|
try: () => client.deployments.getHostnames(deploymentId),
|
|
3229
4124
|
catch: (error) => new DeploymentHostnamesFetchError({
|
|
3230
4125
|
message: `Failed to get hostnames: ${error instanceof Error ? error.message : String(error)}`
|
|
@@ -3247,32 +4142,32 @@ var DeploymentApiService = class extends Effect23.Service()("DeploymentApiServic
|
|
|
3247
4142
|
};
|
|
3248
4143
|
|
|
3249
4144
|
// src/commands/platform/deploy/index.ts
|
|
3250
|
-
var
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
4145
|
+
var appOption7 = Options8.text("app").pipe(
|
|
4146
|
+
Options8.withAlias("a"),
|
|
4147
|
+
Options8.withDescription("Application ID to deploy to (overrides the linked application)"),
|
|
4148
|
+
Options8.optional
|
|
3254
4149
|
);
|
|
3255
|
-
var orgOption6 =
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
4150
|
+
var orgOption6 = Options8.text("org").pipe(
|
|
4151
|
+
Options8.withAlias("o"),
|
|
4152
|
+
Options8.withDescription("Organization ID to use when automatically creating an application"),
|
|
4153
|
+
Options8.optional
|
|
3259
4154
|
);
|
|
3260
|
-
var nameOption2 =
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
4155
|
+
var nameOption2 = Options8.text("name").pipe(
|
|
4156
|
+
Options8.withAlias("n"),
|
|
4157
|
+
Options8.withDescription("Application name to use when automatically creating an application"),
|
|
4158
|
+
Options8.optional
|
|
3264
4159
|
);
|
|
3265
|
-
var createAndLinkDeploymentApplication = (input) =>
|
|
4160
|
+
var createAndLinkDeploymentApplication = (input) => Effect25.gen(function* () {
|
|
3266
4161
|
const projectLink = yield* ProjectLinkService;
|
|
3267
4162
|
const apiClient = yield* ApiClientService;
|
|
3268
4163
|
const { items: organizations } = yield* apiClient.request((client) => client.organizations.list());
|
|
3269
|
-
const availableOrganizations =
|
|
4164
|
+
const availableOrganizations = Option15.match(input.org, {
|
|
3270
4165
|
onNone: () => organizations,
|
|
3271
4166
|
onSome: (organizationId) => organizations.filter((organization2) => organization2.id === organizationId)
|
|
3272
4167
|
});
|
|
3273
4168
|
if (availableOrganizations.length === 0) {
|
|
3274
4169
|
return yield* new NoOrganizationsFoundError({
|
|
3275
|
-
message:
|
|
4170
|
+
message: Option15.match(input.org, {
|
|
3276
4171
|
onNone: () => 'No organization is available for this deployment. Create an organization, then run "gigadrive deploy" again.',
|
|
3277
4172
|
onSome: (organizationId) => `Organization "${organizationId}" was not found among your organizations.`
|
|
3278
4173
|
})
|
|
@@ -3288,7 +4183,7 @@ var createAndLinkDeploymentApplication = (input) => Effect24.gen(function* () {
|
|
|
3288
4183
|
})
|
|
3289
4184
|
);
|
|
3290
4185
|
const inferredName = path.basename(input.cwd) || "Application";
|
|
3291
|
-
const applicationName =
|
|
4186
|
+
const applicationName = Option15.match(input.name, {
|
|
3292
4187
|
onNone: () => inferredName,
|
|
3293
4188
|
onSome: (name) => name
|
|
3294
4189
|
}).trim();
|
|
@@ -3302,37 +4197,37 @@ var createAndLinkDeploymentApplication = (input) => Effect24.gen(function* () {
|
|
|
3302
4197
|
applicationId: application.id,
|
|
3303
4198
|
organizationId: organization.id
|
|
3304
4199
|
});
|
|
3305
|
-
yield*
|
|
4200
|
+
yield* Console13.log(`Created and linked application ${application.name} (${application.id}).`);
|
|
3306
4201
|
return application.id;
|
|
3307
4202
|
});
|
|
3308
|
-
var resolveLinkedDeploymentApplication = (input) =>
|
|
4203
|
+
var resolveLinkedDeploymentApplication = (input) => Effect25.gen(function* () {
|
|
3309
4204
|
const projectLink = yield* ProjectLinkService;
|
|
3310
4205
|
const existingLink = yield* projectLink.load(input.cwd);
|
|
3311
|
-
return yield*
|
|
4206
|
+
return yield* Option15.match(existingLink, {
|
|
3312
4207
|
onNone: () => createAndLinkDeploymentApplication(input),
|
|
3313
|
-
onSome: (link) =>
|
|
4208
|
+
onSome: (link) => Effect25.succeed(link.applicationId)
|
|
3314
4209
|
});
|
|
3315
4210
|
});
|
|
3316
|
-
var resolveDeploymentApplication = (input) =>
|
|
4211
|
+
var resolveDeploymentApplication = (input) => Option15.match(input.app, {
|
|
3317
4212
|
onNone: () => resolveLinkedDeploymentApplication(input),
|
|
3318
|
-
onSome:
|
|
4213
|
+
onSome: Effect25.succeed
|
|
3319
4214
|
});
|
|
3320
|
-
var deployCommand =
|
|
4215
|
+
var deployCommand = Command13.make(
|
|
3321
4216
|
"deploy",
|
|
3322
|
-
{ app:
|
|
3323
|
-
({ app, org, name }) =>
|
|
4217
|
+
{ app: appOption7, org: orgOption6, name: nameOption2 },
|
|
4218
|
+
({ app, org, name }) => Effect25.gen(function* () {
|
|
3324
4219
|
const projectConfig = yield* ProjectConfigService;
|
|
3325
4220
|
const deploymentApi = yield* DeploymentApiService;
|
|
3326
4221
|
const archiveService = yield* ArchiveService;
|
|
3327
|
-
const pathService = yield*
|
|
4222
|
+
const pathService = yield* Path9.Path;
|
|
3328
4223
|
const cwd = process.cwd();
|
|
3329
|
-
yield*
|
|
4224
|
+
yield* Effect25.log("Starting deploy command");
|
|
3330
4225
|
const { config } = yield* projectConfig.resolve(cwd);
|
|
3331
4226
|
const applicationId = yield* resolveDeploymentApplication({ app, org, name, cwd });
|
|
3332
|
-
yield*
|
|
4227
|
+
yield* Console13.log("Creating deployment...");
|
|
3333
4228
|
const deploymentId = yield* deploymentApi.createDeployment(applicationId);
|
|
3334
|
-
yield*
|
|
3335
|
-
yield*
|
|
4229
|
+
yield* Console13.log(`Deployment ID: ${deploymentId}`);
|
|
4230
|
+
yield* Console13.log("Creating archive...");
|
|
3336
4231
|
const archivePath = pathService.join(os4.tmpdir(), `project-${Date.now()}.zip`);
|
|
3337
4232
|
const archive = yield* archiveService.createZipArchive(config.userArchive?.rootOverwrite || cwd, archivePath, {
|
|
3338
4233
|
whitelist: config.userArchive?.fileWhitelist,
|
|
@@ -3342,68 +4237,68 @@ var deployCommand = Command12.make(
|
|
|
3342
4237
|
...config.sidecars ?? []
|
|
3343
4238
|
].some((container) => container.source.type === "dockerfile")
|
|
3344
4239
|
});
|
|
3345
|
-
yield*
|
|
3346
|
-
yield*
|
|
4240
|
+
yield* Console13.log(`Archive created (${formatFileSize(archive.size)})`);
|
|
4241
|
+
yield* Console13.log("Uploading archive...");
|
|
3347
4242
|
yield* uploadArchive(deploymentId, archivePath, archive.size);
|
|
3348
|
-
yield*
|
|
3349
|
-
yield*
|
|
4243
|
+
yield* Console13.log("Upload complete.");
|
|
4244
|
+
yield* Console13.log("The deployment pipeline is now being provisioned. This may take a few seconds.");
|
|
3350
4245
|
yield* pollDeployment(deploymentId);
|
|
3351
4246
|
}).pipe(
|
|
3352
|
-
|
|
3353
|
-
QuitException: () =>
|
|
3354
|
-
NotAuthenticatedError: (err) =>
|
|
3355
|
-
|
|
4247
|
+
Effect25.catchTags({
|
|
4248
|
+
QuitException: () => Console13.log("Cancelled."),
|
|
4249
|
+
NotAuthenticatedError: (err) => Console13.error('You are not logged in. Run "gigadrive login" to authenticate.').pipe(
|
|
4250
|
+
Effect25.andThen(Effect25.fail(err))
|
|
3356
4251
|
),
|
|
3357
|
-
ApiRequestError: (err) =>
|
|
3358
|
-
NoOrganizationsFoundError: (err) =>
|
|
3359
|
-
ProjectLinkReadError: (err) =>
|
|
3360
|
-
ProjectLinkWriteError: (err) =>
|
|
3361
|
-
ConfigNotFoundError: (err) =>
|
|
3362
|
-
ConfigParseError: (err) =>
|
|
3363
|
-
ConfigValidationError: (err) =>
|
|
4252
|
+
ApiRequestError: (err) => Console13.error(`Application setup failed: ${err.message}`).pipe(Effect25.andThen(Effect25.fail(err))),
|
|
4253
|
+
NoOrganizationsFoundError: (err) => Console13.error(err.message).pipe(Effect25.andThen(Effect25.fail(err))),
|
|
4254
|
+
ProjectLinkReadError: (err) => Console13.error(err.message).pipe(Effect25.andThen(Effect25.fail(err))),
|
|
4255
|
+
ProjectLinkWriteError: (err) => Console13.error(err.message).pipe(Effect25.andThen(Effect25.fail(err))),
|
|
4256
|
+
ConfigNotFoundError: (err) => Console13.error(err.message).pipe(Effect25.andThen(Effect25.fail(err))),
|
|
4257
|
+
ConfigParseError: (err) => Console13.error(`Config parse error: ${err.message}`).pipe(Effect25.andThen(Effect25.fail(err))),
|
|
4258
|
+
ConfigValidationError: (err) => Effect25.gen(function* () {
|
|
3364
4259
|
for (const e of err.errors) {
|
|
3365
|
-
yield*
|
|
4260
|
+
yield* Console13.error(e);
|
|
3366
4261
|
}
|
|
3367
|
-
yield*
|
|
3368
|
-
}).pipe(
|
|
3369
|
-
DeploymentCreateError: (err) =>
|
|
3370
|
-
ArchiveCreateError: (err) =>
|
|
3371
|
-
UploadStartError: (err) =>
|
|
3372
|
-
UploadPartError: (err) =>
|
|
3373
|
-
UploadCompleteError: (err) =>
|
|
3374
|
-
PresignedUrlError: (err) =>
|
|
3375
|
-
DeploymentAuthError: (err) =>
|
|
3376
|
-
DeploymentFailedError: (err) =>
|
|
4262
|
+
yield* Console13.error(err.message);
|
|
4263
|
+
}).pipe(Effect25.andThen(Effect25.fail(err))),
|
|
4264
|
+
DeploymentCreateError: (err) => Console13.error(`Deployment creation failed: ${err.message}`).pipe(Effect25.andThen(Effect25.fail(err))),
|
|
4265
|
+
ArchiveCreateError: (err) => Console13.error(`Archive creation failed: ${err.message}`).pipe(Effect25.andThen(Effect25.fail(err))),
|
|
4266
|
+
UploadStartError: (err) => Console13.error(`Upload failed: ${err.message}`).pipe(Effect25.andThen(Effect25.fail(err))),
|
|
4267
|
+
UploadPartError: (err) => Console13.error(`Upload failed: ${err.message}`).pipe(Effect25.andThen(Effect25.fail(err))),
|
|
4268
|
+
UploadCompleteError: (err) => Console13.error(`Upload failed: ${err.message}`).pipe(Effect25.andThen(Effect25.fail(err))),
|
|
4269
|
+
PresignedUrlError: (err) => Console13.error(`Upload failed: ${err.message}`).pipe(Effect25.andThen(Effect25.fail(err))),
|
|
4270
|
+
DeploymentAuthError: (err) => Console13.error(`Authentication failed: ${err.message}`).pipe(Effect25.andThen(Effect25.fail(err))),
|
|
4271
|
+
DeploymentFailedError: (err) => Console13.error(err.message).pipe(Effect25.andThen(Effect25.fail(err)))
|
|
3377
4272
|
})
|
|
3378
4273
|
)
|
|
3379
4274
|
);
|
|
3380
|
-
var uploadArchive = (deploymentId, archivePath, fileSize) =>
|
|
4275
|
+
var uploadArchive = (deploymentId, archivePath, fileSize) => Effect25.gen(function* () {
|
|
3381
4276
|
const deploymentApi = yield* DeploymentApiService;
|
|
3382
|
-
const fs = yield*
|
|
4277
|
+
const fs = yield* FileSystem10.FileSystem;
|
|
3383
4278
|
const uploadId = yield* deploymentApi.startMultipartUpload(deploymentId);
|
|
3384
4279
|
const FILE_CHUNK_SIZE = 10 * 1024 * 1024;
|
|
3385
4280
|
const numChunks = Math.ceil(fileSize / FILE_CHUNK_SIZE);
|
|
3386
|
-
yield*
|
|
3387
|
-
const parts = yield*
|
|
4281
|
+
yield* Effect25.log("Starting multipart upload", { uploadId, numChunks, fileSize });
|
|
4282
|
+
const parts = yield* Effect25.all(
|
|
3388
4283
|
Array.from({ length: numChunks }, (_, i) => {
|
|
3389
4284
|
const partNumber = i + 1;
|
|
3390
4285
|
const start = i * FILE_CHUNK_SIZE;
|
|
3391
4286
|
const bytesToRead = partNumber < numChunks ? FILE_CHUNK_SIZE : fileSize - start;
|
|
3392
|
-
return
|
|
4287
|
+
return Effect25.gen(function* () {
|
|
3393
4288
|
const presignedUrl = yield* deploymentApi.getPresignedUrl(deploymentId, uploadId, partNumber);
|
|
3394
4289
|
const chunkData = yield* Stream.runCollect(fs.stream(archivePath, { offset: start, bytesToRead })).pipe(
|
|
3395
|
-
|
|
4290
|
+
Effect25.map((chunks) => {
|
|
3396
4291
|
const arrays = Array.from(chunks);
|
|
3397
4292
|
return Buffer.concat(arrays);
|
|
3398
4293
|
}),
|
|
3399
|
-
|
|
4294
|
+
Effect25.mapError(
|
|
3400
4295
|
(error) => new UploadPartError({
|
|
3401
4296
|
message: `Failed to read chunk ${partNumber}: ${String(error)}`,
|
|
3402
4297
|
partNumber
|
|
3403
4298
|
})
|
|
3404
4299
|
)
|
|
3405
4300
|
);
|
|
3406
|
-
yield*
|
|
4301
|
+
yield* Effect25.log(`Uploading part ${partNumber}/${numChunks}`, { size: chunkData.length });
|
|
3407
4302
|
return yield* deploymentApi.uploadPart(presignedUrl, chunkData, partNumber);
|
|
3408
4303
|
});
|
|
3409
4304
|
}),
|
|
@@ -3411,20 +4306,20 @@ var uploadArchive = (deploymentId, archivePath, fileSize) => Effect24.gen(functi
|
|
|
3411
4306
|
);
|
|
3412
4307
|
yield* deploymentApi.completeUpload(deploymentId, uploadId, parts);
|
|
3413
4308
|
});
|
|
3414
|
-
var pollDeployment = (deploymentId) =>
|
|
4309
|
+
var pollDeployment = (deploymentId) => Effect25.gen(function* () {
|
|
3415
4310
|
const deploymentApi = yield* DeploymentApiService;
|
|
3416
4311
|
let lastStatus = "PENDING";
|
|
3417
4312
|
let logOffset = 0;
|
|
3418
|
-
yield*
|
|
3419
|
-
yield*
|
|
3420
|
-
|
|
3421
|
-
const newStatus = yield* deploymentApi.getDeploymentStatus(deploymentId).pipe(
|
|
4313
|
+
yield* Console13.log("Status: Pending");
|
|
4314
|
+
yield* Effect25.repeat(
|
|
4315
|
+
Effect25.gen(function* () {
|
|
4316
|
+
const newStatus = yield* deploymentApi.getDeploymentStatus(deploymentId).pipe(Effect25.catchTag("DeploymentStatusError", () => Effect25.succeed(lastStatus)));
|
|
3422
4317
|
if (newStatus !== lastStatus) {
|
|
3423
4318
|
lastStatus = newStatus;
|
|
3424
4319
|
const label = newStatus.charAt(0) + newStatus.slice(1).toLowerCase();
|
|
3425
|
-
yield*
|
|
4320
|
+
yield* Console13.log(`Status: ${label}`);
|
|
3426
4321
|
}
|
|
3427
|
-
const logsPage = yield* deploymentApi.getLogs(deploymentId, { offset: logOffset, limit: 100 }).pipe(
|
|
4322
|
+
const logsPage = yield* deploymentApi.getLogs(deploymentId, { offset: logOffset, limit: 100 }).pipe(Effect25.catchTag("DeploymentLogsFetchError", () => Effect25.succeed(null)));
|
|
3428
4323
|
if (logsPage) {
|
|
3429
4324
|
for (const logEntry of logsPage.items) {
|
|
3430
4325
|
logOffset++;
|
|
@@ -3432,17 +4327,17 @@ var pollDeployment = (deploymentId) => Effect24.gen(function* () {
|
|
|
3432
4327
|
}
|
|
3433
4328
|
}
|
|
3434
4329
|
if (newStatus === "ACTIVE") {
|
|
3435
|
-
const hostnames = yield* deploymentApi.getHostnames(deploymentId).pipe(
|
|
4330
|
+
const hostnames = yield* deploymentApi.getHostnames(deploymentId).pipe(Effect25.catchTag("DeploymentHostnamesFetchError", () => Effect25.succeed([])));
|
|
3436
4331
|
const active = hostnames.find((h) => h.active) ?? hostnames.at(0);
|
|
3437
4332
|
if (active) {
|
|
3438
|
-
yield*
|
|
4333
|
+
yield* Console13.log(`Deployed to https://${active.hostname}`);
|
|
3439
4334
|
} else {
|
|
3440
|
-
yield*
|
|
4335
|
+
yield* Console13.log("Deployment is live.");
|
|
3441
4336
|
}
|
|
3442
|
-
return yield*
|
|
4337
|
+
return yield* Effect25.fail("done");
|
|
3443
4338
|
}
|
|
3444
4339
|
if (newStatus === "FAILED") {
|
|
3445
|
-
return yield*
|
|
4340
|
+
return yield* Effect25.fail(
|
|
3446
4341
|
new DeploymentFailedError({
|
|
3447
4342
|
message: "The deployment failed. Please check the logs for more information."
|
|
3448
4343
|
})
|
|
@@ -3451,67 +4346,67 @@ var pollDeployment = (deploymentId) => Effect24.gen(function* () {
|
|
|
3451
4346
|
}),
|
|
3452
4347
|
Schedule.addDelay(Schedule.forever, () => Duration3.seconds(1))
|
|
3453
4348
|
).pipe(
|
|
3454
|
-
|
|
3455
|
-
if (err === "done") return
|
|
3456
|
-
return
|
|
4349
|
+
Effect25.catchAll((err) => {
|
|
4350
|
+
if (err === "done") return Effect25.void;
|
|
4351
|
+
return Effect25.fail(err);
|
|
3457
4352
|
})
|
|
3458
4353
|
);
|
|
3459
4354
|
});
|
|
3460
4355
|
var printLog = (logEntry) => {
|
|
3461
4356
|
switch (logEntry.type) {
|
|
3462
4357
|
case "ERROR":
|
|
3463
|
-
return
|
|
4358
|
+
return Console13.error(logEntry.message);
|
|
3464
4359
|
case "WARN":
|
|
3465
|
-
return
|
|
4360
|
+
return Console13.warn(logEntry.message);
|
|
3466
4361
|
default:
|
|
3467
|
-
return
|
|
4362
|
+
return Console13.log(logEntry.message);
|
|
3468
4363
|
}
|
|
3469
4364
|
};
|
|
3470
4365
|
|
|
3471
4366
|
// src/commands/platform/index.ts
|
|
3472
|
-
var platformBase =
|
|
3473
|
-
var platformCommand = platformBase.pipe(
|
|
4367
|
+
var platformBase = Command14.make("platform", {}, () => Effect26.void);
|
|
4368
|
+
var platformCommand = platformBase.pipe(Command14.withSubcommands([deployCommand]));
|
|
3474
4369
|
|
|
3475
4370
|
// src/commands/setup/index.ts
|
|
3476
|
-
import { Args as
|
|
3477
|
-
import { Console as
|
|
3478
|
-
var fileArg2 =
|
|
3479
|
-
|
|
3480
|
-
|
|
4371
|
+
import { Args as Args6, Command as Command15, Options as Options9, Prompt as Prompt5 } from "@effect/cli";
|
|
4372
|
+
import { Console as Console14, Effect as Effect27, Option as Option16 } from "effect";
|
|
4373
|
+
var fileArg2 = Args6.text({ name: "file" }).pipe(
|
|
4374
|
+
Args6.withDescription("Output file to write (default: .env.local)"),
|
|
4375
|
+
Args6.optional
|
|
3481
4376
|
);
|
|
3482
|
-
var
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
4377
|
+
var appOption8 = Options9.text("app").pipe(
|
|
4378
|
+
Options9.withAlias("a"),
|
|
4379
|
+
Options9.withDescription("Application ID to set up (skips the interactive picker)"),
|
|
4380
|
+
Options9.optional
|
|
3486
4381
|
);
|
|
3487
|
-
var orgOption7 =
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
|
-
|
|
4382
|
+
var orgOption7 = Options9.text("org").pipe(
|
|
4383
|
+
Options9.withAlias("o"),
|
|
4384
|
+
Options9.withDescription("Only list applications belonging to this organization ID when picking"),
|
|
4385
|
+
Options9.optional
|
|
3491
4386
|
);
|
|
3492
|
-
var environmentOption2 =
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
4387
|
+
var environmentOption2 = Options9.text("environment").pipe(
|
|
4388
|
+
Options9.withAlias("e"),
|
|
4389
|
+
Options9.withDescription("Environment slug to resolve overrides for (e.g. preview)"),
|
|
4390
|
+
Options9.optional
|
|
3496
4391
|
);
|
|
3497
|
-
var yesOption3 =
|
|
3498
|
-
|
|
3499
|
-
|
|
4392
|
+
var yesOption3 = Options9.boolean("yes").pipe(
|
|
4393
|
+
Options9.withAlias("y"),
|
|
4394
|
+
Options9.withDescription("Overwrite an existing file without confirmation")
|
|
3500
4395
|
);
|
|
3501
|
-
var rotateOption2 =
|
|
3502
|
-
|
|
4396
|
+
var rotateOption2 = Options9.boolean("rotate").pipe(
|
|
4397
|
+
Options9.withDescription("Force a fresh API key, revoking the previous one")
|
|
3503
4398
|
);
|
|
3504
|
-
var setupCommand =
|
|
4399
|
+
var setupCommand = Command15.make(
|
|
3505
4400
|
"setup",
|
|
3506
4401
|
{
|
|
3507
4402
|
file: fileArg2,
|
|
3508
|
-
app:
|
|
4403
|
+
app: appOption8,
|
|
3509
4404
|
org: orgOption7,
|
|
3510
4405
|
environment: environmentOption2,
|
|
3511
4406
|
yes: yesOption3,
|
|
3512
4407
|
rotate: rotateOption2
|
|
3513
4408
|
},
|
|
3514
|
-
({ file, app, org, environment, yes, rotate }) =>
|
|
4409
|
+
({ file, app, org, environment, yes, rotate }) => Effect27.gen(function* () {
|
|
3515
4410
|
const apiClient = yield* ApiClientService;
|
|
3516
4411
|
const projectLink = yield* ProjectLinkService;
|
|
3517
4412
|
const envFile = yield* EnvFileService;
|
|
@@ -3519,29 +4414,29 @@ var setupCommand = Command14.make(
|
|
|
3519
4414
|
const cwd = process.cwd();
|
|
3520
4415
|
const existingLink = yield* projectLink.load(cwd);
|
|
3521
4416
|
let applicationId;
|
|
3522
|
-
if (
|
|
4417
|
+
if (Option16.isSome(existingLink)) {
|
|
3523
4418
|
applicationId = existingLink.value.applicationId;
|
|
3524
|
-
if (
|
|
3525
|
-
yield*
|
|
4419
|
+
if (Option16.isSome(app) || Option16.isSome(org)) {
|
|
4420
|
+
yield* Console14.log(
|
|
3526
4421
|
`Note: this directory is already linked to ${applicationId}; ignoring --app/--org. Run "gigadrive unlink" first to target a different application.`
|
|
3527
4422
|
);
|
|
3528
4423
|
}
|
|
3529
4424
|
} else {
|
|
3530
|
-
const organizationId =
|
|
4425
|
+
const organizationId = Option16.getOrUndefined(org);
|
|
3531
4426
|
const { items: apps } = yield* apiClient.request((client) => client.applications.list({ organizationId }));
|
|
3532
4427
|
if (apps.length === 0) {
|
|
3533
|
-
return yield*
|
|
4428
|
+
return yield* Effect27.fail(
|
|
3534
4429
|
new NoApplicationsFoundError({
|
|
3535
4430
|
message: 'No applications found. Create one in the Gigadrive console, then run "gigadrive setup" again.'
|
|
3536
4431
|
})
|
|
3537
4432
|
);
|
|
3538
4433
|
}
|
|
3539
|
-
const explicitAppId =
|
|
4434
|
+
const explicitAppId = Option16.getOrUndefined(app);
|
|
3540
4435
|
let chosen;
|
|
3541
4436
|
if (explicitAppId !== void 0) {
|
|
3542
4437
|
const match = apps.find((a) => a.id === explicitAppId);
|
|
3543
4438
|
if (match === void 0) {
|
|
3544
|
-
return yield*
|
|
4439
|
+
return yield* Effect27.fail(
|
|
3545
4440
|
new ApplicationNotFoundError({
|
|
3546
4441
|
message: `Application "${explicitAppId}" was not found among your applications.`
|
|
3547
4442
|
})
|
|
@@ -3560,83 +4455,83 @@ var setupCommand = Command14.make(
|
|
|
3560
4455
|
}
|
|
3561
4456
|
applicationId = chosen.id;
|
|
3562
4457
|
yield* projectLink.save(cwd, { applicationId, organizationId: chosen.organization.id });
|
|
3563
|
-
yield*
|
|
4458
|
+
yield* Console14.log(`Linked this directory to application ${applicationId}.`);
|
|
3564
4459
|
}
|
|
3565
|
-
const target =
|
|
3566
|
-
const environmentSlug =
|
|
4460
|
+
const target = Option16.getOrElse(file, () => ".env.local");
|
|
4461
|
+
const environmentSlug = Option16.getOrUndefined(environment);
|
|
3567
4462
|
const { items, omittedSensitive } = yield* apiClient.request(
|
|
3568
4463
|
(client) => client.applications.envVars.pull(applicationId, environmentSlug ? { environment: environmentSlug } : void 0)
|
|
3569
4464
|
);
|
|
3570
4465
|
const existing = yield* envFile.read(target);
|
|
3571
|
-
if (
|
|
4466
|
+
if (Option16.isSome(existing) && !yes) {
|
|
3572
4467
|
const overwrite = yield* Prompt5.run(
|
|
3573
4468
|
Prompt5.confirm({ message: `${target} already exists. Overwrite?`, initial: false })
|
|
3574
4469
|
);
|
|
3575
4470
|
if (!overwrite) {
|
|
3576
|
-
return yield*
|
|
4471
|
+
return yield* Console14.log("Cancelled.");
|
|
3577
4472
|
}
|
|
3578
4473
|
}
|
|
3579
4474
|
const credentialEntries2 = yield* localCredentials.provision({
|
|
3580
4475
|
applicationId,
|
|
3581
4476
|
rotate,
|
|
3582
|
-
existingContent:
|
|
4477
|
+
existingContent: Option16.getOrUndefined(existing)
|
|
3583
4478
|
});
|
|
3584
4479
|
const entries = [...items.map((v) => ({ key: v.key, value: v.value })), ...credentialEntries2];
|
|
3585
4480
|
yield* envFile.write(target, entries);
|
|
3586
4481
|
const gitignored = yield* envFile.ensureGitignored(cwd, target);
|
|
3587
|
-
yield*
|
|
3588
|
-
yield*
|
|
4482
|
+
yield* Console14.log("\nLocal dev environment ready.");
|
|
4483
|
+
yield* Console14.log(` \u2022 Wrote ${items.length} variable(s) and API credentials to ${target}.`);
|
|
3589
4484
|
if (omittedSensitive > 0) {
|
|
3590
|
-
yield*
|
|
4485
|
+
yield* Console14.log(
|
|
3591
4486
|
` \u2022 Skipped ${omittedSensitive} sensitive variable(s) (only production/preview hold secrets).`
|
|
3592
4487
|
);
|
|
3593
4488
|
}
|
|
3594
4489
|
if (gitignored) {
|
|
3595
|
-
yield*
|
|
4490
|
+
yield* Console14.log(` \u2022 Added ${target} to .gitignore.`);
|
|
3596
4491
|
}
|
|
3597
|
-
yield*
|
|
4492
|
+
yield* Console14.log(" \u2022 Your app can now call the Gigadrive API via the SDK (GIGADRIVE_CLIENT_ID/SECRET).");
|
|
3598
4493
|
}).pipe(
|
|
3599
|
-
|
|
4494
|
+
Effect27.catchTags({
|
|
3600
4495
|
// Ctrl+C during a prompt is a deliberate cancel — exit 0.
|
|
3601
|
-
QuitException: () =>
|
|
3602
|
-
NotAuthenticatedError: (err) =>
|
|
3603
|
-
|
|
4496
|
+
QuitException: () => Console14.log("Cancelled."),
|
|
4497
|
+
NotAuthenticatedError: (err) => Console14.error('You are not logged in. Run "gigadrive login" to authenticate.').pipe(
|
|
4498
|
+
Effect27.andThen(Effect27.fail(err))
|
|
3604
4499
|
),
|
|
3605
|
-
ApiRequestError: (err) =>
|
|
3606
|
-
NoApplicationsFoundError: (err) =>
|
|
3607
|
-
ApplicationNotFoundError: (err) =>
|
|
3608
|
-
ProjectLinkReadError: (err) =>
|
|
3609
|
-
ProjectLinkWriteError: (err) =>
|
|
3610
|
-
EnvFileReadError: (err) =>
|
|
3611
|
-
EnvFileWriteError: (err) =>
|
|
3612
|
-
DevCredentialsStoreReadError: (err) =>
|
|
3613
|
-
DevCredentialsStoreWriteError: (err) =>
|
|
4500
|
+
ApiRequestError: (err) => Console14.error(`Setup failed: ${err.message}`).pipe(Effect27.andThen(Effect27.fail(err))),
|
|
4501
|
+
NoApplicationsFoundError: (err) => Console14.error(err.message).pipe(Effect27.andThen(Effect27.fail(err))),
|
|
4502
|
+
ApplicationNotFoundError: (err) => Console14.error(err.message).pipe(Effect27.andThen(Effect27.fail(err))),
|
|
4503
|
+
ProjectLinkReadError: (err) => Console14.error(err.message).pipe(Effect27.andThen(Effect27.fail(err))),
|
|
4504
|
+
ProjectLinkWriteError: (err) => Console14.error(err.message).pipe(Effect27.andThen(Effect27.fail(err))),
|
|
4505
|
+
EnvFileReadError: (err) => Console14.error(err.message).pipe(Effect27.andThen(Effect27.fail(err))),
|
|
4506
|
+
EnvFileWriteError: (err) => Console14.error(err.message).pipe(Effect27.andThen(Effect27.fail(err))),
|
|
4507
|
+
DevCredentialsStoreReadError: (err) => Console14.error(err.message).pipe(Effect27.andThen(Effect27.fail(err))),
|
|
4508
|
+
DevCredentialsStoreWriteError: (err) => Console14.error(err.message).pipe(Effect27.andThen(Effect27.fail(err)))
|
|
3614
4509
|
})
|
|
3615
4510
|
)
|
|
3616
4511
|
);
|
|
3617
4512
|
|
|
3618
4513
|
// src/commands/whoami/index.ts
|
|
3619
|
-
import { Command as
|
|
3620
|
-
import { Console as
|
|
3621
|
-
var whoamiCommand =
|
|
4514
|
+
import { Command as Command16 } from "@effect/cli";
|
|
4515
|
+
import { Console as Console15, Effect as Effect28 } from "effect";
|
|
4516
|
+
var whoamiCommand = Command16.make(
|
|
3622
4517
|
"whoami",
|
|
3623
4518
|
{},
|
|
3624
|
-
() =>
|
|
4519
|
+
() => Effect28.gen(function* () {
|
|
3625
4520
|
const auth = yield* AuthService;
|
|
3626
4521
|
const info = yield* auth.getUserInfo;
|
|
3627
|
-
yield*
|
|
4522
|
+
yield* Console15.log(JSON.stringify(info, null, 2));
|
|
3628
4523
|
}).pipe(
|
|
3629
|
-
|
|
3630
|
-
NotAuthenticatedError: () =>
|
|
3631
|
-
UserInfoFetchError: (err) =>
|
|
3632
|
-
OAuthDiscoveryError: (err) =>
|
|
4524
|
+
Effect28.catchTags({
|
|
4525
|
+
NotAuthenticatedError: () => Console15.log('You are not logged in. Run "gigadrive login" to authenticate.'),
|
|
4526
|
+
UserInfoFetchError: (err) => Console15.error(`Failed to retrieve user info: ${err.message}`),
|
|
4527
|
+
OAuthDiscoveryError: (err) => Console15.error(`Failed to retrieve user info: ${err.message}`)
|
|
3633
4528
|
})
|
|
3634
4529
|
)
|
|
3635
4530
|
);
|
|
3636
4531
|
|
|
3637
4532
|
// src/index.ts
|
|
3638
|
-
var gigadrive =
|
|
3639
|
-
|
|
4533
|
+
var gigadrive = Command17.make("gigadrive", {}, () => Effect29.void).pipe(
|
|
4534
|
+
Command17.withSubcommands([
|
|
3640
4535
|
loginCommand,
|
|
3641
4536
|
logoutCommand,
|
|
3642
4537
|
whoamiCommand,
|
|
@@ -3647,6 +4542,8 @@ var gigadrive = Command16.make("gigadrive", {}, () => Effect28.void).pipe(
|
|
|
3647
4542
|
deploymentsCommand,
|
|
3648
4543
|
domainsCommand,
|
|
3649
4544
|
aiCommand,
|
|
4545
|
+
microvmsCommand,
|
|
4546
|
+
vmCommand,
|
|
3650
4547
|
buildCommand,
|
|
3651
4548
|
debugCommand,
|
|
3652
4549
|
deployCommand,
|
|
@@ -3654,7 +4551,7 @@ var gigadrive = Command16.make("gigadrive", {}, () => Effect28.void).pipe(
|
|
|
3654
4551
|
setupCommand
|
|
3655
4552
|
])
|
|
3656
4553
|
);
|
|
3657
|
-
var cli =
|
|
4554
|
+
var cli = Command17.run(gigadrive, {
|
|
3658
4555
|
name: "Gigadrive CLI",
|
|
3659
4556
|
version: package_default.version
|
|
3660
4557
|
});
|
|
@@ -3676,5 +4573,5 @@ var ServicesLive = Layer.mergeAll(BaseServices, ApiClientLive, DeploymentApiLive
|
|
|
3676
4573
|
var AppLive = Layer.mergeAll(ServicesLive, Logger.minimumLogLevel(LogLevel.Info)).pipe(
|
|
3677
4574
|
Layer.provideMerge(NodeContext.layer)
|
|
3678
4575
|
);
|
|
3679
|
-
NodeRuntime.runMain(
|
|
4576
|
+
NodeRuntime.runMain(Effect29.provide(cli(process.argv), AppLive));
|
|
3680
4577
|
//# sourceMappingURL=index.mjs.map
|