diff --git a/README.md b/README.md index 8c9826c..1594022 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,7 @@ fluentci # Run the pipeline fluentci --help Usage: fluentci [pipeline] [jobs...] -Version: 0.14.2 +Version: 0.14.3 Description: @@ -120,6 +120,7 @@ Commands: repl [pipelines...] - Start FluentCI REPL studio - Start FluentCI Studio, a web-based user interface project - Manage projects + server - Start FluentCI GraphQL Server ``` ## 📚 Documentation diff --git a/deps.ts b/deps.ts index e304b4d..38f591e 100644 --- a/deps.ts +++ b/deps.ts @@ -74,6 +74,7 @@ export { open } from "https://deno.land/x/open@v0.0.6/index.ts"; import dockernames from "npm:docker-names-ts"; export { dockernames }; export { resolve } from "jsr:@std/path@0.224.0"; +export { sleep } from "jsr:@jotsr/delayed@2.1.1"; export { GitlabCI, Job } from "jsr:@tsirysndr/fluent-gitlab-ci@0.5"; export { Workflow } from "jsr:@tsirysndr/fluent-gh-actions@0.3"; export type { JobSpec } from "jsr:@tsirysndr/fluent-gh-actions@0.3"; diff --git a/main.ts b/main.ts index f3be0ec..3706a6a 100644 --- a/main.ts +++ b/main.ts @@ -17,6 +17,7 @@ import { VERSION } from "./src/consts.ts"; import repl from "./src/cmd/repl.ts"; import studio from "./src/cmd/studio.ts"; import * as projects from "./src/cmd/project.ts"; +import server from "./src/cmd/server.ts"; export async function main() { await new Command() @@ -192,6 +193,11 @@ export async function main() { }) ) .description("Manage projects") + .command("server", "Start FluentCI GraphQL Server") + .option("--port ", "Port to run FluentCI Server") + .action(function (options) { + server(options); + }) .globalOption("--check-update ", "check for update", { default: true, }) diff --git a/src/cmd/server.ts b/src/cmd/server.ts new file mode 100644 index 0000000..67edd3a --- /dev/null +++ b/src/cmd/server.ts @@ -0,0 +1,60 @@ +import { createId, createYoga, cyan, green } from "../../deps.ts"; +import { schema } from "../server/graphql/schema.ts"; +import * as actions from "../server/kv/actions.ts"; +import * as projects from "../server/kv/projects.ts"; +import * as runs from "../server/kv/runs.ts"; + +function server({ port }: { port?: number }) { + const sockets: Record = {}; + + const yoga = createYoga({ + schema, + context: () => { + return { + sockets, + kv: { + actions, + projects, + runs, + }, + }; + }, + }); + + Deno.serve( + { + port: port || 6076, + onListen: () => { + const PORT = port || 6076; + console.log( + `${green("FluentCI API")} is up and running on ${cyan( + `http://localhost:${PORT}/graphql` + )}` + ); + }, + }, + (req) => { + const upgrade = req.headers.get("upgrade") || ""; + if (upgrade.toLowerCase() === "websocket") { + const ws = Deno.upgradeWebSocket(req); + const id = createId(); + sockets[id] = ws.socket; + + sockets[id].onmessage = (e) => { + if (e.data !== "ping") { + console.log("> socket message:", e.data); + } + sockets[id]?.send(new Date().toString()); + }; + sockets[id].onerror = (e) => + console.log("socket errored:", (e as unknown as Error).message); + sockets[id].onclose = () => delete sockets[id]; + + return ws.response; + } + return yoga(req); + } + ); +} + +export default server; diff --git a/src/cmd/studio.ts b/src/cmd/studio.ts index 06a064e..1c477dd 100644 --- a/src/cmd/studio.ts +++ b/src/cmd/studio.ts @@ -6,6 +6,7 @@ import { green, cyan, dockernames, + sleep, } from "../../deps.ts"; import { schema } from "../server/graphql/schema.ts"; import * as actions from "../server/kv/actions.ts"; @@ -76,6 +77,8 @@ async function studio({ port }: { port?: number }) { } }); + await sleep(1000); // wait for the studio to start + Deno.serve( { port: port || 6076, diff --git a/src/consts.ts b/src/consts.ts index 714f23f..22b0cc7 100644 --- a/src/consts.ts +++ b/src/consts.ts @@ -1,6 +1,6 @@ import { dir } from "../deps.ts"; -export const VERSION = "0.14.2"; +export const VERSION = "0.14.3"; export const BASE_URL = "https://api.fluentci.io/v1"; diff --git a/src/utils.ts b/src/utils.ts index f09ec30..8fc367c 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,4 +1,4 @@ -import { dir, brightGreen } from "../deps.ts"; +import { dir, brightGreen, wait } from "../deps.ts"; export async function isLogged(): Promise { if (Deno.env.get("FLUENTCI_ACCESS_TOKEN")) { @@ -181,14 +181,20 @@ async function installDagger() { } export async function setupPkgx() { + await Deno.mkdir(`${Deno.env.get("HOME")}/.local/bin`, { recursive: true }); Deno.env.set( "PATH", `${Deno.env.get("HOME")}/.local/bin:${Deno.env.get("PATH")}` ); + const spinner = wait("Setting up pkgx...").start(); const command = new Deno.Command("bash", { - args: ["-c", `type pkgx >/dev/null 2>&1 || curl -Ssf https://pkgx.sh | sh`], + args: [ + "-c", + `type pkgx >/dev/null 2>&1 || curl -Ssf https://pkgx.sh/$(uname)/$(uname -m).tgz | tar xz -C $HOME/.local/bin`, + ], stdout: "inherit", stderr: "inherit", + stdin: "inherit", }); const process = await command.spawn(); @@ -198,6 +204,8 @@ export async function setupPkgx() { console.log("Failed to install pkgx."); Deno.exit(1); } + + spinner.succeed("Pkgx setup complete"); } export async function setupRust() { @@ -257,10 +265,6 @@ export async function setupFluentCIengine() { FLUENTCI_ENGINE_VERSION = `v${FLUENTCI_ENGINE_VERSION}`; } - Deno.env.set( - "PATH", - `${Deno.env.get("HOME")}/.local/bin:${Deno.env.get("PATH")}` - ); const target = Deno.build.target; const command = new Deno.Command("bash", { args: [ @@ -295,10 +299,6 @@ export async function setupFluentCIStudio() { FLUENTCI_STUDIO_VERSION = `v${FLUENTCI_STUDIO_VERSION}`; } - Deno.env.set( - "PATH", - `${Deno.env.get("HOME")}/.local/bin:${Deno.env.get("PATH")}` - ); const target = Deno.build.target; const command = new Deno.Command("bash", { args: [