Skip to content

Commit 99d0d86

Browse files
committed
Merge pull request #449 from sij411/feat/optique-traverse
Support multiple URLs for -t/--traverse in cli command fedify lookup
2 parents 7922f81 + f894e9b commit 99d0d86

File tree

2 files changed

+91
-73
lines changed

2 files changed

+91
-73
lines changed

CHANGES.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,14 @@ To be released.
8484
to trash and continue the initialization from new created directory.
8585
- Ask again if some options is not specified or invalid.
8686

87+
- The `fedify lookup` command now supports multiple URLs with the
88+
`-t`/`--traverse` option, allowing users to traverse multiple collections
89+
in a single command. [[#408], [#449] by Jiwon Kwon]
90+
8791
[#397]: https://github.com/fedify-dev/fedify/issues/397
92+
[#408]: https://github.com/fedify-dev/fedify/issues/408
8893
[#435]: https://github.com/fedify-dev/fedify/issues/435
94+
[#449]: https://github.com/fedify-dev/fedify/pull/449
8995

9096

9197
Version 1.9.0

packages/cli/src/lookup.ts

Lines changed: 85 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ const traverseOption = withDefault(
6969
object({
7070
traverse: flag("-t", "--traverse", {
7171
description:
72-
message`Traverse the given collection to fetch all items. If it is turned on, the argument cannot be multiple.`,
72+
message`Traverse the given collection(s) to fetch all items.`,
7373
}),
7474
suppressErrors: option("-S", "--suppress-errors", {
7575
description:
@@ -276,15 +276,9 @@ function handleTimeoutError(
276276
}
277277

278278
export async function runLookup(command: InferValue<typeof lookupCommand>) {
279-
// FIXME: Implement -t, --traverse when multiple URLs are provided
280279
if (command.urls.length < 1) {
281280
printError(message`At least one URL or actor handle must be provided.`);
282281
process.exit(1);
283-
} else if (command.traverse && command.urls.length > 1) {
284-
printError(
285-
message`The -t/--traverse option cannot be used with multiple arguments.`,
286-
);
287-
process.exit(1);
288282
}
289283

290284
// Enable Debug mode if requested
@@ -389,84 +383,103 @@ export async function runLookup(command: InferValue<typeof lookupCommand>) {
389383
}...`;
390384

391385
if (command.traverse) {
392-
const url = command.urls[0];
393-
let collection: APObject | null;
394-
try {
395-
collection = await lookupObject(url, {
396-
documentLoader: authLoader ?? documentLoader,
397-
contextLoader,
398-
userAgent: command.userAgent,
399-
});
400-
} catch (error) {
401-
if (error instanceof TimeoutError) {
402-
handleTimeoutError(spinner, command.timeout, url);
403-
} else {
386+
let totalItems = 0;
387+
388+
for (let urlIndex = 0; urlIndex < command.urls.length; urlIndex++) {
389+
const url = command.urls[urlIndex];
390+
391+
if (urlIndex > 0) {
392+
spinner.text = `Looking up collection ${
393+
urlIndex + 1
394+
}/${command.urls.length}...`;
395+
}
396+
397+
let collection: APObject | null;
398+
try {
399+
collection = await lookupObject(url, {
400+
documentLoader: authLoader ?? documentLoader,
401+
contextLoader,
402+
userAgent: command.userAgent,
403+
});
404+
} catch (error) {
405+
if (error instanceof TimeoutError) {
406+
handleTimeoutError(spinner, command.timeout, url);
407+
} else {
408+
spinner.fail(`Failed to fetch object: ${colors.red(url)}.`);
409+
if (authLoader == null) {
410+
printError(
411+
message`It may be a private object. Try with -a/--authorized-fetch.`,
412+
);
413+
}
414+
}
415+
await server?.close();
416+
process.exit(1);
417+
}
418+
if (collection == null) {
404419
spinner.fail(`Failed to fetch object: ${colors.red(url)}.`);
405420
if (authLoader == null) {
406421
printError(
407422
message`It may be a private object. Try with -a/--authorized-fetch.`,
408423
);
409424
}
425+
await server?.close();
426+
process.exit(1);
410427
}
411-
await server?.close();
412-
process.exit(1);
413-
}
414-
if (collection == null) {
415-
spinner.fail(`Failed to fetch object: ${colors.red(url)}.`);
416-
if (authLoader == null) {
417-
printError(
418-
message`It may be a private object. Try with -a/--authorized-fetch.`,
419-
);
420-
}
421-
await server?.close();
422-
process.exit(1);
423-
}
424-
if (!(collection instanceof Collection)) {
425-
spinner.fail(
426-
`Not a collection: ${colors.red(url)}. ` +
427-
"The -t/--traverse option requires a collection.",
428-
);
429-
await server?.close();
430-
process.exit(1);
431-
}
432-
spinner.succeed(`Fetched collection: ${colors.green(url)}.`);
433-
434-
try {
435-
let i = 0;
436-
for await (
437-
const item of traverseCollection(collection, {
438-
documentLoader: authLoader ?? documentLoader,
439-
contextLoader,
440-
suppressError: command.suppressErrors,
441-
})
442-
) {
443-
if (!command.output && i > 0) print(message`${command.separator}`);
444-
await writeObjectToStream(
445-
item,
446-
command.output,
447-
command.format,
448-
contextLoader,
428+
if (!(collection instanceof Collection)) {
429+
spinner.fail(
430+
`Not a collection: ${colors.red(url)}. ` +
431+
"The -t/--traverse option requires a collection.",
449432
);
450-
i++;
433+
await server?.close();
434+
process.exit(1);
451435
}
452-
} catch (error) {
453-
logger.error("Failed to complete the traversal: {error}", { error });
454-
if (error instanceof TimeoutError) {
455-
handleTimeoutError(spinner, command.timeout);
456-
} else {
457-
spinner.fail("Failed to complete the traversal.");
458-
if (authLoader == null) {
459-
printError(
460-
message`It may be a private object. Try with -a/--authorized-fetch.`,
436+
spinner.succeed(`Fetched collection: ${colors.green(url)}.`);
437+
438+
try {
439+
let collectionItems = 0;
440+
for await (
441+
const item of traverseCollection(collection, {
442+
documentLoader: authLoader ?? documentLoader,
443+
contextLoader,
444+
suppressError: command.suppressErrors,
445+
})
446+
) {
447+
if (!command.output && (totalItems > 0 || collectionItems > 0)) {
448+
print(message`${command.separator}`);
449+
}
450+
await writeObjectToStream(
451+
item,
452+
command.output,
453+
command.format,
454+
contextLoader,
461455
);
456+
collectionItems++;
457+
totalItems++;
458+
}
459+
} catch (error) {
460+
logger.error("Failed to complete the traversal for {url}: {error}", {
461+
url,
462+
error,
463+
});
464+
if (error instanceof TimeoutError) {
465+
handleTimeoutError(spinner, command.timeout, url);
462466
} else {
463-
printError(
464-
message`Use the -S/--suppress-errors option to suppress partial errors.`,
467+
spinner.fail(
468+
`Failed to complete the traversal for: ${colors.red(url)}.`,
465469
);
470+
if (authLoader == null) {
471+
printError(
472+
message`It may be a private object. Try with -a/--authorized-fetch.`,
473+
);
474+
} else {
475+
printError(
476+
message`Use the -S/--suppress-errors option to suppress partial errors.`,
477+
);
478+
}
466479
}
480+
await server?.close();
481+
process.exit(1);
467482
}
468-
await server?.close();
469-
process.exit(1);
470483
}
471484
spinner.succeed("Successfully fetched all items in the collection.");
472485

@@ -495,8 +508,7 @@ export async function runLookup(command: InferValue<typeof lookupCommand>) {
495508
try {
496509
objects = await Promise.all(promises);
497510
} catch (_error) {
498-
//TODO: implement -a --authorized-fetch
499-
// await server?.close();
511+
await server?.close();
500512
process.exit(1);
501513
}
502514

0 commit comments

Comments
 (0)