From 3352a3ea393849550573deff8d774ba6bf149471 Mon Sep 17 00:00:00 2001 From: MohamedBassem Date: Wed, 24 Apr 2024 11:37:35 +0100 Subject: build(cli): Prepare for publishing CLI to npm --- apps/cli/.gitignore | 1 + apps/cli/.npmignore | 4 ++ apps/cli/commands/bookmarks.ts | 135 ------------------------------------- apps/cli/commands/lists.ts | 74 -------------------- apps/cli/commands/tags.ts | 40 ----------- apps/cli/commands/whoami.ts | 10 --- apps/cli/index.ts | 33 --------- apps/cli/lib/globals.ts | 17 ----- apps/cli/lib/trpc.ts | 23 ------- apps/cli/package.json | 34 +++++++--- apps/cli/src/commands/bookmarks.ts | 135 +++++++++++++++++++++++++++++++++++++ apps/cli/src/commands/lists.ts | 74 ++++++++++++++++++++ apps/cli/src/commands/tags.ts | 40 +++++++++++ apps/cli/src/commands/whoami.ts | 10 +++ apps/cli/src/index.ts | 34 ++++++++++ apps/cli/src/lib/globals.ts | 17 +++++ apps/cli/src/lib/trpc.ts | 22 ++++++ apps/cli/tsconfig.json | 7 +- apps/cli/vite.config.mts | 21 ++++++ 19 files changed, 387 insertions(+), 344 deletions(-) create mode 100644 apps/cli/.gitignore create mode 100644 apps/cli/.npmignore delete mode 100644 apps/cli/commands/bookmarks.ts delete mode 100644 apps/cli/commands/lists.ts delete mode 100644 apps/cli/commands/tags.ts delete mode 100644 apps/cli/commands/whoami.ts delete mode 100644 apps/cli/index.ts delete mode 100644 apps/cli/lib/globals.ts delete mode 100644 apps/cli/lib/trpc.ts create mode 100644 apps/cli/src/commands/bookmarks.ts create mode 100644 apps/cli/src/commands/lists.ts create mode 100644 apps/cli/src/commands/tags.ts create mode 100644 apps/cli/src/commands/whoami.ts create mode 100644 apps/cli/src/index.ts create mode 100644 apps/cli/src/lib/globals.ts create mode 100644 apps/cli/src/lib/trpc.ts create mode 100644 apps/cli/vite.config.mts (limited to 'apps/cli') diff --git a/apps/cli/.gitignore b/apps/cli/.gitignore new file mode 100644 index 00000000..1521c8b7 --- /dev/null +++ b/apps/cli/.gitignore @@ -0,0 +1 @@ +dist diff --git a/apps/cli/.npmignore b/apps/cli/.npmignore new file mode 100644 index 00000000..18a504f5 --- /dev/null +++ b/apps/cli/.npmignore @@ -0,0 +1,4 @@ +.turbo/** +src/** +vite.config.mts +tsconfig.json diff --git a/apps/cli/commands/bookmarks.ts b/apps/cli/commands/bookmarks.ts deleted file mode 100644 index 0da3dd71..00000000 --- a/apps/cli/commands/bookmarks.ts +++ /dev/null @@ -1,135 +0,0 @@ -import * as fs from "node:fs"; -import { Command } from "@commander-js/extra-typings"; -import chalk from "chalk"; -import { getAPIClient } from "lib/trpc"; - -import type { ZBookmark } from "@hoarder/shared/types/bookmarks"; - -export const bookmarkCmd = new Command() - .name("bookmarks") - .description("Manipulating bookmarks"); - -function collect(val: T, acc: T[]) { - acc.push(val); - return acc; -} - -function normalizeBookmark(bookmark: ZBookmark) { - const ret = { - ...bookmark, - tags: bookmark.tags.map((t) => t.name), - }; - - if (ret.content.type == "link" && ret.content.htmlContent) { - if (ret.content.htmlContent.length > 10) { - ret.content.htmlContent = - ret.content.htmlContent.substring(0, 10) + "... "; - } - } - return ret; -} - -bookmarkCmd - .command("add") - .description("Creates a new bookmark") - .option( - "--link ", - "the link to add. Specify multiple times to add multiple links", - collect, - [], - ) - .option( - "--note ", - "the note text to add. Specify multiple times to add multiple notes", - collect, - [], - ) - .option("--stdin", "reads the data from stdin and store it as a note") - .action(async (opts) => { - const api = getAPIClient(); - - const promises = [ - ...opts.link.map((url) => - api.bookmarks.createBookmark.mutate({ type: "link", url }), - ), - ...opts.note.map((text) => - api.bookmarks.createBookmark.mutate({ type: "text", text }), - ), - ]; - - if (opts.stdin) { - const text = fs.readFileSync(0, "utf-8"); - promises.push( - api.bookmarks.createBookmark.mutate({ type: "text", text }), - ); - } - - const results = await Promise.allSettled(promises); - - for (const res of results) { - if (res.status == "fulfilled") { - console.log(normalizeBookmark(res.value)); - } else { - console.log(chalk.red(`Error: ${res.reason}`)); - } - } - }); - -bookmarkCmd - .command("get") - .description("fetch information about a bookmark") - .argument("", "The id of the bookmark to get") - .action(async (id) => { - const api = getAPIClient(); - const resp = await api.bookmarks.getBookmark.query({ bookmarkId: id }); - console.log(normalizeBookmark(resp)); - }); - -bookmarkCmd - .command("update") - .description("Archive a bookmark") - .option("--title ", "If set, the bookmark's title will be updated") - .option("--note <note>", "If set, the bookmark's note will be updated") - .option("--archive", "If set, the bookmark will be archived") - .option("--no-archive", "If set, the bookmark will be unarchived") - .option("--favourite", "If set, the bookmark will be favourited") - .option("--no-favourite", "If set, the bookmark will be unfavourited") - .argument("<id>", "The id of the bookmark to get") - .action(async (id, opts) => { - const api = getAPIClient(); - const resp = await api.bookmarks.updateBookmark.mutate({ - bookmarkId: id, - archived: opts.archive, - favourited: opts.favourite, - title: opts.title, - }); - console.log(resp); - }); - -bookmarkCmd - .command("list") - .description("list all bookmarks") - .option( - "--include-archived", - "If set, archived bookmarks will be fetched as well", - false, - ) - .option("--list-id <id>", "If set, only items from that list will be fetched") - .action(async (opts) => { - const api = getAPIClient(); - const resp = await api.bookmarks.getBookmarks.query({ - archived: opts.includeArchived ? undefined : false, - listId: opts.listId, - }); - console.log(resp.bookmarks.map(normalizeBookmark)); - }); - -bookmarkCmd - .command("delete") - .description("delete a bookmark") - .argument("<id>", "The id of the bookmark to delete") - .action(async (id) => { - const api = getAPIClient(); - await api.bookmarks.deleteBookmark.mutate({ bookmarkId: id }); - console.log(`Bookmark ${id} got deleted`); - }); diff --git a/apps/cli/commands/lists.ts b/apps/cli/commands/lists.ts deleted file mode 100644 index 099b7869..00000000 --- a/apps/cli/commands/lists.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { Command } from "@commander-js/extra-typings"; -import { getAPIClient } from "lib/trpc"; -import { getBorderCharacters, table } from "table"; - -import { listsToTree } from "@hoarder/shared/utils/listUtils"; - -export const listsCmd = new Command() - .name("lists") - .description("Manipulating lists"); - -listsCmd - .command("list") - .description("Lists all lists") - .action(async () => { - const api = getAPIClient(); - - const resp = await api.lists.list.query(); - const { allPaths } = listsToTree(resp.lists); - - const data: string[][] = [["Id", "Name"]]; - - allPaths.forEach((path) => { - const name = path.map((p) => `${p.icon} ${p.name}`).join(" / "); - const id = path[path.length - 1].id; - data.push([id, name]); - }); - console.log( - table(data, { border: getBorderCharacters("ramac"), singleLine: true }), - ); - }); - -listsCmd - .command("delete") - .description("Deletes a list") - .argument("<id>", "The id of the list") - .action(async (id) => { - const api = getAPIClient(); - - await api.lists.delete.mutate({ - listId: id, - }); - console.log("Successfully deleted list with id:", id); - }); - -listsCmd - .command("add-bookmark") - .description("Add a bookmark to list") - .requiredOption("--list <id>", "The id of the list") - .requiredOption("--bookmark <bookmark>", "The id of the bookmark") - .action(async (opts) => { - const api = getAPIClient(); - - await api.lists.addToList.mutate({ - listId: opts.list, - bookmarkId: opts.bookmark, - }); - console.log("Successfully added bookmark from list"); - }); - -listsCmd - .command("remove-bookmark") - .description("Remove a bookmark from list") - .requiredOption("--list <id>", "The id of the list") - .requiredOption("--bookmark <bookmark>", "The id of the bookmark") - .action(async (opts) => { - const api = getAPIClient(); - - await api.lists.removeFromList.mutate({ - listId: opts.list, - bookmarkId: opts.bookmark, - }); - - console.log("Successfully removed bookmark from list"); - }); diff --git a/apps/cli/commands/tags.ts b/apps/cli/commands/tags.ts deleted file mode 100644 index f9df83cd..00000000 --- a/apps/cli/commands/tags.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { Command } from "@commander-js/extra-typings"; -import { getAPIClient } from "lib/trpc"; -import { getBorderCharacters, table } from "table"; - -export const tagsCmd = new Command() - .name("tags") - .description("Manipulating tags"); - -tagsCmd - .command("list") - .description("Lists all tags") - .action(async () => { - const api = getAPIClient(); - - const tags = (await api.tags.list.query()).tags; - tags.sort((a, b) => b.count - a.count); - - const data: string[][] = [["Id", "Name", "Num bookmarks"]]; - - tags.forEach((tag) => { - data.push([tag.id, tag.name, tag.count.toString()]); - }); - console.log( - table(data, { border: getBorderCharacters("ramac"), singleLine: true }), - ); - }); - -tagsCmd - .command("delete") - .description("Delete a tag") - .argument("<id>", "The id of the tag") - .action(async (id) => { - const api = getAPIClient(); - - await api.tags.delete.mutate({ - tagId: id, - }); - - console.log("Successfully delete the tag with id:", id); - }); diff --git a/apps/cli/commands/whoami.ts b/apps/cli/commands/whoami.ts deleted file mode 100644 index 2b32f2f0..00000000 --- a/apps/cli/commands/whoami.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Command } from "@commander-js/extra-typings"; -import { getAPIClient } from "lib/trpc"; - -export const whoamiCmd = new Command() - .name("whoami") - .description("returns info about the owner of this API key") - .action(async () => { - const resp = await getAPIClient().users.whoami.query(); - console.log(resp); - }); diff --git a/apps/cli/index.ts b/apps/cli/index.ts deleted file mode 100644 index 5971b811..00000000 --- a/apps/cli/index.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { Command, Option } from "@commander-js/extra-typings"; -import { bookmarkCmd } from "commands/bookmarks"; -import { listsCmd } from "commands/lists"; -import { tagsCmd } from "commands/tags"; -import { whoamiCmd } from "commands/whoami"; -import { setGlobalOptions } from "lib/globals"; - -const program = new Command() - .name("hoarder-cli") - .description("A CLI interface to interact with the hoarder api") - .addOption( - new Option("--api-key <key>", "The API key to interact with the API") - .makeOptionMandatory(true) - .env("HOARDER_API_KEY"), - ) - .addOption( - new Option( - "--server-addr <addr>", - "The address of the server to connect to", - ) - .makeOptionMandatory(true) - .env("HOARDER_SERVER_ADDR"), - ) - .version(process.env.SERVER_VERSION ?? "nightly"); - -program.addCommand(bookmarkCmd); -program.addCommand(listsCmd); -program.addCommand(tagsCmd); -program.addCommand(whoamiCmd); - -setGlobalOptions(program.opts()); - -program.parse(); diff --git a/apps/cli/lib/globals.ts b/apps/cli/lib/globals.ts deleted file mode 100644 index 771136da..00000000 --- a/apps/cli/lib/globals.ts +++ /dev/null @@ -1,17 +0,0 @@ -export interface GlobalOptions { - apiKey: string; - serverAddr: string; -} - -export let globalOpts: GlobalOptions | undefined = undefined; - -export function setGlobalOptions(opts: GlobalOptions) { - globalOpts = opts; -} - -export function getGlobalOptions() { - if (!globalOpts) { - throw new Error("Global options are not initalized yet"); - } - return globalOpts; -} diff --git a/apps/cli/lib/trpc.ts b/apps/cli/lib/trpc.ts deleted file mode 100644 index 6f0dccfe..00000000 --- a/apps/cli/lib/trpc.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { createTRPCClient, httpBatchLink } from "@trpc/client"; -import superjson from "superjson"; - -import type { AppRouter } from "@hoarder/trpc/routers/_app"; - -import { getGlobalOptions } from "./globals"; - -export function getAPIClient() { - const globals = getGlobalOptions(); - return createTRPCClient<AppRouter>({ - links: [ - httpBatchLink({ - url: `${globals.serverAddr}/api/trpc`, - transformer: superjson, - headers() { - return { - authorization: `Bearer ${globals.apiKey}`, - }; - }, - }), - ], - }); -} diff --git a/apps/cli/package.json b/apps/cli/package.json index 87c02c3d..2878ad96 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -1,9 +1,20 @@ { "$schema": "https://json.schemastore.org/package.json", - "name": "@hoarder/cli", - "version": "0.1.0", - "private": true, - "dependencies": { + "name": "@hoarderapp/cli", + "version": "0.12.1", + "description": "Command Line Interface (CLI) for Hoarder", + "license": "GNU Affero General Public License version 3", + "keywords": [ + "hoarder", + "cli" + ], + "exports": "./dist/index.mjs", + "bin": { + "hoarder": "dist/index.mjs" + }, + "devDependencies": { + "@hoarder/eslint-config": "workspace:^0.2.0", + "@hoarder/prettier-config": "workspace:^0.1.0", "@commander-js/extra-typings": "^12.0.1", "@hoarder/shared": "workspace:^0.1.0", "@hoarder/trpc": "workspace:^0.1.0", @@ -15,18 +26,21 @@ "commander": "^12.0.0", "superjson": "^2.2.1", "table": "^6.8.2", - "tsx": "^4.7.1" - }, - "devDependencies": { - "@hoarder/eslint-config": "workspace:^0.2.0", - "@hoarder/prettier-config": "workspace:^0.1.0" + "tsx": "^4.7.1", + "vite": "^5.1.0" }, "scripts": { - "run": "tsx index.ts", + "build": "vite build", + "run": "tsx src/index.ts", "lint": "eslint .", "format": "prettier . --ignore-path ../../.prettierignore", "typecheck": "tsc --noEmit" }, + "repository": { + "type": "git", + "url": "git+https://github.com/MohamedBassem/hoarder-app.git", + "directory": "apps/cli" + }, "eslintConfig": { "root": true, "extends": [ diff --git a/apps/cli/src/commands/bookmarks.ts b/apps/cli/src/commands/bookmarks.ts new file mode 100644 index 00000000..264ad818 --- /dev/null +++ b/apps/cli/src/commands/bookmarks.ts @@ -0,0 +1,135 @@ +import * as fs from "node:fs"; +import { getAPIClient } from "@/lib/trpc"; +import { Command } from "@commander-js/extra-typings"; +import chalk from "chalk"; + +import type { ZBookmark } from "@hoarder/shared/types/bookmarks"; + +export const bookmarkCmd = new Command() + .name("bookmarks") + .description("Manipulating bookmarks"); + +function collect<T>(val: T, acc: T[]) { + acc.push(val); + return acc; +} + +function normalizeBookmark(bookmark: ZBookmark) { + const ret = { + ...bookmark, + tags: bookmark.tags.map((t) => t.name), + }; + + if (ret.content.type == "link" && ret.content.htmlContent) { + if (ret.content.htmlContent.length > 10) { + ret.content.htmlContent = + ret.content.htmlContent.substring(0, 10) + "... <CROPPED>"; + } + } + return ret; +} + +bookmarkCmd + .command("add") + .description("Creates a new bookmark") + .option( + "--link <link>", + "the link to add. Specify multiple times to add multiple links", + collect<string>, + [], + ) + .option( + "--note <note>", + "the note text to add. Specify multiple times to add multiple notes", + collect<string>, + [], + ) + .option("--stdin", "reads the data from stdin and store it as a note") + .action(async (opts) => { + const api = getAPIClient(); + + const promises = [ + ...opts.link.map((url) => + api.bookmarks.createBookmark.mutate({ type: "link", url }), + ), + ...opts.note.map((text) => + api.bookmarks.createBookmark.mutate({ type: "text", text }), + ), + ]; + + if (opts.stdin) { + const text = fs.readFileSync(0, "utf-8"); + promises.push( + api.bookmarks.createBookmark.mutate({ type: "text", text }), + ); + } + + const results = await Promise.allSettled(promises); + + for (const res of results) { + if (res.status == "fulfilled") { + console.log(normalizeBookmark(res.value)); + } else { + console.log(chalk.red(`Error: ${res.reason}`)); + } + } + }); + +bookmarkCmd + .command("get") + .description("fetch information about a bookmark") + .argument("<id>", "The id of the bookmark to get") + .action(async (id) => { + const api = getAPIClient(); + const resp = await api.bookmarks.getBookmark.query({ bookmarkId: id }); + console.log(normalizeBookmark(resp)); + }); + +bookmarkCmd + .command("update") + .description("Archive a bookmark") + .option("--title <title>", "If set, the bookmark's title will be updated") + .option("--note <note>", "If set, the bookmark's note will be updated") + .option("--archive", "If set, the bookmark will be archived") + .option("--no-archive", "If set, the bookmark will be unarchived") + .option("--favourite", "If set, the bookmark will be favourited") + .option("--no-favourite", "If set, the bookmark will be unfavourited") + .argument("<id>", "The id of the bookmark to get") + .action(async (id, opts) => { + const api = getAPIClient(); + const resp = await api.bookmarks.updateBookmark.mutate({ + bookmarkId: id, + archived: opts.archive, + favourited: opts.favourite, + title: opts.title, + }); + console.log(resp); + }); + +bookmarkCmd + .command("list") + .description("list all bookmarks") + .option( + "--include-archived", + "If set, archived bookmarks will be fetched as well", + false, + ) + .option("--list-id <id>", "If set, only items from that list will be fetched") + .action(async (opts) => { + const api = getAPIClient(); + const resp = await api.bookmarks.getBookmarks.query({ + archived: opts.includeArchived ? undefined : false, + listId: opts.listId, + }); + console.log(resp.bookmarks.map(normalizeBookmark)); + }); + +bookmarkCmd + .command("delete") + .description("delete a bookmark") + .argument("<id>", "The id of the bookmark to delete") + .action(async (id) => { + const api = getAPIClient(); + await api.bookmarks.deleteBookmark.mutate({ bookmarkId: id }); + console.log(`Bookmark ${id} got deleted`); + }); diff --git a/apps/cli/src/commands/lists.ts b/apps/cli/src/commands/lists.ts new file mode 100644 index 00000000..abf6f78c --- /dev/null +++ b/apps/cli/src/commands/lists.ts @@ -0,0 +1,74 @@ +import { getAPIClient } from "@/lib/trpc"; +import { Command } from "@commander-js/extra-typings"; +import { getBorderCharacters, table } from "table"; + +import { listsToTree } from "@hoarder/shared/utils/listUtils"; + +export const listsCmd = new Command() + .name("lists") + .description("Manipulating lists"); + +listsCmd + .command("list") + .description("Lists all lists") + .action(async () => { + const api = getAPIClient(); + + const resp = await api.lists.list.query(); + const { allPaths } = listsToTree(resp.lists); + + const data: string[][] = [["Id", "Name"]]; + + allPaths.forEach((path) => { + const name = path.map((p) => `${p.icon} ${p.name}`).join(" / "); + const id = path[path.length - 1].id; + data.push([id, name]); + }); + console.log( + table(data, { border: getBorderCharacters("ramac"), singleLine: true }), + ); + }); + +listsCmd + .command("delete") + .description("Deletes a list") + .argument("<id>", "The id of the list") + .action(async (id) => { + const api = getAPIClient(); + + await api.lists.delete.mutate({ + listId: id, + }); + console.log("Successfully deleted list with id:", id); + }); + +listsCmd + .command("add-bookmark") + .description("Add a bookmark to list") + .requiredOption("--list <id>", "The id of the list") + .requiredOption("--bookmark <bookmark>", "The id of the bookmark") + .action(async (opts) => { + const api = getAPIClient(); + + await api.lists.addToList.mutate({ + listId: opts.list, + bookmarkId: opts.bookmark, + }); + console.log("Successfully added bookmark from list"); + }); + +listsCmd + .command("remove-bookmark") + .description("Remove a bookmark from list") + .requiredOption("--list <id>", "The id of the list") + .requiredOption("--bookmark <bookmark>", "The id of the bookmark") + .action(async (opts) => { + const api = getAPIClient(); + + await api.lists.removeFromList.mutate({ + listId: opts.list, + bookmarkId: opts.bookmark, + }); + + console.log("Successfully removed bookmark from list"); + }); diff --git a/apps/cli/src/commands/tags.ts b/apps/cli/src/commands/tags.ts new file mode 100644 index 00000000..f74f1df6 --- /dev/null +++ b/apps/cli/src/commands/tags.ts @@ -0,0 +1,40 @@ +import { getAPIClient } from "@/lib/trpc"; +import { Command } from "@commander-js/extra-typings"; +import { getBorderCharacters, table } from "table"; + +export const tagsCmd = new Command() + .name("tags") + .description("Manipulating tags"); + +tagsCmd + .command("list") + .description("Lists all tags") + .action(async () => { + const api = getAPIClient(); + + const tags = (await api.tags.list.query()).tags; + tags.sort((a, b) => b.count - a.count); + + const data: string[][] = [["Id", "Name", "Num bookmarks"]]; + + tags.forEach((tag) => { + data.push([tag.id, tag.name, tag.count.toString()]); + }); + console.log( + table(data, { border: getBorderCharacters("ramac"), singleLine: true }), + ); + }); + +tagsCmd + .command("delete") + .description("Delete a tag") + .argument("<id>", "The id of the tag") + .action(async (id) => { + const api = getAPIClient(); + + await api.tags.delete.mutate({ + tagId: id, + }); + + console.log("Successfully delete the tag with id:", id); + }); diff --git a/apps/cli/src/commands/whoami.ts b/apps/cli/src/commands/whoami.ts new file mode 100644 index 00000000..b55bfa67 --- /dev/null +++ b/apps/cli/src/commands/whoami.ts @@ -0,0 +1,10 @@ +import { getAPIClient } from "@/lib/trpc"; +import { Command } from "@commander-js/extra-typings"; + +export const whoamiCmd = new Command() + .name("whoami") + .description("returns info about the owner of this API key") + .action(async () => { + const resp = await getAPIClient().users.whoami.query(); + console.log(resp); + }); diff --git a/apps/cli/src/index.ts b/apps/cli/src/index.ts new file mode 100644 index 00000000..cdb2956e --- /dev/null +++ b/apps/cli/src/index.ts @@ -0,0 +1,34 @@ +#! /usr/bin/env node +import { bookmarkCmd } from "@/commands/bookmarks"; +import { listsCmd } from "@/commands/lists"; +import { tagsCmd } from "@/commands/tags"; +import { whoamiCmd } from "@/commands/whoami"; +import { setGlobalOptions } from "@/lib/globals"; +import { Command, Option } from "@commander-js/extra-typings"; + +const program = new Command() + .name("hoarder-cli") + .description("A CLI interface to interact with the hoarder api") + .addOption( + new Option("--api-key <key>", "The API key to interact with the API") + .makeOptionMandatory(true) + .env("HOARDER_API_KEY"), + ) + .addOption( + new Option( + "--server-addr <addr>", + "The address of the server to connect to", + ) + .makeOptionMandatory(true) + .env("HOARDER_SERVER_ADDR"), + ) + .version(process.env.SERVER_VERSION ?? "nightly"); + +program.addCommand(bookmarkCmd); +program.addCommand(listsCmd); +program.addCommand(tagsCmd); +program.addCommand(whoamiCmd); + +setGlobalOptions(program.opts()); + +program.parse(); diff --git a/apps/cli/src/lib/globals.ts b/apps/cli/src/lib/globals.ts new file mode 100644 index 00000000..771136da --- /dev/null +++ b/apps/cli/src/lib/globals.ts @@ -0,0 +1,17 @@ +export interface GlobalOptions { + apiKey: string; + serverAddr: string; +} + +export let globalOpts: GlobalOptions | undefined = undefined; + +export function setGlobalOptions(opts: GlobalOptions) { + globalOpts = opts; +} + +export function getGlobalOptions() { + if (!globalOpts) { + throw new Error("Global options are not initalized yet"); + } + return globalOpts; +} diff --git a/apps/cli/src/lib/trpc.ts b/apps/cli/src/lib/trpc.ts new file mode 100644 index 00000000..aa7aec21 --- /dev/null +++ b/apps/cli/src/lib/trpc.ts @@ -0,0 +1,22 @@ +import { getGlobalOptions } from "@/lib/globals"; +import { createTRPCClient, httpBatchLink } from "@trpc/client"; +import superjson from "superjson"; + +import type { AppRouter } from "@hoarder/trpc/routers/_app"; + +export function getAPIClient() { + const globals = getGlobalOptions(); + return createTRPCClient<AppRouter>({ + links: [ + httpBatchLink({ + url: `${globals.serverAddr}/api/trpc`, + transformer: superjson, + headers() { + return { + authorization: `Bearer ${globals.apiKey}`, + }; + }, + }), + ], + }); +} diff --git a/apps/cli/tsconfig.json b/apps/cli/tsconfig.json index dc71844c..43577686 100644 --- a/apps/cli/tsconfig.json +++ b/apps/cli/tsconfig.json @@ -1,11 +1,14 @@ { "$schema": "https://json.schemastore.org/tsconfig", "extends": "@hoarder/tsconfig/node.json", - "include": ["**/*.ts"], - "exclude": ["node_modules"], + "include": ["src", "vite.config.mts"], + "exclude": ["node_modules", "dist"], "compilerOptions": { "baseUrl": ".", "tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json", "strictNullChecks": true, + "paths": { + "@/*": ["./src/*"] + } } } diff --git a/apps/cli/vite.config.mts b/apps/cli/vite.config.mts new file mode 100644 index 00000000..4c18902f --- /dev/null +++ b/apps/cli/vite.config.mts @@ -0,0 +1,21 @@ +// This file is shamelessly copied from immich's CLI vite config +// https://github.com/immich-app/immich/blob/main/cli/vite.config.ts +import { defineConfig } from "vite"; +import tsconfigPaths from "vite-tsconfig-paths"; + +export default defineConfig({ + build: { + rollupOptions: { + input: "src/index.ts", + output: { + dir: "dist", + }, + }, + ssr: true, + }, + ssr: { + // bundle everything except for Node built-ins + noExternal: /^(?!node:).*$/, + }, + plugins: [tsconfigPaths()], +}); -- cgit v1.2.3-70-g09d2