aboutsummaryrefslogtreecommitdiffstats
path: root/apps
diff options
context:
space:
mode:
Diffstat (limited to 'apps')
-rw-r--r--apps/cli/src/commands/bookmarks.ts104
-rw-r--r--apps/cli/src/commands/lists.ts91
-rw-r--r--apps/cli/src/commands/tags.ts50
-rw-r--r--apps/cli/src/commands/whoami.ts11
-rw-r--r--apps/cli/src/index.ts1
-rw-r--r--apps/cli/src/lib/globals.ts1
-rw-r--r--apps/cli/src/lib/output.ts61
7 files changed, 241 insertions, 78 deletions
diff --git a/apps/cli/src/commands/bookmarks.ts b/apps/cli/src/commands/bookmarks.ts
index 0f557120..40442ec1 100644
--- a/apps/cli/src/commands/bookmarks.ts
+++ b/apps/cli/src/commands/bookmarks.ts
@@ -1,7 +1,12 @@
import * as fs from "node:fs";
+import {
+ printError,
+ printObject,
+ printStatusMessage,
+ printSuccess,
+} from "@/lib/output";
import { getAPIClient } from "@/lib/trpc";
import { Command } from "@commander-js/extra-typings";
-import chalk from "chalk";
import type { ZBookmark } from "@hoarder/shared/types/bookmarks";
import { MAX_NUM_BOOKMARKS_PER_PAGE } from "@hoarder/shared/types/bookmarks";
@@ -30,6 +35,10 @@ function normalizeBookmark(bookmark: ZBookmark) {
return ret;
}
+function printBookmark(bookmark: ZBookmark) {
+ printObject(normalizeBookmark(bookmark));
+}
+
bookmarkCmd
.command("add")
.description("creates a new bookmark")
@@ -49,31 +58,49 @@ bookmarkCmd
.action(async (opts) => {
const api = getAPIClient();
+ const results: object[] = [];
+
const promises = [
...opts.link.map((url) =>
- api.bookmarks.createBookmark.mutate({ type: "link", url }),
+ api.bookmarks.createBookmark
+ .mutate({ type: "link", url })
+ .then((bookmark: ZBookmark) => {
+ results.push(normalizeBookmark(bookmark));
+ })
+ .catch(printError(`Failed to add a link bookmark for url "${url}"`)),
),
...opts.note.map((text) =>
- api.bookmarks.createBookmark.mutate({ type: "text", text }),
+ api.bookmarks.createBookmark
+ .mutate({ type: "text", text })
+ .then((bookmark: ZBookmark) => {
+ results.push(normalizeBookmark(bookmark));
+ })
+ .catch(
+ printError(
+ `Failed to add a text bookmark with text "${text.substring(0, 50)}"`,
+ ),
+ ),
),
];
if (opts.stdin) {
const text = fs.readFileSync(0, "utf-8");
promises.push(
- api.bookmarks.createBookmark.mutate({ type: "text", text }),
+ api.bookmarks.createBookmark
+ .mutate({ type: "text", text })
+ .then((bookmark: ZBookmark) => {
+ results.push(normalizeBookmark(bookmark));
+ })
+ .catch(
+ printError(
+ `Failed to add a text bookmark with text "${text.substring(0, 50)}"`,
+ ),
+ ),
);
}
- 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}`));
- }
- }
+ await Promise.allSettled(promises);
+ printObject(results);
});
bookmarkCmd
@@ -82,8 +109,10 @@ bookmarkCmd
.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));
+ await api.bookmarks.getBookmark
+ .query({ bookmarkId: id })
+ .then(printBookmark)
+ .catch(printError(`Failed to get the bookmark with id "${id}"`));
});
bookmarkCmd
@@ -98,13 +127,15 @@ bookmarkCmd
.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);
+ await api.bookmarks.updateBookmark
+ .mutate({
+ bookmarkId: id,
+ archived: opts.archive,
+ favourited: opts.favourite,
+ title: opts.title,
+ })
+ .then(printObject)
+ .catch(printError(`Failed to update bookmark with id "${id}"`));
});
bookmarkCmd
@@ -126,18 +157,21 @@ bookmarkCmd
useCursorV2: true,
};
- let resp = await api.bookmarks.getBookmarks.query(request);
- let results: ZBookmark[] = resp.bookmarks;
+ try {
+ let resp = await api.bookmarks.getBookmarks.query(request);
+ let results: ZBookmark[] = resp.bookmarks;
- while (resp.nextCursor) {
- resp = await api.bookmarks.getBookmarks.query({
- ...request,
- cursor: resp.nextCursor,
- });
- results = [...results, ...resp.bookmarks];
+ while (resp.nextCursor) {
+ resp = await api.bookmarks.getBookmarks.query({
+ ...request,
+ cursor: resp.nextCursor,
+ });
+ results = [...results, ...resp.bookmarks];
+ }
+ printObject(results.map(normalizeBookmark), { maxArrayLength: null });
+ } catch (e) {
+ printStatusMessage(false, "Failed to query bookmarks");
}
-
- console.dir(results.map(normalizeBookmark), { maxArrayLength: null });
});
bookmarkCmd
@@ -146,6 +180,8 @@ bookmarkCmd
.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`);
+ await api.bookmarks.deleteBookmark
+ .mutate({ bookmarkId: id })
+ .then(printSuccess(`Bookmark with id '${id}' got deleted`))
+ .catch(printError(`Failed to delete bookmark with id "${id}"`));
});
diff --git a/apps/cli/src/commands/lists.ts b/apps/cli/src/commands/lists.ts
index c7b2a5f0..2f85ae7b 100644
--- a/apps/cli/src/commands/lists.ts
+++ b/apps/cli/src/commands/lists.ts
@@ -1,3 +1,10 @@
+import { getGlobalOptions } from "@/lib/globals";
+import {
+ printError,
+ printErrorMessageWithReason,
+ printObject,
+ printSuccess,
+} from "@/lib/output";
import { getAPIClient } from "@/lib/trpc";
import { Command } from "@commander-js/extra-typings";
import { getBorderCharacters, table } from "table";
@@ -14,19 +21,30 @@ listsCmd
.action(async () => {
const api = getAPIClient();
- const resp = await api.lists.list.query();
- const { allPaths } = listsToTree(resp.lists);
+ try {
+ const resp = await api.lists.list.query();
- const data: string[][] = [["Id", "Name"]];
+ if (getGlobalOptions().json) {
+ printObject(resp);
+ } else {
+ 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 }),
- );
+ 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,
+ }),
+ );
+ }
+ } catch (error) {
+ printErrorMessageWithReason("Failed to list all lists", error as object);
+ }
});
listsCmd
@@ -36,10 +54,12 @@ listsCmd
.action(async (id) => {
const api = getAPIClient();
- await api.lists.delete.mutate({
- listId: id,
- });
- console.log("Successfully deleted list with id:", id);
+ await api.lists.delete
+ .mutate({
+ listId: id,
+ })
+ .then(printSuccess(`Successfully deleted list with id "${id}"`))
+ .catch(printError(`Failed to delete list with id "${id}"`));
});
listsCmd
@@ -50,11 +70,21 @@ listsCmd
.action(async (opts) => {
const api = getAPIClient();
- await api.lists.addToList.mutate({
- listId: opts.list,
- bookmarkId: opts.bookmark,
- });
- console.log("Successfully added bookmark from list");
+ await api.lists.addToList
+ .mutate({
+ listId: opts.list,
+ bookmarkId: opts.bookmark,
+ })
+ .then(
+ printSuccess(
+ `Successfully added bookmark "${opts.bookmark}" to list with id "${opts.list}"`,
+ ),
+ )
+ .catch(
+ printError(
+ `Failed to add bookmark "${opts.bookmark}" to list with id "${opts.list}"`,
+ ),
+ );
});
listsCmd
@@ -65,10 +95,19 @@ listsCmd
.action(async (opts) => {
const api = getAPIClient();
- await api.lists.removeFromList.mutate({
- listId: opts.list,
- bookmarkId: opts.bookmark,
- });
-
- console.log("Successfully removed bookmark from list");
+ await api.lists.removeFromList
+ .mutate({
+ listId: opts.list,
+ bookmarkId: opts.bookmark,
+ })
+ .then(
+ printSuccess(
+ `Successfully removed bookmark "${opts.bookmark}" from list with id "${opts.list}"`,
+ ),
+ )
+ .catch(
+ printError(
+ `Failed to remove bookmark "${opts.bookmark}" from list with id "${opts.list}"`,
+ ),
+ );
});
diff --git a/apps/cli/src/commands/tags.ts b/apps/cli/src/commands/tags.ts
index 410f1abd..c2c1dd3a 100644
--- a/apps/cli/src/commands/tags.ts
+++ b/apps/cli/src/commands/tags.ts
@@ -1,3 +1,10 @@
+import { getGlobalOptions } from "@/lib/globals";
+import {
+ printError,
+ printErrorMessageWithReason,
+ printObject,
+ printSuccess,
+} from "@/lib/output";
import { getAPIClient } from "@/lib/trpc";
import { Command } from "@commander-js/extra-typings";
import { getBorderCharacters, table } from "table";
@@ -12,17 +19,27 @@ tagsCmd
.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 }),
- );
+ try {
+ const tags = (await api.tags.list.query()).tags;
+ tags.sort((a, b) => b.count - a.count);
+ if (getGlobalOptions().json) {
+ printObject(tags);
+ } else {
+ 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,
+ }),
+ );
+ }
+ } catch (error) {
+ printErrorMessageWithReason("Failed to list all tags", error as object);
+ }
});
tagsCmd
@@ -32,9 +49,10 @@ tagsCmd
.action(async (id) => {
const api = getAPIClient();
- await api.tags.delete.mutate({
- tagId: id,
- });
-
- console.log("Successfully delete the tag with id:", id);
+ await api.tags.delete
+ .mutate({
+ tagId: id,
+ })
+ .then(printSuccess(`Successfully deleted the tag with the id "${id}"`))
+ .catch(printError(`Failed to delete the tag with the id "${id}"`));
});
diff --git a/apps/cli/src/commands/whoami.ts b/apps/cli/src/commands/whoami.ts
index b55bfa67..06a94e8f 100644
--- a/apps/cli/src/commands/whoami.ts
+++ b/apps/cli/src/commands/whoami.ts
@@ -1,3 +1,4 @@
+import { printError, printObject } from "@/lib/output";
import { getAPIClient } from "@/lib/trpc";
import { Command } from "@commander-js/extra-typings";
@@ -5,6 +6,12 @@ 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);
+ await getAPIClient()
+ .users.whoami.query()
+ .then(printObject)
+ .catch(
+ printError(
+ `Unable to fetch information about the owner of this API key`,
+ ),
+ );
});
diff --git a/apps/cli/src/index.ts b/apps/cli/src/index.ts
index a4f6f7b4..a2a21a75 100644
--- a/apps/cli/src/index.ts
+++ b/apps/cli/src/index.ts
@@ -22,6 +22,7 @@ const program = new Command()
.makeOptionMandatory(true)
.env("HOARDER_SERVER_ADDR"),
)
+ .addOption(new Option("--json", "to output the result as JSON"))
.version(
import.meta.env && "CLI_VERSION" in import.meta.env
? import.meta.env.CLI_VERSION
diff --git a/apps/cli/src/lib/globals.ts b/apps/cli/src/lib/globals.ts
index 771136da..8a301cfe 100644
--- a/apps/cli/src/lib/globals.ts
+++ b/apps/cli/src/lib/globals.ts
@@ -1,6 +1,7 @@
export interface GlobalOptions {
apiKey: string;
serverAddr: string;
+ json?: true;
}
export let globalOpts: GlobalOptions | undefined = undefined;
diff --git a/apps/cli/src/lib/output.ts b/apps/cli/src/lib/output.ts
new file mode 100644
index 00000000..34d86461
--- /dev/null
+++ b/apps/cli/src/lib/output.ts
@@ -0,0 +1,61 @@
+import { InspectOptions } from "util";
+import chalk from "chalk";
+
+import { getGlobalOptions } from "./globals";
+
+/**
+ * Prints an object either in a nicely formatted way or as JSON (depending on the command flag --json)
+ *
+ * @param output
+ */
+export function printObject(
+ output: object,
+ extraOptions?: InspectOptions,
+): void {
+ if (getGlobalOptions().json) {
+ console.log(JSON.stringify(output, undefined, 4));
+ } else {
+ console.dir(output, extraOptions);
+ }
+}
+
+/**
+ * Used to output a status (success/error) and a message either as string or as JSON (depending on the command flag --json)
+ *
+ * @param success if the message is a successful message or an error
+ * @param output the message to output
+ */
+export function printStatusMessage(success: boolean, message: unknown): void {
+ const status = success ? "Success" : "Error";
+ const colorFunction = success ? chalk.green : chalk.red;
+ console.error(colorFunction(`${status}: ${message}`));
+}
+
+/**
+ * @param message The message that will be printed as a successful message
+ * @returns a function that can be used in a Promise on success
+ */
+export function printSuccess(message: string) {
+ return () => {
+ printStatusMessage(true, message);
+ };
+}
+
+/**
+ * @param message The message that will be printed as an error message
+ * @returns a function that can be used in a Promise on rejection
+ */
+export function printError(message: string) {
+ return (error: object) => {
+ printErrorMessageWithReason(message, error);
+ };
+}
+
+/**
+ * @param message The message that will be printed as an error message
+ * @param error an error object with the reason for the error
+ */
+export function printErrorMessageWithReason(message: string, error: object) {
+ const errorMessage = "message" in error ? error.message : error;
+ printStatusMessage(false, `${message}. Reason: ${errorMessage}`);
+}