everything-dev 0.0.15 → 0.0.16
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/package.json +1 -1
- package/src/cli.ts +236 -25
- package/src/contract.ts +117 -19
- package/src/lib/nova.ts +2 -2
- package/src/lib/process-registry.ts +157 -0
- package/src/lib/secrets.ts +1 -0
- package/src/lib/sync.ts +134 -0
- package/src/plugin.ts +358 -113
- package/src/types.ts +15 -2
package/package.json
CHANGED
package/src/cli.ts
CHANGED
|
@@ -213,8 +213,77 @@ async function main() {
|
|
|
213
213
|
.description("Run CLI as HTTP server (exposes /api)")
|
|
214
214
|
.option("-p, --port <port>", "Port to run on", "4000")
|
|
215
215
|
.action(async (options) => {
|
|
216
|
-
const
|
|
217
|
-
|
|
216
|
+
const port = parseInt(options.port, 10);
|
|
217
|
+
|
|
218
|
+
const { Hono } = await import("hono");
|
|
219
|
+
const { cors } = await import("hono/cors");
|
|
220
|
+
const { RPCHandler } = await import("@orpc/server/fetch");
|
|
221
|
+
const { OpenAPIHandler } = await import("@orpc/openapi/fetch");
|
|
222
|
+
const { OpenAPIReferencePlugin } = await import("@orpc/openapi/plugins");
|
|
223
|
+
const { ZodToJsonSchemaConverter } = await import("@orpc/zod/zod4");
|
|
224
|
+
const { onError } = await import("every-plugin/orpc");
|
|
225
|
+
const { formatORPCError } = await import("every-plugin/errors");
|
|
226
|
+
|
|
227
|
+
const app = new Hono();
|
|
228
|
+
|
|
229
|
+
app.use("/*", cors({ origin: "*", credentials: true }));
|
|
230
|
+
|
|
231
|
+
const rpcHandler = new RPCHandler(result.router, {
|
|
232
|
+
interceptors: [onError((error: unknown) => formatORPCError(error))],
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
const apiHandler = new OpenAPIHandler(result.router, {
|
|
236
|
+
plugins: [
|
|
237
|
+
new OpenAPIReferencePlugin({
|
|
238
|
+
schemaConverters: [new ZodToJsonSchemaConverter()],
|
|
239
|
+
specGenerateOptions: {
|
|
240
|
+
info: { title: "BOS CLI API", version: "1.0.0" },
|
|
241
|
+
servers: [{ url: `http://localhost:${port}/api` }],
|
|
242
|
+
},
|
|
243
|
+
}),
|
|
244
|
+
],
|
|
245
|
+
interceptors: [onError((error: unknown) => formatORPCError(error))],
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
app.get("/", (c) => c.json({
|
|
249
|
+
ok: true,
|
|
250
|
+
plugin: "bos-cli",
|
|
251
|
+
status: "ready",
|
|
252
|
+
endpoints: {
|
|
253
|
+
health: "/",
|
|
254
|
+
docs: "/api",
|
|
255
|
+
rpc: "/api/rpc"
|
|
256
|
+
}
|
|
257
|
+
}));
|
|
258
|
+
|
|
259
|
+
app.all("/api/rpc/*", async (c) => {
|
|
260
|
+
const rpcResult = await rpcHandler.handle(c.req.raw, {
|
|
261
|
+
prefix: "/api/rpc",
|
|
262
|
+
context: {},
|
|
263
|
+
});
|
|
264
|
+
return rpcResult.response
|
|
265
|
+
? new Response(rpcResult.response.body, rpcResult.response)
|
|
266
|
+
: c.text("Not Found", 404);
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
app.all("/api", async (c) => {
|
|
270
|
+
const apiResult = await apiHandler.handle(c.req.raw, {
|
|
271
|
+
prefix: "/api",
|
|
272
|
+
context: {},
|
|
273
|
+
});
|
|
274
|
+
return apiResult.response
|
|
275
|
+
? new Response(apiResult.response.body, apiResult.response)
|
|
276
|
+
: c.text("Not Found", 404);
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
app.all("/api/*", async (c) => {
|
|
280
|
+
const apiResult = await apiHandler.handle(c.req.raw, {
|
|
281
|
+
prefix: "/api",
|
|
282
|
+
context: {},
|
|
283
|
+
});
|
|
284
|
+
return apiResult.response
|
|
285
|
+
? new Response(apiResult.response.body, apiResult.response)
|
|
286
|
+
: c.text("Not Found", 404);
|
|
218
287
|
});
|
|
219
288
|
|
|
220
289
|
console.log();
|
|
@@ -222,10 +291,27 @@ async function main() {
|
|
|
222
291
|
console.log(` ${icons.run} ${gradients.cyber("CLI SERVER")}`);
|
|
223
292
|
console.log(colors.cyan(frames.bottom(48)));
|
|
224
293
|
console.log();
|
|
225
|
-
console.log(` ${colors.dim("URL:")} ${colors.white(
|
|
226
|
-
console.log(` ${colors.dim("RPC:")} ${colors.white(
|
|
227
|
-
console.log(` ${colors.dim("Docs:")} ${colors.white(
|
|
294
|
+
console.log(` ${colors.dim("URL:")} ${colors.white(`http://localhost:${port}`)}`);
|
|
295
|
+
console.log(` ${colors.dim("RPC:")} ${colors.white(`http://localhost:${port}/api/rpc`)}`);
|
|
296
|
+
console.log(` ${colors.dim("Docs:")} ${colors.white(`http://localhost:${port}/api`)}`);
|
|
228
297
|
console.log();
|
|
298
|
+
|
|
299
|
+
const server = Bun.serve({
|
|
300
|
+
port,
|
|
301
|
+
fetch: app.fetch,
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
const shutdown = () => {
|
|
305
|
+
console.log();
|
|
306
|
+
console.log(colors.dim(" Shutting down..."));
|
|
307
|
+
server.stop();
|
|
308
|
+
process.exit(0);
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
process.on("SIGINT", shutdown);
|
|
312
|
+
process.on("SIGTERM", shutdown);
|
|
313
|
+
|
|
314
|
+
await new Promise(() => {});
|
|
229
315
|
});
|
|
230
316
|
|
|
231
317
|
program
|
|
@@ -659,8 +745,9 @@ Zephyr Configuration:
|
|
|
659
745
|
.description("Sync dependencies and config from everything-dev CLI")
|
|
660
746
|
.option("--account <account>", "NEAR account to sync from (default: every.near)")
|
|
661
747
|
.option("--gateway <gateway>", "Gateway domain to sync from (default: everything.dev)")
|
|
748
|
+
.option("--network <network>", "Network: mainnet | testnet", "mainnet")
|
|
662
749
|
.option("--force", "Force sync even if versions match")
|
|
663
|
-
.action(async (options: { account?: string; gateway?: string; force?: boolean }) => {
|
|
750
|
+
.action(async (options: { account?: string; gateway?: string; network?: string; force?: boolean }) => {
|
|
664
751
|
console.log();
|
|
665
752
|
const source = options.account || options.gateway
|
|
666
753
|
? `${options.account || "every.near"}/${options.gateway || "everything.dev"}`
|
|
@@ -670,6 +757,7 @@ Zephyr Configuration:
|
|
|
670
757
|
const result = await client.sync({
|
|
671
758
|
account: options.account,
|
|
672
759
|
gateway: options.gateway,
|
|
760
|
+
network: (options.network as "mainnet" | "testnet") || "mainnet",
|
|
673
761
|
force: options.force || false,
|
|
674
762
|
});
|
|
675
763
|
|
|
@@ -746,38 +834,161 @@ Zephyr Configuration:
|
|
|
746
834
|
console.log();
|
|
747
835
|
});
|
|
748
836
|
|
|
749
|
-
|
|
750
|
-
.command("
|
|
751
|
-
.description("
|
|
752
|
-
.
|
|
753
|
-
.
|
|
754
|
-
|
|
837
|
+
program
|
|
838
|
+
.command("kill")
|
|
839
|
+
.description("Kill all tracked BOS processes")
|
|
840
|
+
.option("--force", "Force kill with SIGKILL immediately")
|
|
841
|
+
.action(async (options: { force?: boolean }) => {
|
|
842
|
+
const result = await client.kill({ force: options.force ?? false });
|
|
843
|
+
|
|
755
844
|
console.log();
|
|
756
|
-
|
|
845
|
+
if (result.status === "error") {
|
|
846
|
+
console.error(colors.error(`${icons.err} ${result.error || "Failed to kill processes"}`));
|
|
847
|
+
process.exit(1);
|
|
848
|
+
}
|
|
757
849
|
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
850
|
+
if (result.killed.length > 0) {
|
|
851
|
+
console.log(colors.green(`${icons.ok} Killed ${result.killed.length} processes`));
|
|
852
|
+
for (const pid of result.killed) {
|
|
853
|
+
console.log(colors.dim(` PID ${pid}`));
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
if (result.failed.length > 0) {
|
|
857
|
+
console.log(colors.error(`${icons.err} Failed to kill ${result.failed.length} processes`));
|
|
858
|
+
for (const pid of result.failed) {
|
|
859
|
+
console.log(colors.dim(` PID ${pid}`));
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
if (result.killed.length === 0 && result.failed.length === 0) {
|
|
863
|
+
console.log(colors.dim(" No tracked processes found"));
|
|
864
|
+
}
|
|
865
|
+
console.log();
|
|
866
|
+
});
|
|
867
|
+
|
|
868
|
+
program
|
|
869
|
+
.command("ps")
|
|
870
|
+
.description("List tracked BOS processes")
|
|
871
|
+
.action(async () => {
|
|
872
|
+
const result = await client.ps({});
|
|
873
|
+
|
|
874
|
+
console.log();
|
|
875
|
+
console.log(colors.cyan(frames.top(52)));
|
|
876
|
+
console.log(` ${icons.run} ${gradients.cyber("PROCESSES")}`);
|
|
877
|
+
console.log(colors.cyan(frames.bottom(52)));
|
|
878
|
+
console.log();
|
|
879
|
+
|
|
880
|
+
if (result.processes.length === 0) {
|
|
881
|
+
console.log(colors.dim(" No tracked processes"));
|
|
882
|
+
} else {
|
|
883
|
+
for (const proc of result.processes) {
|
|
884
|
+
const age = Math.round((Date.now() - proc.startedAt) / 1000);
|
|
885
|
+
console.log(` ${colors.white(proc.name)} ${colors.dim(`(PID ${proc.pid})`)}`);
|
|
886
|
+
console.log(` ${colors.dim("Port:")} ${colors.cyan(String(proc.port))}`);
|
|
887
|
+
console.log(` ${colors.dim("Age:")} ${colors.cyan(`${age}s`)}`);
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
console.log();
|
|
891
|
+
});
|
|
892
|
+
|
|
893
|
+
const docker = program
|
|
894
|
+
.command("docker")
|
|
895
|
+
.description("Docker container management");
|
|
896
|
+
|
|
897
|
+
docker
|
|
898
|
+
.command("build")
|
|
899
|
+
.description("Build Docker image")
|
|
900
|
+
.option("-t, --target <target>", "Build target: production | development", "production")
|
|
901
|
+
.option("--tag <tag>", "Custom image tag")
|
|
902
|
+
.option("--no-cache", "Build without cache")
|
|
903
|
+
.action(async (options: { target: string; tag?: string; noCache?: boolean }) => {
|
|
904
|
+
console.log();
|
|
905
|
+
console.log(` ${icons.pkg} Building Docker image (${options.target})...`);
|
|
906
|
+
|
|
907
|
+
const result = await client.dockerBuild({
|
|
908
|
+
target: options.target as "production" | "development",
|
|
909
|
+
tag: options.tag,
|
|
910
|
+
noCache: options.noCache ?? false,
|
|
761
911
|
});
|
|
762
912
|
|
|
763
913
|
if (result.status === "error") {
|
|
764
|
-
console.error(colors.error(`${icons.err} ${result.error || "
|
|
914
|
+
console.error(colors.error(`${icons.err} ${result.error || "Build failed"}`));
|
|
765
915
|
process.exit(1);
|
|
766
916
|
}
|
|
767
917
|
|
|
768
918
|
console.log();
|
|
769
|
-
console.log(colors.green(`${icons.ok}
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
919
|
+
console.log(colors.green(`${icons.ok} Built ${result.tag}`));
|
|
920
|
+
console.log();
|
|
921
|
+
});
|
|
922
|
+
|
|
923
|
+
docker
|
|
924
|
+
.command("run")
|
|
925
|
+
.description("Run Docker container")
|
|
926
|
+
.option("-t, --target <target>", "Image target: production | development", "production")
|
|
927
|
+
.option("-m, --mode <mode>", "Run mode: start | serve | dev", "start")
|
|
928
|
+
.option("-p, --port <port>", "Port to expose")
|
|
929
|
+
.option("-d, --detach", "Run in background")
|
|
930
|
+
.option("-e, --env <env...>", "Environment variables (KEY=value)")
|
|
931
|
+
.action(async (options: { target: string; mode: string; port?: string; detach?: boolean; env?: string[] }) => {
|
|
932
|
+
console.log();
|
|
933
|
+
console.log(` ${icons.run} Starting Docker container...`);
|
|
934
|
+
|
|
935
|
+
const envVars: Record<string, string> = {};
|
|
936
|
+
if (options.env) {
|
|
937
|
+
for (const e of options.env) {
|
|
938
|
+
const [key, ...rest] = e.split("=");
|
|
939
|
+
if (key) envVars[key] = rest.join("=");
|
|
774
940
|
}
|
|
775
941
|
}
|
|
776
942
|
|
|
777
|
-
|
|
778
|
-
|
|
943
|
+
const result = await client.dockerRun({
|
|
944
|
+
target: options.target as "production" | "development",
|
|
945
|
+
mode: options.mode as "start" | "serve" | "dev",
|
|
946
|
+
port: options.port ? parseInt(options.port, 10) : undefined,
|
|
947
|
+
detach: options.detach ?? false,
|
|
948
|
+
env: Object.keys(envVars).length > 0 ? envVars : undefined,
|
|
949
|
+
});
|
|
950
|
+
|
|
951
|
+
if (result.status === "error") {
|
|
952
|
+
console.error(colors.error(`${icons.err} ${result.error || "Run failed"}`));
|
|
953
|
+
process.exit(1);
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
console.log();
|
|
957
|
+
console.log(colors.green(`${icons.ok} Container running`));
|
|
958
|
+
console.log(` ${colors.dim("URL:")} ${colors.cyan(result.url)}`);
|
|
959
|
+
if (result.containerId !== "attached") {
|
|
960
|
+
console.log(` ${colors.dim("Container:")} ${colors.cyan(result.containerId)}`);
|
|
961
|
+
}
|
|
962
|
+
console.log();
|
|
963
|
+
});
|
|
964
|
+
|
|
965
|
+
docker
|
|
966
|
+
.command("stop")
|
|
967
|
+
.description("Stop Docker container(s)")
|
|
968
|
+
.option("-c, --container <id>", "Container ID to stop")
|
|
969
|
+
.option("-a, --all", "Stop all containers for this app")
|
|
970
|
+
.action(async (options: { container?: string; all?: boolean }) => {
|
|
971
|
+
console.log();
|
|
972
|
+
console.log(` ${icons.pkg} Stopping containers...`);
|
|
973
|
+
|
|
974
|
+
const result = await client.dockerStop({
|
|
975
|
+
containerId: options.container,
|
|
976
|
+
all: options.all ?? false,
|
|
977
|
+
});
|
|
978
|
+
|
|
979
|
+
if (result.status === "error") {
|
|
980
|
+
console.error(colors.error(`${icons.err} ${result.error || "Stop failed"}`));
|
|
981
|
+
process.exit(1);
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
console.log();
|
|
985
|
+
if (result.stopped.length > 0) {
|
|
986
|
+
console.log(colors.green(`${icons.ok} Stopped ${result.stopped.length} container(s)`));
|
|
987
|
+
for (const id of result.stopped) {
|
|
988
|
+
console.log(colors.dim(` ${id}`));
|
|
989
|
+
}
|
|
779
990
|
} else {
|
|
780
|
-
console.log(colors.dim("
|
|
991
|
+
console.log(colors.dim(" No containers stopped"));
|
|
781
992
|
}
|
|
782
993
|
console.log();
|
|
783
994
|
});
|
package/src/contract.ts
CHANGED
|
@@ -2,8 +2,6 @@ import { oc } from "every-plugin/orpc";
|
|
|
2
2
|
import { z } from "every-plugin/zod";
|
|
3
3
|
import {
|
|
4
4
|
BosConfigSchema,
|
|
5
|
-
HostConfigSchema,
|
|
6
|
-
RemoteConfigSchema,
|
|
7
5
|
SourceModeSchema,
|
|
8
6
|
} from "./types";
|
|
9
7
|
|
|
@@ -27,6 +25,7 @@ const StartOptionsSchema = z.object({
|
|
|
27
25
|
interactive: z.boolean().optional(),
|
|
28
26
|
account: z.string().optional(),
|
|
29
27
|
domain: z.string().optional(),
|
|
28
|
+
network: z.enum(["mainnet", "testnet"]).default("mainnet"),
|
|
30
29
|
});
|
|
31
30
|
|
|
32
31
|
const StartResultSchema = z.object({
|
|
@@ -215,7 +214,9 @@ const GatewaySyncResultSchema = z.object({
|
|
|
215
214
|
const SyncOptionsSchema = z.object({
|
|
216
215
|
account: z.string().optional(),
|
|
217
216
|
gateway: z.string().optional(),
|
|
217
|
+
network: z.enum(["mainnet", "testnet"]).default("mainnet"),
|
|
218
218
|
force: z.boolean().optional(),
|
|
219
|
+
files: z.boolean().optional(),
|
|
219
220
|
});
|
|
220
221
|
|
|
221
222
|
const SyncResultSchema = z.object({
|
|
@@ -225,6 +226,90 @@ const SyncResultSchema = z.object({
|
|
|
225
226
|
hostUrl: z.string(),
|
|
226
227
|
catalogUpdated: z.boolean(),
|
|
227
228
|
packagesUpdated: z.array(z.string()),
|
|
229
|
+
filesSynced: z.array(z.object({
|
|
230
|
+
package: z.string(),
|
|
231
|
+
files: z.array(z.string()),
|
|
232
|
+
})).optional(),
|
|
233
|
+
error: z.string().optional(),
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
const FilesSyncOptionsSchema = z.object({
|
|
237
|
+
packages: z.array(z.string()).optional(),
|
|
238
|
+
force: z.boolean().optional(),
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
const FilesSyncResultSchema = z.object({
|
|
242
|
+
status: z.enum(["synced", "error"]),
|
|
243
|
+
synced: z.array(z.object({
|
|
244
|
+
package: z.string(),
|
|
245
|
+
files: z.array(z.string()),
|
|
246
|
+
depsAdded: z.array(z.string()).optional(),
|
|
247
|
+
depsUpdated: z.array(z.string()).optional(),
|
|
248
|
+
})),
|
|
249
|
+
error: z.string().optional(),
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
const KillOptionsSchema = z.object({
|
|
253
|
+
force: z.boolean().default(false),
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
const KillResultSchema = z.object({
|
|
257
|
+
status: z.enum(["killed", "error"]),
|
|
258
|
+
killed: z.array(z.number()),
|
|
259
|
+
failed: z.array(z.number()),
|
|
260
|
+
error: z.string().optional(),
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
const ProcessStatusSchema = z.object({
|
|
264
|
+
pid: z.number(),
|
|
265
|
+
name: z.string(),
|
|
266
|
+
port: z.number(),
|
|
267
|
+
startedAt: z.number(),
|
|
268
|
+
command: z.string(),
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
const PsResultSchema = z.object({
|
|
272
|
+
status: z.enum(["listed", "error"]),
|
|
273
|
+
processes: z.array(ProcessStatusSchema),
|
|
274
|
+
error: z.string().optional(),
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
const DockerBuildOptionsSchema = z.object({
|
|
278
|
+
target: z.enum(["production", "development"]).default("production"),
|
|
279
|
+
tag: z.string().optional(),
|
|
280
|
+
noCache: z.boolean().default(false),
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
const DockerBuildResultSchema = z.object({
|
|
284
|
+
status: z.enum(["built", "error"]),
|
|
285
|
+
image: z.string(),
|
|
286
|
+
tag: z.string(),
|
|
287
|
+
error: z.string().optional(),
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
const DockerRunOptionsSchema = z.object({
|
|
291
|
+
target: z.enum(["production", "development"]).default("production"),
|
|
292
|
+
mode: z.enum(["start", "serve", "dev"]).default("start"),
|
|
293
|
+
port: z.number().optional(),
|
|
294
|
+
detach: z.boolean().default(false),
|
|
295
|
+
env: z.record(z.string(), z.string()).optional(),
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
const DockerRunResultSchema = z.object({
|
|
299
|
+
status: z.enum(["running", "error"]),
|
|
300
|
+
containerId: z.string(),
|
|
301
|
+
url: z.string(),
|
|
302
|
+
error: z.string().optional(),
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
const DockerStopOptionsSchema = z.object({
|
|
306
|
+
containerId: z.string().optional(),
|
|
307
|
+
all: z.boolean().default(false),
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
const DockerStopResultSchema = z.object({
|
|
311
|
+
status: z.enum(["stopped", "error"]),
|
|
312
|
+
stopped: z.array(z.string()),
|
|
228
313
|
error: z.string().optional(),
|
|
229
314
|
});
|
|
230
315
|
|
|
@@ -244,17 +329,6 @@ const DepsUpdateResultSchema = z.object({
|
|
|
244
329
|
error: z.string().optional(),
|
|
245
330
|
});
|
|
246
331
|
|
|
247
|
-
const DepsSyncOptionsSchema = z.object({
|
|
248
|
-
category: z.enum(["ui", "api"]).default("ui"),
|
|
249
|
-
install: z.boolean().default(true),
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
const DepsSyncResultSchema = z.object({
|
|
253
|
-
status: z.enum(["synced", "error"]),
|
|
254
|
-
synced: z.array(z.string()),
|
|
255
|
-
error: z.string().optional(),
|
|
256
|
-
});
|
|
257
|
-
|
|
258
332
|
export const bosContract = oc.router({
|
|
259
333
|
dev: oc
|
|
260
334
|
.route({ method: "POST", path: "/dev" })
|
|
@@ -357,10 +431,34 @@ export const bosContract = oc.router({
|
|
|
357
431
|
.input(DepsUpdateOptionsSchema)
|
|
358
432
|
.output(DepsUpdateResultSchema),
|
|
359
433
|
|
|
360
|
-
|
|
361
|
-
.route({ method: "POST", path: "/
|
|
362
|
-
.input(
|
|
363
|
-
.output(
|
|
434
|
+
filesSync: oc
|
|
435
|
+
.route({ method: "POST", path: "/files/sync" })
|
|
436
|
+
.input(FilesSyncOptionsSchema)
|
|
437
|
+
.output(FilesSyncResultSchema),
|
|
438
|
+
|
|
439
|
+
kill: oc
|
|
440
|
+
.route({ method: "POST", path: "/kill" })
|
|
441
|
+
.input(KillOptionsSchema)
|
|
442
|
+
.output(KillResultSchema),
|
|
443
|
+
|
|
444
|
+
ps: oc
|
|
445
|
+
.route({ method: "GET", path: "/ps" })
|
|
446
|
+
.output(PsResultSchema),
|
|
447
|
+
|
|
448
|
+
dockerBuild: oc
|
|
449
|
+
.route({ method: "POST", path: "/docker/build" })
|
|
450
|
+
.input(DockerBuildOptionsSchema)
|
|
451
|
+
.output(DockerBuildResultSchema),
|
|
452
|
+
|
|
453
|
+
dockerRun: oc
|
|
454
|
+
.route({ method: "POST", path: "/docker/run" })
|
|
455
|
+
.input(DockerRunOptionsSchema)
|
|
456
|
+
.output(DockerRunResultSchema),
|
|
457
|
+
|
|
458
|
+
dockerStop: oc
|
|
459
|
+
.route({ method: "POST", path: "/docker/stop" })
|
|
460
|
+
.input(DockerStopOptionsSchema)
|
|
461
|
+
.output(DockerStopResultSchema),
|
|
364
462
|
});
|
|
365
463
|
|
|
366
464
|
export type BosContract = typeof bosContract;
|
|
@@ -397,5 +495,5 @@ export type SyncOptions = z.infer<typeof SyncOptionsSchema>;
|
|
|
397
495
|
export type SyncResult = z.infer<typeof SyncResultSchema>;
|
|
398
496
|
export type DepsUpdateOptions = z.infer<typeof DepsUpdateOptionsSchema>;
|
|
399
497
|
export type DepsUpdateResult = z.infer<typeof DepsUpdateResultSchema>;
|
|
400
|
-
export type
|
|
401
|
-
export type
|
|
498
|
+
export type FilesSyncOptions = z.infer<typeof FilesSyncOptionsSchema>;
|
|
499
|
+
export type FilesSyncResult = z.infer<typeof FilesSyncResultSchema>;
|
package/src/lib/nova.ts
CHANGED
|
@@ -35,7 +35,7 @@ export const getNovaConfig = Effect.gen(function* () {
|
|
|
35
35
|
|
|
36
36
|
export function createNovaClient(config: NovaConfig): NovaSdk {
|
|
37
37
|
return new NovaSdk(config.accountId, {
|
|
38
|
-
|
|
38
|
+
apiKey: config.sessionToken,
|
|
39
39
|
});
|
|
40
40
|
}
|
|
41
41
|
|
|
@@ -240,7 +240,7 @@ export const removeNovaCredentials = Effect.gen(function* () {
|
|
|
240
240
|
|
|
241
241
|
export const verifyNovaCredentials = (accountId: string, sessionToken: string) =>
|
|
242
242
|
Effect.gen(function* () {
|
|
243
|
-
const nova = new NovaSdk(accountId, { sessionToken });
|
|
243
|
+
const nova = new NovaSdk(accountId, { apiKey: sessionToken });
|
|
244
244
|
|
|
245
245
|
yield* Effect.tryPromise({
|
|
246
246
|
try: () => nova.getBalance(),
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { Context, Effect, Layer, Ref } from "every-plugin/effect";
|
|
5
|
+
import { getConfigDir } from "../config";
|
|
6
|
+
|
|
7
|
+
const getPidFilePath = () => join(getConfigDir(), ".bos", "pids.json");
|
|
8
|
+
|
|
9
|
+
export interface TrackedProcess {
|
|
10
|
+
pid: number;
|
|
11
|
+
name: string;
|
|
12
|
+
port: number;
|
|
13
|
+
startedAt: number;
|
|
14
|
+
command: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface ProcessRegistry {
|
|
18
|
+
readonly tracked: Ref.Ref<Map<number, TrackedProcess>>;
|
|
19
|
+
track: (proc: TrackedProcess) => Effect.Effect<void>;
|
|
20
|
+
untrack: (pid: number) => Effect.Effect<void>;
|
|
21
|
+
getAll: () => Effect.Effect<TrackedProcess[]>;
|
|
22
|
+
killAll: (force?: boolean) => Effect.Effect<{ killed: number[]; failed: number[] }>;
|
|
23
|
+
persist: () => Effect.Effect<void>;
|
|
24
|
+
restore: () => Effect.Effect<void>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const isProcessAlive = (pid: number): boolean => {
|
|
28
|
+
try {
|
|
29
|
+
process.kill(pid, 0);
|
|
30
|
+
return true;
|
|
31
|
+
} catch {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const killProcess = (pid: number, signal: NodeJS.Signals): boolean => {
|
|
37
|
+
try {
|
|
38
|
+
process.kill(pid, signal);
|
|
39
|
+
return true;
|
|
40
|
+
} catch {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const make = Effect.gen(function* () {
|
|
46
|
+
const tracked = yield* Ref.make(new Map<number, TrackedProcess>());
|
|
47
|
+
const pidFile = getPidFilePath();
|
|
48
|
+
|
|
49
|
+
const track: ProcessRegistry["track"] = (proc) =>
|
|
50
|
+
Effect.gen(function* () {
|
|
51
|
+
yield* Ref.update(tracked, (m) => new Map(m).set(proc.pid, proc));
|
|
52
|
+
yield* persist();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const untrack: ProcessRegistry["untrack"] = (pid) =>
|
|
56
|
+
Effect.gen(function* () {
|
|
57
|
+
yield* Ref.update(tracked, (m) => {
|
|
58
|
+
const copy = new Map(m);
|
|
59
|
+
copy.delete(pid);
|
|
60
|
+
return copy;
|
|
61
|
+
});
|
|
62
|
+
yield* persist();
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const getAll: ProcessRegistry["getAll"] = () =>
|
|
66
|
+
Ref.get(tracked).pipe(Effect.map((m) => Array.from(m.values())));
|
|
67
|
+
|
|
68
|
+
const killAll: ProcessRegistry["killAll"] = (force = false) =>
|
|
69
|
+
Effect.gen(function* () {
|
|
70
|
+
const procs = yield* getAll();
|
|
71
|
+
const killed: number[] = [];
|
|
72
|
+
const failed: number[] = [];
|
|
73
|
+
|
|
74
|
+
for (const proc of procs) {
|
|
75
|
+
if (!isProcessAlive(proc.pid)) {
|
|
76
|
+
yield* untrack(proc.pid);
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const signal = force ? "SIGKILL" : "SIGTERM";
|
|
81
|
+
if (killProcess(proc.pid, signal)) {
|
|
82
|
+
killed.push(proc.pid);
|
|
83
|
+
yield* untrack(proc.pid);
|
|
84
|
+
} else {
|
|
85
|
+
failed.push(proc.pid);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (!force && failed.length > 0) {
|
|
90
|
+
yield* Effect.sleep("500 millis");
|
|
91
|
+
for (const pid of [...failed]) {
|
|
92
|
+
if (killProcess(pid, "SIGKILL")) {
|
|
93
|
+
const idx = failed.indexOf(pid);
|
|
94
|
+
if (idx !== -1) {
|
|
95
|
+
failed.splice(idx, 1);
|
|
96
|
+
killed.push(pid);
|
|
97
|
+
}
|
|
98
|
+
yield* untrack(pid);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
yield* persist();
|
|
104
|
+
return { killed, failed };
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
const persist: ProcessRegistry["persist"] = () =>
|
|
108
|
+
Effect.gen(function* () {
|
|
109
|
+
const procs = yield* getAll();
|
|
110
|
+
const dir = dirname(pidFile);
|
|
111
|
+
|
|
112
|
+
yield* Effect.tryPromise({
|
|
113
|
+
try: async () => {
|
|
114
|
+
if (!existsSync(dir)) {
|
|
115
|
+
await mkdir(dir, { recursive: true });
|
|
116
|
+
}
|
|
117
|
+
await writeFile(pidFile, JSON.stringify(procs, null, 2));
|
|
118
|
+
},
|
|
119
|
+
catch: () => new Error("Failed to persist PIDs"),
|
|
120
|
+
}).pipe(Effect.catchAll(() => Effect.void));
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
const restore: ProcessRegistry["restore"] = () =>
|
|
124
|
+
Effect.gen(function* () {
|
|
125
|
+
if (!existsSync(pidFile)) return;
|
|
126
|
+
|
|
127
|
+
const content = yield* Effect.tryPromise({
|
|
128
|
+
try: () => readFile(pidFile, "utf8"),
|
|
129
|
+
catch: () => new Error("Failed to read PID file"),
|
|
130
|
+
}).pipe(Effect.catchAll(() => Effect.succeed("")));
|
|
131
|
+
|
|
132
|
+
if (!content) return;
|
|
133
|
+
|
|
134
|
+
let procs: TrackedProcess[];
|
|
135
|
+
try {
|
|
136
|
+
procs = JSON.parse(content) as TrackedProcess[];
|
|
137
|
+
} catch {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const alive = procs.filter((p) => isProcessAlive(p.pid));
|
|
142
|
+
yield* Ref.set(tracked, new Map(alive.map((p) => [p.pid, p])));
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
yield* restore();
|
|
146
|
+
|
|
147
|
+
return { tracked, track, untrack, getAll, killAll, persist, restore };
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
export class ProcessRegistryService extends Context.Tag("bos/ProcessRegistry")<
|
|
151
|
+
ProcessRegistryService,
|
|
152
|
+
ProcessRegistry
|
|
153
|
+
>() {
|
|
154
|
+
static Live = Layer.effect(ProcessRegistryService, make);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export const createProcessRegistry = (): Effect.Effect<ProcessRegistry> => make;
|
package/src/lib/secrets.ts
CHANGED