Loading CHANGES.md +7 −0 Original line number Diff line number Diff line Loading @@ -97,10 +97,15 @@ the versioning. - Added `@fedify/nestjs` package. - Added `FedifyModule` for integrating Fedify into NestJS applications. - Added `-o`/`--output` option to `fedify lookup` command. This option allows users to save retrieved lookup results to specified path. [[#261], [#321] by Jiwon Kwon] [#168]: https://github.com/fedify-dev/fedify/issues/168 [#197]: https://github.com/fedify-dev/fedify/issues/197 [#248]: https://github.com/fedify-dev/fedify/issues/248 [#260]: https://github.com/fedify-dev/fedify/issues/260 [#261]: https://github.com/fedify-dev/fedify/issues/261 [#262]: https://github.com/fedify-dev/fedify/issues/262 [#263]: https://github.com/fedify-dev/fedify/issues/263 [#269]: https://github.com/fedify-dev/fedify/issues/269 Loading @@ -113,6 +118,8 @@ the versioning. [#300]: https://github.com/fedify-dev/fedify/pull/300 [#304]: https://github.com/fedify-dev/fedify/issues/304 [#309]: https://github.com/fedify-dev/fedify/pull/309 [#321]: https://github.com/fedify-dev/fedify/pull/321 Version 1.7.6 Loading cli/lookup.test.ts 0 → 100644 +301 −0 Original line number Diff line number Diff line import { Activity, Note } from "@fedify/fedify"; import { assertEquals, assertExists } from "@std/assert"; import { getContextLoader } from "./docloader.ts"; import { createFileStream, writeObjectToStream } from "./lookup.ts"; Deno.test("createFileStream - creates file stream with proper directory creation", async () => { const testDir = "./test_output"; const testFile = `${testDir}/test.json`; try { await Deno.remove(testDir, { recursive: true }); } catch { // Ignore if doesn't exist } const stream = await createFileStream(testFile); assertExists(stream); const stat = await Deno.stat(testDir); assertEquals(stat.isDirectory, true); stream.close(); await Deno.remove(testDir, { recursive: true }); }); Deno.test("createFileStream - works with absolute paths", async () => { const testDir = `${Deno.cwd()}/test_output_absolute`; const testFile = `${testDir}/test.json`; try { await Deno.remove(testDir, { recursive: true }); } catch { // Ignore if doesn't exist } const stream = await createFileStream(testFile); assertExists(stream); const stat = await Deno.stat(testDir); assertEquals(stat.isDirectory, true); stream.close(); await Deno.remove(testDir, { recursive: true }); }); Deno.test("createFileStream - creates nested directories", async () => { const testDir = "./test_output_nested/deep/path"; const testFile = `${testDir}/test.json`; try { await Deno.remove("./test_output_nested", { recursive: true }); } catch { // Ignore if doesn't exist } const stream = await createFileStream(testFile); assertExists(stream); // Verify nested directories were created const stat = await Deno.stat(testDir); assertEquals(stat.isDirectory, true); stream.close(); await Deno.remove("./test_output_nested", { recursive: true }); }); Deno.test("createFileStream - writes data correctly", async () => { const testDir = "./test_output_write"; const testFile = `${testDir}/test.txt`; try { await Deno.remove(testDir, { recursive: true }); } catch { // Ignore if doesn't exist } const stream = await createFileStream(testFile); const writer = stream.getWriter(); const testData = new TextEncoder().encode("Hello, World!"); await writer.write(testData); await writer.close(); const content = await Deno.readTextFile(testFile); assertEquals(content, "Hello, World!"); await Deno.remove(testDir, { recursive: true }); }); Deno.test("createFileStream - truncates existing file", async () => { const testDir = "./test_output_truncate"; const testFile = `${testDir}/test.txt`; try { await Deno.remove(testDir, { recursive: true }); } catch { // Ignore if doesn't exist } await Deno.mkdir(testDir, { recursive: true }); await Deno.writeTextFile(testFile, "Old content"); const stream = await createFileStream(testFile); const writer = stream.getWriter(); const testData = new TextEncoder().encode("New content"); await writer.write(testData); await writer.close(); // Verify file was truncated and new content written const content = await Deno.readTextFile(testFile); assertEquals(content, "New content"); await Deno.remove(testDir, { recursive: true }); }); Deno.test("writeObjectToStream - writes Note object with default options", { sanitizeResources: false, }, async () => { const testDir = "./test_output_note"; const testFile = `${testDir}/note.txt`; try { await Deno.remove(testDir, { recursive: true }); } catch { // Ignore if doesn't exist } const note = new Note({ id: new URL("https://example.com/notes/1"), content: "Hello, fediverse!", }); const options = { firstKnock: "rfc9421" as const, separator: "----", output: testFile, }; const contextLoader = await getContextLoader({}); await writeObjectToStream(note, options, contextLoader); const content = await Deno.readTextFile(testFile); assertExists(content); assertEquals(content.includes("Hello, fediverse!"), true); assertEquals(content.includes("Note"), true); await Deno.remove(testDir, { recursive: true }); }); Deno.test("writeObjectToStream - writes Activity object in raw JSON-LD format", async () => { const testDir = "./test_output_activity"; const testFile = `${testDir}/activity.json`; try { await Deno.remove(testDir, { recursive: true }); } catch { // Ignore if doesn't exist } const activity = new Activity({ id: new URL("https://example.com/activities/1"), }); const options = { firstKnock: "rfc9421" as const, separator: "----", output: testFile, raw: true, }; const contextLoader = await getContextLoader({}); await writeObjectToStream(activity, options, contextLoader); // Verify file exists and contains JSON-LD const content = await Deno.readTextFile(testFile); assertExists(content); assertEquals(content.includes("@context"), true); assertEquals(content.includes("id"), true); await Deno.remove(testDir, { recursive: true }); }); Deno.test("writeObjectToStream - writes object in compact JSON-LD format", async () => { const testDir = "./test_output_compact"; const testFile = `${testDir}/compact.json`; try { await Deno.remove(testDir, { recursive: true }); } catch { // Ignore if doesn't exist } const note = new Note({ id: new URL("https://example.com/notes/1"), content: "Test note", }); const options = { firstKnock: "rfc9421" as const, separator: "----", output: testFile, compact: true, }; const contextLoader = await getContextLoader({}); await writeObjectToStream(note, options, contextLoader); // Verify file exists and contains compacted JSON-LD const content = await Deno.readTextFile(testFile); assertExists(content); assertEquals(content.includes("Test note"), true); await Deno.remove(testDir, { recursive: true }); }); Deno.test("writeObjectToStream - writes object in expanded JSON-LD format", async () => { const testDir = "./test_output_expand"; const testFile = `${testDir}/expand.json`; try { await Deno.remove(testDir, { recursive: true }); } catch { // Ignore if doesn't exist } const note = new Note({ id: new URL("https://example.com/notes/1"), content: "Test note for expansion", }); const options = { firstKnock: "rfc9421" as const, separator: "----", output: testFile, expand: true, }; const contextLoader = await getContextLoader({}); await writeObjectToStream(note, options, contextLoader); const content = await Deno.readTextFile(testFile); assertExists(content); assertEquals(content.includes("Test note for expansion"), true); await Deno.remove(testDir, { recursive: true }); }); Deno.test("writeObjectToStream - writes to stdout when no output file specified", async () => { const note = new Note({ id: new URL("https://example.com/notes/1"), content: "Test stdout note", }); const options = { firstKnock: "rfc9421" as const, separator: "----", }; const contextLoader = await getContextLoader({}); await writeObjectToStream(note, options, contextLoader); }); Deno.test("writeObjectToStream - handles empty content properly", async () => { const testDir = "./test_output_empty"; const testFile = `${testDir}/empty.txt`; try { await Deno.remove(testDir, { recursive: true }); } catch { // Ignore if doesn't exist } const note = new Note({ id: new URL("https://example.com/notes/1"), }); const options = { firstKnock: "rfc9421" as const, separator: "----", output: testFile, }; const contextLoader = await getContextLoader({}); await writeObjectToStream(note, options, contextLoader); const content = await Deno.readTextFile(testFile); assertExists(content); assertEquals(content.includes("Note"), true); await Deno.remove(testDir, { recursive: true }); }); cli/lookup.ts +115 −20 Original line number Diff line number Diff line Loading @@ -15,15 +15,114 @@ import { traverseCollection, } from "@fedify/fedify"; import { getLogger } from "@logtape/logtape"; import { dirname, isAbsolute, resolve } from "@std/path"; import ora from "ora"; import { getContextLoader, getDocumentLoader } from "./docloader.ts"; import { spawnTemporaryServer, type TemporaryServer } from "./tempserver.ts"; import { printJson } from "./utils.ts"; const logger = getLogger(["fedify", "cli", "lookup"]); const sigSpec = new EnumType(["draft-cavage-http-signatures-12", "rfc9421"]); interface CommandOptions { authorizedFetch?: boolean; firstKnock: "draft-cavage-http-signatures-12" | "rfc9421"; traverse?: boolean; suppressErrors?: boolean; raw?: boolean; compact?: boolean; expand?: boolean; userAgent?: string; separator: string; output?: string; } export async function createFileStream( outputPath: string, ): Promise<WritableStream> { try { const filepath = isAbsolute(outputPath) ? outputPath : resolve(Deno.env.get("PWD") || Deno.cwd(), outputPath); const parentDir = dirname(filepath); await Deno.mkdir(parentDir, { recursive: true }); const file = await Deno.open(filepath, { write: true, create: true, truncate: true, }); return new WritableStream({ write: (chunk) => file.write(chunk).then(() => {}), close: () => file.close(), abort: (reason) => { file.close(); throw reason; }, }); } catch (err) { const spinner = ora({ text: `Failed to write output to ${colors.red(outputPath)}.`, discardStdin: false, }); spinner.fail(); console.error(`Error: ${String(err)}`); if (err instanceof Deno.errors.PermissionDenied) { console.error( "Permission denied. Try running with proper permissions.", ); } else if (err instanceof Deno.errors.NotFound) { console.error("Path does not exist or is invalid."); } else if (err instanceof Deno.errors.IsADirectory) { console.error("The specified path is a directory, not a file."); } Deno.exit(1); } } export async function writeObjectToStream( object: Object | Link, options: CommandOptions, contextLoader: DocumentLoader, ): Promise<void> { const stream = options.output ? await createFileStream(options.output) : Deno.stdout.writable; const writer = stream.getWriter(); try { let content; if (options.raw) { content = await object.toJsonLd({ contextLoader }); } else if (options.compact) { content = await object.toJsonLd({ format: "compact", contextLoader }); } else if (options.expand) { content = await object.toJsonLd({ format: "expand", contextLoader }); } else { content = object; } content = Deno.inspect(content, { colors: !(options.output), }); const encoder = new TextEncoder(); const bytes = encoder.encode(content + "\n"); await writer.write(bytes); } finally { writer.releaseLock(); if (options.output) { await stream.close(); } } } export const command = new Command() .type("sig-spec", sigSpec) .arguments("<...urls:string>") Loading Loading @@ -65,6 +164,10 @@ export const command = new Command() "collection items.", { default: "----" }, ) .option( "-o, --output <file>", "Specify the output file path.", ) .action(async (options, ...urls: string[]) => { if (urls.length < 1) { console.error("At least one URL or actor handle must be provided."); Loading @@ -75,6 +178,7 @@ export const command = new Command() ); Deno.exit(1); } const spinner = ora({ text: `Looking up the ${ options.traverse ? "collection" : urls.length > 1 ? "objects" : "object" Loading Loading @@ -143,26 +247,11 @@ export const command = new Command() }, ); } spinner.text = `Looking up the ${ options.traverse ? "collection" : urls.length > 1 ? "objects" : "object" }...`; async function printObject(object: Object | Link): Promise<void> { if (options.raw) { printJson(await object.toJsonLd({ contextLoader })); } else if (options.compact) { printJson( await object.toJsonLd({ format: "compact", contextLoader }), ); } else if (options.expand) { printJson( await object.toJsonLd({ format: "expand", contextLoader }), ); } else { console.log(object); } } if (options.traverse) { const url = urls[0]; const collection = await lookupObject(url, { Loading Loading @@ -198,8 +287,8 @@ export const command = new Command() suppressError: options.suppressErrors, }) ) { if (i > 0) console.log(options.separator); printObject(item); if (!options.output && i > 0) console.log(options.separator); await writeObjectToStream(item, options, contextLoader); i++; } } catch (error) { Loading @@ -218,6 +307,7 @@ export const command = new Command() Deno.exit(1); } spinner.succeed("Successfully fetched all items in the collection."); await server?.close(); Deno.exit(0); } Loading Loading @@ -254,7 +344,7 @@ export const command = new Command() success = false; } else { spinner.succeed(`Fetched object: ${colors.green(url)}.`); printObject(object); await writeObjectToStream(object, options, contextLoader); if (i < urls.length - 1) { console.log(options.separator); } Loading @@ -274,6 +364,11 @@ export const command = new Command() if (!success) { Deno.exit(1); } if (success && options.output) { spinner.succeed( `Successfully wrote output to ${colors.green(options.output)}.`, ); } }); // cSpell: ignore sigspec docs/cli.md +12 −0 Original line number Diff line number Diff line Loading @@ -811,6 +811,18 @@ It does not affect the output when looking up a single object. > The separator is also used when looking up a collection object with the > [`-t`/`--traverse`](#t-traverse-traverse-the-collection) option. ### `-o`/`--output`: Output file path *This option is available since Fedify 1.8.0.* You can specify the output file path to save lookup results, instead of printing results to stdout. For example, to save the retrieved information about the specified objects to a given path, run the command below: ~~~~ sh fedify lookup -o actors.json @fedify@hollo.social @hongminhee@fosstodon.org ~~~~ `fedify inbox`: Ephemeral inbox server -------------------------------------- Loading Loading
CHANGES.md +7 −0 Original line number Diff line number Diff line Loading @@ -97,10 +97,15 @@ the versioning. - Added `@fedify/nestjs` package. - Added `FedifyModule` for integrating Fedify into NestJS applications. - Added `-o`/`--output` option to `fedify lookup` command. This option allows users to save retrieved lookup results to specified path. [[#261], [#321] by Jiwon Kwon] [#168]: https://github.com/fedify-dev/fedify/issues/168 [#197]: https://github.com/fedify-dev/fedify/issues/197 [#248]: https://github.com/fedify-dev/fedify/issues/248 [#260]: https://github.com/fedify-dev/fedify/issues/260 [#261]: https://github.com/fedify-dev/fedify/issues/261 [#262]: https://github.com/fedify-dev/fedify/issues/262 [#263]: https://github.com/fedify-dev/fedify/issues/263 [#269]: https://github.com/fedify-dev/fedify/issues/269 Loading @@ -113,6 +118,8 @@ the versioning. [#300]: https://github.com/fedify-dev/fedify/pull/300 [#304]: https://github.com/fedify-dev/fedify/issues/304 [#309]: https://github.com/fedify-dev/fedify/pull/309 [#321]: https://github.com/fedify-dev/fedify/pull/321 Version 1.7.6 Loading
cli/lookup.test.ts 0 → 100644 +301 −0 Original line number Diff line number Diff line import { Activity, Note } from "@fedify/fedify"; import { assertEquals, assertExists } from "@std/assert"; import { getContextLoader } from "./docloader.ts"; import { createFileStream, writeObjectToStream } from "./lookup.ts"; Deno.test("createFileStream - creates file stream with proper directory creation", async () => { const testDir = "./test_output"; const testFile = `${testDir}/test.json`; try { await Deno.remove(testDir, { recursive: true }); } catch { // Ignore if doesn't exist } const stream = await createFileStream(testFile); assertExists(stream); const stat = await Deno.stat(testDir); assertEquals(stat.isDirectory, true); stream.close(); await Deno.remove(testDir, { recursive: true }); }); Deno.test("createFileStream - works with absolute paths", async () => { const testDir = `${Deno.cwd()}/test_output_absolute`; const testFile = `${testDir}/test.json`; try { await Deno.remove(testDir, { recursive: true }); } catch { // Ignore if doesn't exist } const stream = await createFileStream(testFile); assertExists(stream); const stat = await Deno.stat(testDir); assertEquals(stat.isDirectory, true); stream.close(); await Deno.remove(testDir, { recursive: true }); }); Deno.test("createFileStream - creates nested directories", async () => { const testDir = "./test_output_nested/deep/path"; const testFile = `${testDir}/test.json`; try { await Deno.remove("./test_output_nested", { recursive: true }); } catch { // Ignore if doesn't exist } const stream = await createFileStream(testFile); assertExists(stream); // Verify nested directories were created const stat = await Deno.stat(testDir); assertEquals(stat.isDirectory, true); stream.close(); await Deno.remove("./test_output_nested", { recursive: true }); }); Deno.test("createFileStream - writes data correctly", async () => { const testDir = "./test_output_write"; const testFile = `${testDir}/test.txt`; try { await Deno.remove(testDir, { recursive: true }); } catch { // Ignore if doesn't exist } const stream = await createFileStream(testFile); const writer = stream.getWriter(); const testData = new TextEncoder().encode("Hello, World!"); await writer.write(testData); await writer.close(); const content = await Deno.readTextFile(testFile); assertEquals(content, "Hello, World!"); await Deno.remove(testDir, { recursive: true }); }); Deno.test("createFileStream - truncates existing file", async () => { const testDir = "./test_output_truncate"; const testFile = `${testDir}/test.txt`; try { await Deno.remove(testDir, { recursive: true }); } catch { // Ignore if doesn't exist } await Deno.mkdir(testDir, { recursive: true }); await Deno.writeTextFile(testFile, "Old content"); const stream = await createFileStream(testFile); const writer = stream.getWriter(); const testData = new TextEncoder().encode("New content"); await writer.write(testData); await writer.close(); // Verify file was truncated and new content written const content = await Deno.readTextFile(testFile); assertEquals(content, "New content"); await Deno.remove(testDir, { recursive: true }); }); Deno.test("writeObjectToStream - writes Note object with default options", { sanitizeResources: false, }, async () => { const testDir = "./test_output_note"; const testFile = `${testDir}/note.txt`; try { await Deno.remove(testDir, { recursive: true }); } catch { // Ignore if doesn't exist } const note = new Note({ id: new URL("https://example.com/notes/1"), content: "Hello, fediverse!", }); const options = { firstKnock: "rfc9421" as const, separator: "----", output: testFile, }; const contextLoader = await getContextLoader({}); await writeObjectToStream(note, options, contextLoader); const content = await Deno.readTextFile(testFile); assertExists(content); assertEquals(content.includes("Hello, fediverse!"), true); assertEquals(content.includes("Note"), true); await Deno.remove(testDir, { recursive: true }); }); Deno.test("writeObjectToStream - writes Activity object in raw JSON-LD format", async () => { const testDir = "./test_output_activity"; const testFile = `${testDir}/activity.json`; try { await Deno.remove(testDir, { recursive: true }); } catch { // Ignore if doesn't exist } const activity = new Activity({ id: new URL("https://example.com/activities/1"), }); const options = { firstKnock: "rfc9421" as const, separator: "----", output: testFile, raw: true, }; const contextLoader = await getContextLoader({}); await writeObjectToStream(activity, options, contextLoader); // Verify file exists and contains JSON-LD const content = await Deno.readTextFile(testFile); assertExists(content); assertEquals(content.includes("@context"), true); assertEquals(content.includes("id"), true); await Deno.remove(testDir, { recursive: true }); }); Deno.test("writeObjectToStream - writes object in compact JSON-LD format", async () => { const testDir = "./test_output_compact"; const testFile = `${testDir}/compact.json`; try { await Deno.remove(testDir, { recursive: true }); } catch { // Ignore if doesn't exist } const note = new Note({ id: new URL("https://example.com/notes/1"), content: "Test note", }); const options = { firstKnock: "rfc9421" as const, separator: "----", output: testFile, compact: true, }; const contextLoader = await getContextLoader({}); await writeObjectToStream(note, options, contextLoader); // Verify file exists and contains compacted JSON-LD const content = await Deno.readTextFile(testFile); assertExists(content); assertEquals(content.includes("Test note"), true); await Deno.remove(testDir, { recursive: true }); }); Deno.test("writeObjectToStream - writes object in expanded JSON-LD format", async () => { const testDir = "./test_output_expand"; const testFile = `${testDir}/expand.json`; try { await Deno.remove(testDir, { recursive: true }); } catch { // Ignore if doesn't exist } const note = new Note({ id: new URL("https://example.com/notes/1"), content: "Test note for expansion", }); const options = { firstKnock: "rfc9421" as const, separator: "----", output: testFile, expand: true, }; const contextLoader = await getContextLoader({}); await writeObjectToStream(note, options, contextLoader); const content = await Deno.readTextFile(testFile); assertExists(content); assertEquals(content.includes("Test note for expansion"), true); await Deno.remove(testDir, { recursive: true }); }); Deno.test("writeObjectToStream - writes to stdout when no output file specified", async () => { const note = new Note({ id: new URL("https://example.com/notes/1"), content: "Test stdout note", }); const options = { firstKnock: "rfc9421" as const, separator: "----", }; const contextLoader = await getContextLoader({}); await writeObjectToStream(note, options, contextLoader); }); Deno.test("writeObjectToStream - handles empty content properly", async () => { const testDir = "./test_output_empty"; const testFile = `${testDir}/empty.txt`; try { await Deno.remove(testDir, { recursive: true }); } catch { // Ignore if doesn't exist } const note = new Note({ id: new URL("https://example.com/notes/1"), }); const options = { firstKnock: "rfc9421" as const, separator: "----", output: testFile, }; const contextLoader = await getContextLoader({}); await writeObjectToStream(note, options, contextLoader); const content = await Deno.readTextFile(testFile); assertExists(content); assertEquals(content.includes("Note"), true); await Deno.remove(testDir, { recursive: true }); });
cli/lookup.ts +115 −20 Original line number Diff line number Diff line Loading @@ -15,15 +15,114 @@ import { traverseCollection, } from "@fedify/fedify"; import { getLogger } from "@logtape/logtape"; import { dirname, isAbsolute, resolve } from "@std/path"; import ora from "ora"; import { getContextLoader, getDocumentLoader } from "./docloader.ts"; import { spawnTemporaryServer, type TemporaryServer } from "./tempserver.ts"; import { printJson } from "./utils.ts"; const logger = getLogger(["fedify", "cli", "lookup"]); const sigSpec = new EnumType(["draft-cavage-http-signatures-12", "rfc9421"]); interface CommandOptions { authorizedFetch?: boolean; firstKnock: "draft-cavage-http-signatures-12" | "rfc9421"; traverse?: boolean; suppressErrors?: boolean; raw?: boolean; compact?: boolean; expand?: boolean; userAgent?: string; separator: string; output?: string; } export async function createFileStream( outputPath: string, ): Promise<WritableStream> { try { const filepath = isAbsolute(outputPath) ? outputPath : resolve(Deno.env.get("PWD") || Deno.cwd(), outputPath); const parentDir = dirname(filepath); await Deno.mkdir(parentDir, { recursive: true }); const file = await Deno.open(filepath, { write: true, create: true, truncate: true, }); return new WritableStream({ write: (chunk) => file.write(chunk).then(() => {}), close: () => file.close(), abort: (reason) => { file.close(); throw reason; }, }); } catch (err) { const spinner = ora({ text: `Failed to write output to ${colors.red(outputPath)}.`, discardStdin: false, }); spinner.fail(); console.error(`Error: ${String(err)}`); if (err instanceof Deno.errors.PermissionDenied) { console.error( "Permission denied. Try running with proper permissions.", ); } else if (err instanceof Deno.errors.NotFound) { console.error("Path does not exist or is invalid."); } else if (err instanceof Deno.errors.IsADirectory) { console.error("The specified path is a directory, not a file."); } Deno.exit(1); } } export async function writeObjectToStream( object: Object | Link, options: CommandOptions, contextLoader: DocumentLoader, ): Promise<void> { const stream = options.output ? await createFileStream(options.output) : Deno.stdout.writable; const writer = stream.getWriter(); try { let content; if (options.raw) { content = await object.toJsonLd({ contextLoader }); } else if (options.compact) { content = await object.toJsonLd({ format: "compact", contextLoader }); } else if (options.expand) { content = await object.toJsonLd({ format: "expand", contextLoader }); } else { content = object; } content = Deno.inspect(content, { colors: !(options.output), }); const encoder = new TextEncoder(); const bytes = encoder.encode(content + "\n"); await writer.write(bytes); } finally { writer.releaseLock(); if (options.output) { await stream.close(); } } } export const command = new Command() .type("sig-spec", sigSpec) .arguments("<...urls:string>") Loading Loading @@ -65,6 +164,10 @@ export const command = new Command() "collection items.", { default: "----" }, ) .option( "-o, --output <file>", "Specify the output file path.", ) .action(async (options, ...urls: string[]) => { if (urls.length < 1) { console.error("At least one URL or actor handle must be provided."); Loading @@ -75,6 +178,7 @@ export const command = new Command() ); Deno.exit(1); } const spinner = ora({ text: `Looking up the ${ options.traverse ? "collection" : urls.length > 1 ? "objects" : "object" Loading Loading @@ -143,26 +247,11 @@ export const command = new Command() }, ); } spinner.text = `Looking up the ${ options.traverse ? "collection" : urls.length > 1 ? "objects" : "object" }...`; async function printObject(object: Object | Link): Promise<void> { if (options.raw) { printJson(await object.toJsonLd({ contextLoader })); } else if (options.compact) { printJson( await object.toJsonLd({ format: "compact", contextLoader }), ); } else if (options.expand) { printJson( await object.toJsonLd({ format: "expand", contextLoader }), ); } else { console.log(object); } } if (options.traverse) { const url = urls[0]; const collection = await lookupObject(url, { Loading Loading @@ -198,8 +287,8 @@ export const command = new Command() suppressError: options.suppressErrors, }) ) { if (i > 0) console.log(options.separator); printObject(item); if (!options.output && i > 0) console.log(options.separator); await writeObjectToStream(item, options, contextLoader); i++; } } catch (error) { Loading @@ -218,6 +307,7 @@ export const command = new Command() Deno.exit(1); } spinner.succeed("Successfully fetched all items in the collection."); await server?.close(); Deno.exit(0); } Loading Loading @@ -254,7 +344,7 @@ export const command = new Command() success = false; } else { spinner.succeed(`Fetched object: ${colors.green(url)}.`); printObject(object); await writeObjectToStream(object, options, contextLoader); if (i < urls.length - 1) { console.log(options.separator); } Loading @@ -274,6 +364,11 @@ export const command = new Command() if (!success) { Deno.exit(1); } if (success && options.output) { spinner.succeed( `Successfully wrote output to ${colors.green(options.output)}.`, ); } }); // cSpell: ignore sigspec
docs/cli.md +12 −0 Original line number Diff line number Diff line Loading @@ -811,6 +811,18 @@ It does not affect the output when looking up a single object. > The separator is also used when looking up a collection object with the > [`-t`/`--traverse`](#t-traverse-traverse-the-collection) option. ### `-o`/`--output`: Output file path *This option is available since Fedify 1.8.0.* You can specify the output file path to save lookup results, instead of printing results to stdout. For example, to save the retrieved information about the specified objects to a given path, run the command below: ~~~~ sh fedify lookup -o actors.json @fedify@hollo.social @hongminhee@fosstodon.org ~~~~ `fedify inbox`: Ephemeral inbox server -------------------------------------- Loading