// deno-lint-ignore-file no-explicit-any import { GlobalOptions } from "./types.ts"; import { getRootStore } from "./store.ts"; import { loginInteractive, tryGetLoginInfo } from "./login.ts"; import { colors, Command, DelimiterStream, Input, setClient, Table, WorkspaceService, } from "./deps.ts"; import { requireLogin } from "./context.ts"; export type Workspace = { remote: string; workspaceId: string; name: string; token: string; }; function makeWorkspaceStream( readable: ReadableStream, ): ReadableStream { return readable .pipeThrough(new DelimiterStream(new TextEncoder().encode("\n"))) .pipeThrough(new TextDecoderStream()) .pipeThrough( new TransformStream({ transform(line, controller) { try { if (line.length <= 2) { return; } controller.enqueue(JSON.parse(line) as Workspace); } catch { /* ignore */ } }, }), ); } async function allWorkspaces(): Promise { try { const file = await Deno.open((await getRootStore()) + "remotes.ndjson", { write: false, read: true, }); const workspaceStream = makeWorkspaceStream(file.readable); const workspaces: Workspace[] = []; for await (const workspace of workspaceStream) { workspaces.push(workspace); } return workspaces; } catch (_) { return []; } } async function getActiveWorkspaceName( opts: GlobalOptions, ): Promise { if (opts.workspace) { return opts.workspace; } try { return await Deno.readTextFile((await getRootStore()) + "/activeWorkspace"); } catch { return undefined; } } export async function getActiveWorkspace( opts: GlobalOptions, ): Promise { const name = await getActiveWorkspaceName(opts); if (!name) { return undefined; } return await getWorkspaceByName(name); } export async function getWorkspaceByName( workspaceName: string, ): Promise { const file = await Deno.open((await getRootStore()) + "remotes.ndjson"); const workspaceStream = makeWorkspaceStream(file.readable); for await (const workspace of workspaceStream) { if (workspace.name === workspaceName) { return workspace; } } return undefined; } async function list(opts: GlobalOptions) { const workspaces = await allWorkspaces(); const activeName = await getActiveWorkspaceName(opts); new Table() .header(["name", "remote", "workspace id"]) .padding(2) .border(true) .body( workspaces.map((x) => { const a = [x.name, x.remote, x.workspaceId]; if (x.name === activeName) { return a.map((x) => colors.underline(x)); } else { return a; } }), ) .render(); } async function switchC(opts: GlobalOptions, workspaceName: string) { if (opts.workspace) { console.log( colors.red.bold( "! Workspace needs to be specified as positional argument, not as option.", ), ); return; } const all = await allWorkspaces(); if (all.findIndex((x) => x.name === workspaceName) === -1) { console.log(colors.red.bold("! This workspace name does not exist.")); return; } return await Deno.writeTextFile( (await getRootStore()) + "/activeWorkspace", workspaceName, ); } export async function add( opts: GlobalOptions & { create: boolean; createWorkspaceName: string | undefined; createUsername: string; }, workspaceName: string | undefined, workspaceId: string | undefined, remote: string | undefined, ) { if (opts.workspace) { console.log( colors.red.bold( "! Workspace needs to be specified as positional argument, not as option.", ), ); return; } if (!workspaceName) { workspaceName = await Input.prompt("Name this workspace:"); } const all = await allWorkspaces(); if (all.findIndex((x) => x.name === workspaceName) !== -1) { console.log(colors.red.bold("! Workspace name already exists")); return; } if (!workspaceId) { workspaceId = await Input.prompt("Enter the ID of this workspace"); } if (!remote) { // first check whether workspaceId is actually a URL try { const url = new URL(workspaceId); workspaceId = url.username; url.username = ""; remote = url.toString(); } catch { // not a url remote = new URL(await Input.prompt("Enter the Remote URL")).toString(); } } let token = await tryGetLoginInfo(opts); while (!token) { token = await loginInteractive(remote); } if (opts.create) { setClient(token, remote.endsWith('/') ? remote.substring(0, remote.length - 1) : remote); if ( !await WorkspaceService.existsWorkspace({ requestBody: { id: workspaceId }, }) ) { console.log(colors.yellow("Workspace does not exist. Creating...")); await WorkspaceService.createWorkspace({ requestBody: { id: workspaceId, name: opts.createWorkspaceName ?? workspaceId, username: opts.createUsername, }, }); } } await addWorkspace({ name: workspaceName, remote: remote, workspaceId: workspaceId, token: token, }); await Deno.writeTextFile( (await getRootStore()) + "/activeWorkspace", workspaceName, ); console.log(colors.green.underline("Succesfully added workspace!")); } export async function addWorkspace(workspace: Workspace) { const file = await Deno.open((await getRootStore()) + "remotes.ndjson", { append: true, write: true, read: false, create: true, }); await file.write(new TextEncoder().encode(JSON.stringify(workspace) + "\n")); file.close(); } async function remove(_opts: GlobalOptions, name: string) { const orgWorkspaces = await allWorkspaces(); await Deno.writeTextFile( (await getRootStore()) + "remotes.ndjson", orgWorkspaces .filter((x) => x.name !== name) .map((x) => JSON.stringify(x)) .join("\n"), ); console.log(colors.green.underline("Succesfully removed workspace!")); } const command = new Command() .description("workspace related commands") .action(list as any) .command("switch") .description("Switch to another workspace") .arguments("") .action(switchC as any) .command("add") .description("Add a workspace") .arguments("[workspace_name:string] [workspace_id:string] [remote:string]") .option("-c --create", "Create the workspace if it does not exist") .option( "--create-workspace-name ", "Specify the workspace name. Ignored if --create is not specified or the workspace already exists. Will default to the workspace id.", ) .option( "--create-username ", "Specify your own username in the newly created workspace. Ignored if --create is not specified or the workspace already exists.", { default: "admin", }, ) .action(add as any) .command("remove") .description("Remove a workspace") .arguments("") .action(remove as any); export default command;